From a5e7dc3c97cf960c12eacd0b65f018865c03d624 Mon Sep 17 00:00:00 2001 From: lleplat Date: Thu, 20 Nov 2025 15:53:16 +0000 Subject: [PATCH 1/6] Add ImgBuilder and tests --- .../java/qupath/ext/imglib2/ImgBuilder.java | 425 +++++++++++++++ .../qupath/ext/imglib2/TestImgBuilder.java | 490 ++++++++++++++++++ 2 files changed, 915 insertions(+) create mode 100644 src/main/java/qupath/ext/imglib2/ImgBuilder.java create mode 100644 src/test/java/qupath/ext/imglib2/TestImgBuilder.java diff --git a/src/main/java/qupath/ext/imglib2/ImgBuilder.java b/src/main/java/qupath/ext/imglib2/ImgBuilder.java new file mode 100644 index 0000000..41d43e2 --- /dev/null +++ b/src/main/java/qupath/ext/imglib2/ImgBuilder.java @@ -0,0 +1,425 @@ +package qupath.ext.imglib2; + +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.img.Img; +import net.imglib2.img.cell.Cell; +import net.imglib2.img.cell.CellGrid; +import net.imglib2.img.cell.LazyCellImg; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.ARGBType; +import net.imglib2.type.numeric.NumericType; +import net.imglib2.type.numeric.integer.ByteType; +import net.imglib2.type.numeric.integer.IntType; +import net.imglib2.type.numeric.integer.ShortType; +import net.imglib2.type.numeric.integer.UnsignedByteType; +import net.imglib2.type.numeric.integer.UnsignedIntType; +import net.imglib2.type.numeric.integer.UnsignedShortType; +import net.imglib2.type.numeric.real.DoubleType; +import net.imglib2.type.numeric.real.FloatType; +import qupath.ext.imglib2.bufferedimageaccesses.ArgbBufferedImageAccess; +import qupath.ext.imglib2.bufferedimageaccesses.ByteRasterAccess; +import qupath.ext.imglib2.bufferedimageaccesses.DoubleRasterAccess; +import qupath.ext.imglib2.bufferedimageaccesses.FloatRasterAccess; +import qupath.ext.imglib2.bufferedimageaccesses.IntRasterAccess; +import qupath.ext.imglib2.bufferedimageaccesses.ShortRasterAccess; +import qupath.lib.images.servers.ImageServer; +import qupath.lib.images.servers.ImageServerMetadata; +import qupath.lib.images.servers.PixelType; +import qupath.lib.images.servers.ServerTools; +import qupath.lib.images.servers.TileRequest; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.IntStream; + +/** + * A class to create {@link Img} or {@link RandomAccessibleInterval} from an {@link ImageServer}. + *

+ * Use a {@link #create(ImageServer)} or {@link #create(ImageServer, NativeType)} to create an instance of this class. + *

+ * This class is thread-safe. + * + * @param the type of the returned accessibles + * @param the type contained in the input image + */ +public class ImgBuilder & NumericType, A extends SizableDataAccess> { + + /** + * The index of the X axis of accessibles returned by functions of this class + */ + public static final int AXIS_X = 0; + /** + * The index of the Y axis of accessibles returned by functions of this class + */ + public static final int AXIS_Y = 1; + /** + * The index of the channel axis of accessibles returned by functions of this class + */ + public static final int AXIS_CHANNEL = 2; + /** + * The index of the Z axis of accessibles returned by functions of this class + */ + public static final int AXIS_Z = 3; + /** + * The index of the time axis of accessibles returned by functions of this class + */ + public static final int AXIS_TIME = 4; + /** + * The number of axes of accessibles returned by functions of this class + */ + public static final int NUMBER_OF_AXES = 5; + private static final CellCache DEFAULT_CELL_CACHE = new CellCache((int) (Runtime.getRuntime().maxMemory() * 0.5 / (1024 * 1024))); + private final ImageServer server; + private final Function cellCreator; + private final int numberOfChannels; + private final T type; + private CellCache cellCache = DEFAULT_CELL_CACHE; + + private ImgBuilder(ImageServer server, T type, Function cellCreator) { + if (server.nChannels() <= 0) { + throw new IllegalArgumentException(String.format("The provided image has less than one channel (%d)", server.nChannels())); + } + + this.server = server; + this.numberOfChannels = server.isRGB() ? 1 : server.nChannels(); + this.cellCreator = cellCreator; + this.type = type; + } + + /** + * Create a builder from an {@link ImageServer}. This doesn't create any accessibles yet. + *

+ * The type of the output image is not checked, which might lead to problems later when accessing pixel values of the + * returned accessibles of this class. It is recommended to use {@link #create(ImageServer, NativeType)} instead. + * + * @param server the input image + * @return a builder to create an instance of this class + * @throws IllegalArgumentException if the provided image has less than one channel + */ + public static ImgBuilder create(ImageServer server) { + if (server.isRGB()) { + return new ImgBuilder<>(server, new ARGBType(), ArgbBufferedImageAccess::new); + } else { + return switch (server.getPixelType()) { + case UINT8 -> new ImgBuilder<>( + server, + new UnsignedByteType(), + image -> new ByteRasterAccess(image.getRaster()) + ); + case INT8 -> new ImgBuilder<>( + server, + new ByteType(), + image -> new ByteRasterAccess(image.getRaster()) + ); + case UINT16 -> new ImgBuilder<>( + server, + new UnsignedShortType(), + image -> new ShortRasterAccess(image.getRaster()) + ); + case INT16 -> new ImgBuilder<>( + server, + new ShortType(), + image -> new ShortRasterAccess(image.getRaster()) + ); + case UINT32 -> new ImgBuilder<>( + server, + new UnsignedIntType(), + image -> new IntRasterAccess(image.getRaster()) + ); + case INT32 -> new ImgBuilder<>( + server, + new IntType(), + image -> new IntRasterAccess(image.getRaster()) + ); + case FLOAT32 -> new ImgBuilder<>( + server, + new FloatType(), + image -> new FloatRasterAccess(image.getRaster()) + ); + case FLOAT64 -> new ImgBuilder<>( + server, + new DoubleType(), + image -> new DoubleRasterAccess(image.getRaster()) + ); + }; + } + } + + /** + * Create a builder from an {@link ImageServer}. This doesn't create any accessibles yet. + *

+ * The provided type must be compatible with the input image: + *

+ * + * @param server the input image + * @param type the expected type of the output image + * @return a builder to create an instance of this class + * @param the type corresponding to the provided image + * @throws IllegalArgumentException if the provided type is not compatible with the input image (see above), or if the provided image + * has less than one channel + */ + public static & NumericType> ImgBuilder create(ImageServer server, T type) { + checkType(server, type); + + if (server.isRGB()) { + return new ImgBuilder<>(server, type, ArgbBufferedImageAccess::new); + } else { + return switch (server.getPixelType()) { + case UINT8, INT8 -> new ImgBuilder<>(server, type, image -> new ByteRasterAccess(image.getRaster())); + case UINT16, INT16 -> new ImgBuilder<>(server, type, image -> new ShortRasterAccess(image.getRaster())); + case UINT32, INT32 -> new ImgBuilder<>(server, type, image -> new IntRasterAccess(image.getRaster())); + case FLOAT32 -> new ImgBuilder<>(server, type, image -> new FloatRasterAccess(image.getRaster())); + case FLOAT64 -> new ImgBuilder<>(server, type, image -> new DoubleRasterAccess(image.getRaster())); + }; + } + } + + /** + * Accessibles returned by this class will be divided into cells, which will be cached to gain performance. This function sets the + * cache to use. By default, a static cache of maximal size half the amount of the {@link Runtime#maxMemory() max memory} is used. + * + * @param cellCache the cache to use + * @return this builder + * @throws NullPointerException if the provided cache is null + */ + public ImgBuilder cellCache(CellCache cellCache) { + this.cellCache = Objects.requireNonNull(cellCache); + return this; + } + + /** + * Create a list of {@link RandomAccessibleInterval} corresponding to each level of the input image. + *

+ * The {@link RandomAccessibleInterval} returned by this class are immutable. This means that any attempt to write + * data to it will result in an {@link UnsupportedOperationException}. + *

+ * See {@link #AXIS_X}, {@link #AXIS_Y}, {@link #AXIS_CHANNEL}, {@link #AXIS_Z}, and {@link #AXIS_TIME} to get the physical + * interpretation of the dimensions of the returned {@link RandomAccessibleInterval}. + *

+ * Pixels of the returned images are lazily fetched. + * + * @return a list of {@link RandomAccessibleInterval} corresponding to each level of the input image + */ + public List> buildForAllLevels() { + return IntStream.range(0, server.getMetadata().nLevels()) + .mapToObj(this::createForLevel) + .toList(); + } + + /** + * Create an {@link Img} from the input image and the provided level. + *

+ * The {@link Img} returned by this class is immutable. This means that any attempt to write data to it will result in an + * {@link UnsupportedOperationException}. + *

+ * See {@link #AXIS_X}, {@link #AXIS_Y}, {@link #AXIS_CHANNEL}, {@link #AXIS_Z}, and {@link #AXIS_TIME} to get the physical + * interpretation of the dimensions of the returned {@link Img}. + *

+ * Pixels of the returned image are lazily fetched. + * + * @param level the level to consider + * @return an {@link Img} corresponding to the provided level of the input image + * @throws IllegalArgumentException if the provided level does not match with a level of the input image + */ + public Img createForLevel(int level) { + if (level < 0 || level >= server.getMetadata().nLevels()) { + throw new IllegalArgumentException(String.format( + "The provided level %d is not within 0 and %d", + level, + server.getMetadata().nLevels() - 1 + )); + } + + List tiles = new ArrayList<>(server.getTileRequestManager().getTileRequestsForLevel(level)); + + return new LazyCellImg<>( + new CellGrid( + new long[] { + server.getMetadata().getLevel(level).getWidth(), + server.getMetadata().getLevel(level).getHeight(), + numberOfChannels, + server.nZSlices(), + server.nTimepoints() + }, + new int[] { + server.getMetadata().getPreferredTileWidth(), + server.getMetadata().getPreferredTileHeight(), + numberOfChannels, + 1, + 1 + } + ), + type, + cellIndex -> cellCache.getCell(tiles.get(Math.toIntExact(cellIndex)), this::createCell) + ); + } + + /** + * Create a {@link RandomAccessibleInterval} from the input image and the provided downsample. + *

+ * The {@link RandomAccessibleInterval} returned by this class is immutable. This means that any attempt to write data to it will result in an + * {@link UnsupportedOperationException}. + *

+ * See {@link #AXIS_X}, {@link #AXIS_Y}, {@link #AXIS_CHANNEL}, {@link #AXIS_Z}, and {@link #AXIS_TIME} to get the physical + * interpretation of the dimensions of the returned {@link RandomAccessibleInterval}. + *

+ * Values of the returned image are lazily fetched. + *

+ * If the input image has to be scaled and its {@link ImageServerMetadata#getChannelType() channel type} is + * {@link ImageServerMetadata.ChannelType#CLASSIFICATION}, then the nearest neighbor interpolation is used. + * Otherwise, the linear interpolation is used. + * + * @param downsample the downsample to apply to the input image. Must be greater than 0 + * @return a {@link RandomAccessibleInterval} corresponding to the input image with the provided downsample applied + * @throws IllegalArgumentException if the provided downsample is not greater than 0 + */ + public RandomAccessibleInterval createForDownsample(double downsample) { + if (downsample <= 0) { + throw new IllegalArgumentException(String.format("The provided downsample %f is not greater than 0", downsample)); + } + + int level = ServerTools.getPreferredResolutionLevel(server, downsample); + + if (server.getMetadata().getChannelType() == ImageServerMetadata.ChannelType.CLASSIFICATION) { + return AccessibleScaler.scaleWithNearestNeighborInterpolation(createForLevel(level), server.getDownsampleForResolution(level) / downsample); + } else { + return AccessibleScaler.scaleWithLinearInterpolation(createForLevel(level), server.getDownsampleForResolution(level) / downsample); + } + } + + private static void checkType(ImageServer server, T type) { + if (server.isRGB()) { + if (!(type instanceof ARGBType)) { + throw new IllegalArgumentException(String.format( + "The provided type %s is not an ARGBType, which is the one expected for RGB images", + type + )); + } + } else { + switch (server.getPixelType()) { + case UINT8 -> { + if (!(type instanceof UnsignedByteType)) { + throw new IllegalArgumentException(String.format( + "The provided type %s is not a ByteType, which is the one expected for non-RGB UINT8 images", + type + )); + } + } + case INT8 -> { + if (!(type instanceof ByteType)) { + throw new IllegalArgumentException(String.format( + "The provided type %s is not a UnsignedByteType, which is the one expected for non-RGB INT8 images", + type + )); + } + } + case UINT16 -> { + if (!(type instanceof UnsignedShortType)) { + throw new IllegalArgumentException(String.format( + "The provided type %s is not a UnsignedShortType, which is the one expected for non-RGB UINT16 images", + type + )); + } + } + case INT16 -> { + if (!(type instanceof ShortType)) { + throw new IllegalArgumentException(String.format( + "The provided type %s is not a ShortType, which is the one expected for non-RGB INT16 images", + type + )); + } + } + case UINT32 -> { + if (!(type instanceof UnsignedIntType)) { + throw new IllegalArgumentException(String.format( + "The provided type %s is not a UnsignedIntType, which is the one expected for non-RGB UINT32 images", + type + )); + } + } + case INT32 -> { + if (!(type instanceof IntType)) { + throw new IllegalArgumentException(String.format( + "The provided type %s is not a IntType, which is the one expected for non-RGB INT32 images", + type + )); + } + } + case FLOAT32 -> { + if (!(type instanceof FloatType)) { + throw new IllegalArgumentException(String.format( + "The provided type %s is not a FloatType, which is the one expected for non-RGB FLOAT32 images", + type + )); + } + } + case FLOAT64 -> { + if (!(type instanceof DoubleType)) { + throw new IllegalArgumentException(String.format( + "The provided type %s is not a DoubleType, which is the one expected for non-RGB FLOAT64 images", + type + )); + } + } + } + } + } + + private Cell createCell(TileRequest tile) { + BufferedImage image; + try { + image = server.readRegion(tile.getRegionRequest()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return new Cell<>( + new int[]{ image.getWidth(), image.getHeight(), numberOfChannels, 1, 1 }, + new long[]{ tile.getTileX(), tile.getTileY(), 0, tile.getZ(), tile.getT()}, + cellCreator.apply(image) + ); + } +} diff --git a/src/test/java/qupath/ext/imglib2/TestImgBuilder.java b/src/test/java/qupath/ext/imglib2/TestImgBuilder.java new file mode 100644 index 0000000..38cce46 --- /dev/null +++ b/src/test/java/qupath/ext/imglib2/TestImgBuilder.java @@ -0,0 +1,490 @@ +package qupath.ext.imglib2; + +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.img.Img; +import net.imglib2.type.numeric.ARGBType; +import net.imglib2.type.numeric.integer.ByteType; +import net.imglib2.type.numeric.integer.IntType; +import net.imglib2.type.numeric.integer.ShortType; +import net.imglib2.type.numeric.integer.UnsignedByteType; +import net.imglib2.type.numeric.integer.UnsignedIntType; +import net.imglib2.type.numeric.integer.UnsignedShortType; +import net.imglib2.type.numeric.real.DoubleType; +import net.imglib2.type.numeric.real.FloatType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import qupath.lib.common.ColorTools; +import qupath.lib.images.servers.AbstractImageServer; +import qupath.lib.images.servers.ImageChannel; +import qupath.lib.images.servers.ImageServer; +import qupath.lib.images.servers.ImageServerBuilder; +import qupath.lib.images.servers.ImageServerMetadata; +import qupath.lib.images.servers.PixelType; +import qupath.lib.regions.RegionRequest; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferDouble; +import java.awt.image.DataBufferFloat; +import java.awt.image.DataBufferInt; +import java.awt.image.DataBufferShort; +import java.awt.image.DataBufferUShort; +import java.net.URI; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +public class TestImgBuilder { + + @Test + void Check_Rgb_Server() throws Exception { + boolean isRgb = true; + PixelType pixelType = PixelType.UINT8; + ImageServer imageServer = new GenericImageServer(isRgb, pixelType); + + Img img = ImgBuilder.create(imageServer, new ARGBType()).createForLevel(0); + + Utils.assertArgbRandomAccessibleEquals(img, (x, y, channel, z, t) -> ARGBType.rgba(255, 0, 0, 0), 1); + + imageServer.close(); + } + + @Test + void Check_Uint8_Server() throws Exception { + boolean isRgb = false; + PixelType pixelType = PixelType.UINT8; + ImageServer imageServer = new GenericImageServer(isRgb, pixelType); + + Img img = ImgBuilder.create(imageServer, new UnsignedByteType()).createForLevel(0); + + Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); + + imageServer.close(); + } + + @Test + void Check_Int8_Server() throws Exception { + boolean isRgb = false; + PixelType pixelType = PixelType.INT8; + ImageServer imageServer = new GenericImageServer(isRgb, pixelType); + + Img img = ImgBuilder.create(imageServer, new ByteType()).createForLevel(0); + + Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); + + imageServer.close(); + } + + @Test + void Check_Uint16_Server() throws Exception { + boolean isRgb = false; + PixelType pixelType = PixelType.UINT16; + ImageServer imageServer = new GenericImageServer(isRgb, pixelType); + + Img img = ImgBuilder.create(imageServer, new UnsignedShortType()).createForLevel(0); + + Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); + + imageServer.close(); + } + + @Test + void Check_Int16_Server() throws Exception { + boolean isRgb = false; + PixelType pixelType = PixelType.INT16; + ImageServer imageServer = new GenericImageServer(isRgb, pixelType); + + Img img = ImgBuilder.create(imageServer, new ShortType()).createForLevel(0); + + Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); + + imageServer.close(); + } + + @Test + void Check_Uint32_Server() throws Exception { + boolean isRgb = false; + PixelType pixelType = PixelType.UINT32; + ImageServer imageServer = new GenericImageServer(isRgb, pixelType); + + Img img = ImgBuilder.create(imageServer, new UnsignedIntType()).createForLevel(0); + + Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); + + imageServer.close(); + } + + @Test + void Check_Int32_Server() throws Exception { + boolean isRgb = false; + PixelType pixelType = PixelType.INT32; + ImageServer imageServer = new GenericImageServer(isRgb, pixelType); + + Img img = ImgBuilder.create(imageServer, new IntType()).createForLevel(0); + + Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); + + imageServer.close(); + } + + @Test + void Check_Float32_Server() throws Exception { + boolean isRgb = false; + PixelType pixelType = PixelType.FLOAT32; + ImageServer imageServer = new GenericImageServer(isRgb, pixelType); + + Img img = ImgBuilder.create(imageServer, new FloatType()).createForLevel(0); + + Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); + + imageServer.close(); + } + + @Test + void Check_Float64_Server() throws Exception { + boolean isRgb = false; + PixelType pixelType = PixelType.FLOAT64; + ImageServer imageServer = new GenericImageServer(isRgb, pixelType); + + Img img = ImgBuilder.create(imageServer, new DoubleType()).createForLevel(0); + + Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); + + imageServer.close(); + } + + @Test + void Check_X_Dimension_Size() throws Exception { + ImageServer imageServer = new ComplexDoubleImageServer(); + int expectedSize = imageServer.getWidth(); + Img img = ImgBuilder.create(imageServer).createForLevel(0); + int dimensionIndex = ImgBuilder.AXIS_X; + + Assertions.assertEquals(expectedSize, img.dimension(dimensionIndex)); + + imageServer.close(); + } + + @Test + void Check_Y_Dimension_Size() throws Exception { + ImageServer imageServer = new ComplexDoubleImageServer(); + int expectedSize = imageServer.getHeight(); + Img img = ImgBuilder.create(imageServer).createForLevel(0); + int dimensionIndex = ImgBuilder.AXIS_Y; + + Assertions.assertEquals(expectedSize, img.dimension(dimensionIndex)); + + imageServer.close(); + } + + @Test + void Check_Channel_Dimension_Size() throws Exception { + ImageServer imageServer = new ComplexDoubleImageServer(); + int expectedSize = imageServer.nChannels(); + Img img = ImgBuilder.create(imageServer).createForLevel(0); + int dimensionIndex = ImgBuilder.AXIS_CHANNEL; + + Assertions.assertEquals(expectedSize, img.dimension(dimensionIndex)); + + imageServer.close(); + } + + @Test + void Check_Z_Dimension_Size() throws Exception { + ImageServer imageServer = new ComplexDoubleImageServer(); + int expectedSize = imageServer.getMetadata().getSizeZ(); + Img img = ImgBuilder.create(imageServer).createForLevel(0); + int dimensionIndex = ImgBuilder.AXIS_Z; + + Assertions.assertEquals(expectedSize, img.dimension(dimensionIndex)); + + imageServer.close(); + } + + @Test + void Check_Time_Dimension_Size() throws Exception { + ImageServer imageServer = new ComplexDoubleImageServer(); + int expectedSize = imageServer.getMetadata().getSizeT(); + Img img = ImgBuilder.create(imageServer).createForLevel(0); + int dimensionIndex = ImgBuilder.AXIS_TIME; + + Assertions.assertEquals(expectedSize, img.dimension(dimensionIndex)); + + imageServer.close(); + } + + @Test + void Check_Pixels_Of_Level_0() throws Exception { + int level = 0; + ImageServer imageServer = new ComplexDoubleImageServer(); + double downsample = imageServer.getDownsampleForResolution(level); + + Img img = ImgBuilder.create(imageServer, new DoubleType()).createForLevel(level); + + Utils.assertRandomAccessibleEquals(img, ComplexDoubleImageServer::getPixel, downsample); + + imageServer.close(); + } + + @Test + void Check_Pixels_Of_Level_1() throws Exception { + int level = 1; + ImageServer imageServer = new ComplexDoubleImageServer(); + double downsample = imageServer.getDownsampleForResolution(level); + + Img img = ImgBuilder.create(imageServer, new DoubleType()).createForLevel(level); + + Utils.assertRandomAccessibleEquals(img, ComplexDoubleImageServer::getPixel, downsample); + + imageServer.close(); + } + + @Test + void Check_Pixels_Of_Downsample_1() throws Exception { + int level = 0; + ImageServer imageServer = new ComplexDoubleImageServer(); + double downsample = imageServer.getDownsampleForResolution(level); + + RandomAccessibleInterval img = ImgBuilder.create(imageServer, new DoubleType()).createForDownsample(downsample); + + Utils.assertRandomAccessibleEquals(img, ComplexDoubleImageServer::getPixel, downsample); + + imageServer.close(); + } + + @Test + void Check_Pixels_Of_Downsample_4() throws Exception { + int level = 1; + ImageServer imageServer = new ComplexDoubleImageServer(); + double downsample = imageServer.getDownsampleForResolution(level); + + RandomAccessibleInterval img = ImgBuilder.create(imageServer, new DoubleType()).createForDownsample(downsample); + + Utils.assertRandomAccessibleEquals(img, ComplexDoubleImageServer::getPixel, downsample); + + imageServer.close(); + } + + private static class GenericImageServer extends AbstractImageServer { + + private static final AtomicInteger counter = new AtomicInteger(0); + private final ImageServerMetadata metadata; + private final String id; + + public GenericImageServer(boolean isRgb, PixelType pixelType) { + super(BufferedImage.class); + + this.metadata = new ImageServerMetadata.Builder() + .width(1) + .height(1) + .channels(List.of(ImageChannel.RED)) + .rgb(isRgb) + .pixelType(pixelType) + .build(); + + // Each test uses the same cache, so each created server must have different IDs + this.id = String.format("Generic server %d", counter.incrementAndGet()); + } + + @Override + protected ImageServerBuilder.ServerBuilder createServerBuilder() { + return null; + } + + @Override + protected String createID() { + return id; + } + + @Override + public Collection getURIs() { + return List.of(); + } + + @Override + public BufferedImage readRegion(RegionRequest request) { + if (getMetadata().isRGB()) { + BufferedImage image = new BufferedImage(request.getWidth(), request.getHeight(), BufferedImage.TYPE_INT_ARGB); + int[] rgbArray = new int[request.getWidth() * request.getHeight()]; + Arrays.fill(rgbArray, ARGBType.rgba(255, 0, 0, 0)); + image.setRGB(0, 0, request.getWidth(), request.getHeight(), rgbArray, 0, request.getWidth()); + return image; + } else { + DataBuffer dataBuffer = createDataBuffer(request); + + return Utils.createBufferedImage(dataBuffer, request.getWidth(), request.getHeight(), nChannels(), getPixelType()); + } + } + + @Override + public String getServerType() { + return ""; + } + + @Override + public ImageServerMetadata getOriginalMetadata() { + return metadata; + } + + private DataBuffer createDataBuffer(RegionRequest request) { + return switch (getMetadata().getPixelType()) { + case UINT8, INT8 -> { + byte[][] array = new byte[nChannels()][]; + + for (int c = 0; c < array.length; c++) { + array[c] = new byte[request.getWidth() * request.getHeight()]; + Arrays.fill(array[c], (byte) 1); + } + + yield new DataBufferByte(array, array[0].length); + } + case UINT16, INT16 -> { + short[][] array = new short[nChannels()][]; + + for (int c = 0; c < array.length; c++) { + array[c] = new short[request.getWidth() * request.getHeight()]; + Arrays.fill(array[c], (short) 1); + } + + yield getMetadata().getPixelType().equals(PixelType.UINT16) ? + new DataBufferUShort(array, array[0].length) : + new DataBufferShort(array, array[0].length); + } + case UINT32, INT32 -> { + int[][] array = new int[nChannels()][]; + + for (int c = 0; c < array.length; c++) { + array[c] = new int[request.getWidth() * request.getHeight()]; + Arrays.fill(array[c], 1); + } + + yield new DataBufferInt(array, array[0].length); + } + case FLOAT32 -> { + float[][] array = new float[nChannels()][]; + + for (int c = 0; c < array.length; c++) { + array[c] = new float[request.getWidth() * request.getHeight()]; + Arrays.fill(array[c], 1); + } + + yield new DataBufferFloat(array, array[0].length); + } + case FLOAT64 -> { + double[][] array = new double[nChannels()][]; + + for (int c = 0; c < array.length; c++) { + array[c] = new double[request.getWidth() * request.getHeight()]; + Arrays.fill(array[c], 1); + } + + yield new DataBufferDouble(array, array[0].length); + } + }; + } + } + + private static class ComplexDoubleImageServer extends AbstractImageServer { + + private static final int IMAGE_WIDTH = 64; + private static final int IMAGE_HEIGHT = 64; + private static final ImageServerMetadata metadata = new ImageServerMetadata.Builder() + .width(IMAGE_WIDTH) + .height(IMAGE_HEIGHT) + .sizeZ(3) + .sizeT(2) + .pixelType(PixelType.FLOAT64) + .channels(List.of( + ImageChannel.getInstance("c1", ColorTools.CYAN), + ImageChannel.getInstance("c2", ColorTools.BLUE), + ImageChannel.getInstance("c3", ColorTools.RED), + ImageChannel.getInstance("c4", ColorTools.GREEN), + ImageChannel.getInstance("c5", ColorTools.MAGENTA) + )) + .levelsFromDownsamples(1, 4) + .build(); + private static final AtomicInteger counter = new AtomicInteger(0); + private final String id; + + public ComplexDoubleImageServer() { + super(BufferedImage.class); + + // Each test uses the same cache, so each created server must have different IDs + this.id = String.format("Complex double server %d", counter.incrementAndGet()); + } + + @Override + protected ImageServerBuilder.ServerBuilder createServerBuilder() { + return null; + } + + @Override + protected String createID() { + return id; + } + + @Override + public Collection getURIs() { + return List.of(); + } + + @Override + public String getServerType() { + return ""; + } + + @Override + public ImageServerMetadata getOriginalMetadata() { + return metadata; + } + + @Override + public BufferedImage readRegion(RegionRequest request) { + DataBuffer dataBuffer = createDataBuffer(request); + + return Utils.createBufferedImage( + dataBuffer, + (int) (request.getWidth() / request.getDownsample()), + (int) (request.getHeight() / request.getDownsample()), + nChannels(), + getPixelType() + ); + } + + public static double getPixel(int x, int y, int channel, int z, int t) { + return z + t + channel + ((double) x / IMAGE_WIDTH + (double) y / IMAGE_HEIGHT) / 2; + } + + private DataBuffer createDataBuffer(RegionRequest request) { + double[][] array = new double[nChannels()][]; + + for (int c = 0; c < array.length; c++) { + array[c] = getPixels(request, c); + } + + return new DataBufferDouble(array, (int) ((double) (request.getWidth() * request.getHeight()) / (request.getDownsample() * request.getDownsample()))); + } + + private double[] getPixels(RegionRequest request, int channel) { + int width = (int) (request.getWidth() / request.getDownsample()); + int height = (int) (request.getHeight() / request.getDownsample()); + double[] pixels = new double[width * height]; + + for (int y=0; y Date: Thu, 20 Nov 2025 16:04:18 +0000 Subject: [PATCH 2/6] Rename accesses package --- src/main/java/qupath/ext/imglib2/ImgBuilder.java | 12 ++++++------ src/main/java/qupath/ext/imglib2/ImgCreator.java | 12 ++++++------ .../AccessTools.java | 2 +- .../ArgbBufferedImageAccess.java | 2 +- .../ByteRasterAccess.java | 2 +- .../DoubleRasterAccess.java | 2 +- .../FloatRasterAccess.java | 2 +- .../IntRasterAccess.java | 2 +- .../ShortRasterAccess.java | 2 +- .../package-info.java | 2 +- .../TestArgbBufferedImageAccess.java | 2 +- .../TestByteRasterAccess.java | 2 +- .../TestDoubleRasterAccess.java | 2 +- .../TestFloatRasterAccess.java | 2 +- .../TestIntRasterAccess.java | 2 +- .../TestShortRasterAccess.java | 2 +- 16 files changed, 26 insertions(+), 26 deletions(-) rename src/main/java/qupath/ext/imglib2/{bufferedimageaccesses => accesses}/AccessTools.java (98%) rename src/main/java/qupath/ext/imglib2/{bufferedimageaccesses => accesses}/ArgbBufferedImageAccess.java (98%) rename src/main/java/qupath/ext/imglib2/{bufferedimageaccesses => accesses}/ByteRasterAccess.java (97%) rename src/main/java/qupath/ext/imglib2/{bufferedimageaccesses => accesses}/DoubleRasterAccess.java (97%) rename src/main/java/qupath/ext/imglib2/{bufferedimageaccesses => accesses}/FloatRasterAccess.java (97%) rename src/main/java/qupath/ext/imglib2/{bufferedimageaccesses => accesses}/IntRasterAccess.java (97%) rename src/main/java/qupath/ext/imglib2/{bufferedimageaccesses => accesses}/ShortRasterAccess.java (97%) rename src/main/java/qupath/ext/imglib2/{bufferedimageaccesses => accesses}/package-info.java (79%) rename src/test/java/qupath/ext/imglib2/{bufferedimageaccesses => accesses}/TestArgbBufferedImageAccess.java (97%) rename src/test/java/qupath/ext/imglib2/{bufferedimageaccesses => accesses}/TestByteRasterAccess.java (96%) rename src/test/java/qupath/ext/imglib2/{bufferedimageaccesses => accesses}/TestDoubleRasterAccess.java (96%) rename src/test/java/qupath/ext/imglib2/{bufferedimageaccesses => accesses}/TestFloatRasterAccess.java (96%) rename src/test/java/qupath/ext/imglib2/{bufferedimageaccesses => accesses}/TestIntRasterAccess.java (96%) rename src/test/java/qupath/ext/imglib2/{bufferedimageaccesses => accesses}/TestShortRasterAccess.java (97%) diff --git a/src/main/java/qupath/ext/imglib2/ImgBuilder.java b/src/main/java/qupath/ext/imglib2/ImgBuilder.java index 41d43e2..900b02c 100644 --- a/src/main/java/qupath/ext/imglib2/ImgBuilder.java +++ b/src/main/java/qupath/ext/imglib2/ImgBuilder.java @@ -16,12 +16,12 @@ import net.imglib2.type.numeric.integer.UnsignedShortType; import net.imglib2.type.numeric.real.DoubleType; import net.imglib2.type.numeric.real.FloatType; -import qupath.ext.imglib2.bufferedimageaccesses.ArgbBufferedImageAccess; -import qupath.ext.imglib2.bufferedimageaccesses.ByteRasterAccess; -import qupath.ext.imglib2.bufferedimageaccesses.DoubleRasterAccess; -import qupath.ext.imglib2.bufferedimageaccesses.FloatRasterAccess; -import qupath.ext.imglib2.bufferedimageaccesses.IntRasterAccess; -import qupath.ext.imglib2.bufferedimageaccesses.ShortRasterAccess; +import qupath.ext.imglib2.accesses.ArgbBufferedImageAccess; +import qupath.ext.imglib2.accesses.ByteRasterAccess; +import qupath.ext.imglib2.accesses.DoubleRasterAccess; +import qupath.ext.imglib2.accesses.FloatRasterAccess; +import qupath.ext.imglib2.accesses.IntRasterAccess; +import qupath.ext.imglib2.accesses.ShortRasterAccess; import qupath.lib.images.servers.ImageServer; import qupath.lib.images.servers.ImageServerMetadata; import qupath.lib.images.servers.PixelType; diff --git a/src/main/java/qupath/ext/imglib2/ImgCreator.java b/src/main/java/qupath/ext/imglib2/ImgCreator.java index ddf8dc8..066d396 100644 --- a/src/main/java/qupath/ext/imglib2/ImgCreator.java +++ b/src/main/java/qupath/ext/imglib2/ImgCreator.java @@ -16,12 +16,12 @@ import net.imglib2.type.numeric.integer.UnsignedShortType; import net.imglib2.type.numeric.real.DoubleType; import net.imglib2.type.numeric.real.FloatType; -import qupath.ext.imglib2.bufferedimageaccesses.ArgbBufferedImageAccess; -import qupath.ext.imglib2.bufferedimageaccesses.ByteRasterAccess; -import qupath.ext.imglib2.bufferedimageaccesses.DoubleRasterAccess; -import qupath.ext.imglib2.bufferedimageaccesses.FloatRasterAccess; -import qupath.ext.imglib2.bufferedimageaccesses.IntRasterAccess; -import qupath.ext.imglib2.bufferedimageaccesses.ShortRasterAccess; +import qupath.ext.imglib2.accesses.ArgbBufferedImageAccess; +import qupath.ext.imglib2.accesses.ByteRasterAccess; +import qupath.ext.imglib2.accesses.DoubleRasterAccess; +import qupath.ext.imglib2.accesses.FloatRasterAccess; +import qupath.ext.imglib2.accesses.IntRasterAccess; +import qupath.ext.imglib2.accesses.ShortRasterAccess; import qupath.lib.images.servers.ImageServer; import qupath.lib.images.servers.ImageServerMetadata; import qupath.lib.images.servers.ServerTools; diff --git a/src/main/java/qupath/ext/imglib2/bufferedimageaccesses/AccessTools.java b/src/main/java/qupath/ext/imglib2/accesses/AccessTools.java similarity index 98% rename from src/main/java/qupath/ext/imglib2/bufferedimageaccesses/AccessTools.java rename to src/main/java/qupath/ext/imglib2/accesses/AccessTools.java index 7c075d8..fd37643 100644 --- a/src/main/java/qupath/ext/imglib2/bufferedimageaccesses/AccessTools.java +++ b/src/main/java/qupath/ext/imglib2/accesses/AccessTools.java @@ -1,4 +1,4 @@ -package qupath.ext.imglib2.bufferedimageaccesses; +package qupath.ext.imglib2.accesses; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/qupath/ext/imglib2/bufferedimageaccesses/ArgbBufferedImageAccess.java b/src/main/java/qupath/ext/imglib2/accesses/ArgbBufferedImageAccess.java similarity index 98% rename from src/main/java/qupath/ext/imglib2/bufferedimageaccesses/ArgbBufferedImageAccess.java rename to src/main/java/qupath/ext/imglib2/accesses/ArgbBufferedImageAccess.java index 0d7a6ec..7150526 100644 --- a/src/main/java/qupath/ext/imglib2/bufferedimageaccesses/ArgbBufferedImageAccess.java +++ b/src/main/java/qupath/ext/imglib2/accesses/ArgbBufferedImageAccess.java @@ -1,4 +1,4 @@ -package qupath.ext.imglib2.bufferedimageaccesses; +package qupath.ext.imglib2.accesses; import net.imglib2.img.basictypeaccess.IntAccess; import net.imglib2.img.basictypeaccess.volatiles.VolatileAccess; diff --git a/src/main/java/qupath/ext/imglib2/bufferedimageaccesses/ByteRasterAccess.java b/src/main/java/qupath/ext/imglib2/accesses/ByteRasterAccess.java similarity index 97% rename from src/main/java/qupath/ext/imglib2/bufferedimageaccesses/ByteRasterAccess.java rename to src/main/java/qupath/ext/imglib2/accesses/ByteRasterAccess.java index a7afb9a..299f394 100644 --- a/src/main/java/qupath/ext/imglib2/bufferedimageaccesses/ByteRasterAccess.java +++ b/src/main/java/qupath/ext/imglib2/accesses/ByteRasterAccess.java @@ -1,4 +1,4 @@ -package qupath.ext.imglib2.bufferedimageaccesses; +package qupath.ext.imglib2.accesses; import net.imglib2.img.basictypeaccess.ByteAccess; import net.imglib2.img.basictypeaccess.volatiles.VolatileAccess; diff --git a/src/main/java/qupath/ext/imglib2/bufferedimageaccesses/DoubleRasterAccess.java b/src/main/java/qupath/ext/imglib2/accesses/DoubleRasterAccess.java similarity index 97% rename from src/main/java/qupath/ext/imglib2/bufferedimageaccesses/DoubleRasterAccess.java rename to src/main/java/qupath/ext/imglib2/accesses/DoubleRasterAccess.java index f8e68ba..6075050 100644 --- a/src/main/java/qupath/ext/imglib2/bufferedimageaccesses/DoubleRasterAccess.java +++ b/src/main/java/qupath/ext/imglib2/accesses/DoubleRasterAccess.java @@ -1,4 +1,4 @@ -package qupath.ext.imglib2.bufferedimageaccesses; +package qupath.ext.imglib2.accesses; import net.imglib2.img.basictypeaccess.DoubleAccess; import net.imglib2.img.basictypeaccess.volatiles.VolatileAccess; diff --git a/src/main/java/qupath/ext/imglib2/bufferedimageaccesses/FloatRasterAccess.java b/src/main/java/qupath/ext/imglib2/accesses/FloatRasterAccess.java similarity index 97% rename from src/main/java/qupath/ext/imglib2/bufferedimageaccesses/FloatRasterAccess.java rename to src/main/java/qupath/ext/imglib2/accesses/FloatRasterAccess.java index f324d10..ecab812 100644 --- a/src/main/java/qupath/ext/imglib2/bufferedimageaccesses/FloatRasterAccess.java +++ b/src/main/java/qupath/ext/imglib2/accesses/FloatRasterAccess.java @@ -1,4 +1,4 @@ -package qupath.ext.imglib2.bufferedimageaccesses; +package qupath.ext.imglib2.accesses; import net.imglib2.img.basictypeaccess.FloatAccess; import net.imglib2.img.basictypeaccess.volatiles.VolatileAccess; diff --git a/src/main/java/qupath/ext/imglib2/bufferedimageaccesses/IntRasterAccess.java b/src/main/java/qupath/ext/imglib2/accesses/IntRasterAccess.java similarity index 97% rename from src/main/java/qupath/ext/imglib2/bufferedimageaccesses/IntRasterAccess.java rename to src/main/java/qupath/ext/imglib2/accesses/IntRasterAccess.java index 03472a5..b391fb5 100644 --- a/src/main/java/qupath/ext/imglib2/bufferedimageaccesses/IntRasterAccess.java +++ b/src/main/java/qupath/ext/imglib2/accesses/IntRasterAccess.java @@ -1,4 +1,4 @@ -package qupath.ext.imglib2.bufferedimageaccesses; +package qupath.ext.imglib2.accesses; import net.imglib2.img.basictypeaccess.IntAccess; import net.imglib2.img.basictypeaccess.volatiles.VolatileAccess; diff --git a/src/main/java/qupath/ext/imglib2/bufferedimageaccesses/ShortRasterAccess.java b/src/main/java/qupath/ext/imglib2/accesses/ShortRasterAccess.java similarity index 97% rename from src/main/java/qupath/ext/imglib2/bufferedimageaccesses/ShortRasterAccess.java rename to src/main/java/qupath/ext/imglib2/accesses/ShortRasterAccess.java index ca8a9ad..7a7c04a 100644 --- a/src/main/java/qupath/ext/imglib2/bufferedimageaccesses/ShortRasterAccess.java +++ b/src/main/java/qupath/ext/imglib2/accesses/ShortRasterAccess.java @@ -1,4 +1,4 @@ -package qupath.ext.imglib2.bufferedimageaccesses; +package qupath.ext.imglib2.accesses; import net.imglib2.img.basictypeaccess.ShortAccess; import net.imglib2.img.basictypeaccess.volatiles.VolatileAccess; diff --git a/src/main/java/qupath/ext/imglib2/bufferedimageaccesses/package-info.java b/src/main/java/qupath/ext/imglib2/accesses/package-info.java similarity index 79% rename from src/main/java/qupath/ext/imglib2/bufferedimageaccesses/package-info.java rename to src/main/java/qupath/ext/imglib2/accesses/package-info.java index 730cbe4..a3e5d33 100644 --- a/src/main/java/qupath/ext/imglib2/bufferedimageaccesses/package-info.java +++ b/src/main/java/qupath/ext/imglib2/accesses/package-info.java @@ -2,4 +2,4 @@ * This package contains {@link net.imglib2.img.basictypeaccess.DataAccess} whose values are taken * from {@link java.awt.image.BufferedImage} or {@link java.awt.image.Raster}. */ -package qupath.ext.imglib2.bufferedimageaccesses; \ No newline at end of file +package qupath.ext.imglib2.accesses; \ No newline at end of file diff --git a/src/test/java/qupath/ext/imglib2/bufferedimageaccesses/TestArgbBufferedImageAccess.java b/src/test/java/qupath/ext/imglib2/accesses/TestArgbBufferedImageAccess.java similarity index 97% rename from src/test/java/qupath/ext/imglib2/bufferedimageaccesses/TestArgbBufferedImageAccess.java rename to src/test/java/qupath/ext/imglib2/accesses/TestArgbBufferedImageAccess.java index dc4aaf9..e4bf59b 100644 --- a/src/test/java/qupath/ext/imglib2/bufferedimageaccesses/TestArgbBufferedImageAccess.java +++ b/src/test/java/qupath/ext/imglib2/accesses/TestArgbBufferedImageAccess.java @@ -1,4 +1,4 @@ -package qupath.ext.imglib2.bufferedimageaccesses; +package qupath.ext.imglib2.accesses; import net.imglib2.img.basictypeaccess.IntAccess; import net.imglib2.type.numeric.ARGBType; diff --git a/src/test/java/qupath/ext/imglib2/bufferedimageaccesses/TestByteRasterAccess.java b/src/test/java/qupath/ext/imglib2/accesses/TestByteRasterAccess.java similarity index 96% rename from src/test/java/qupath/ext/imglib2/bufferedimageaccesses/TestByteRasterAccess.java rename to src/test/java/qupath/ext/imglib2/accesses/TestByteRasterAccess.java index b82085c..ab8fcbf 100644 --- a/src/test/java/qupath/ext/imglib2/bufferedimageaccesses/TestByteRasterAccess.java +++ b/src/test/java/qupath/ext/imglib2/accesses/TestByteRasterAccess.java @@ -1,4 +1,4 @@ -package qupath.ext.imglib2.bufferedimageaccesses; +package qupath.ext.imglib2.accesses; import net.imglib2.img.basictypeaccess.ByteAccess; import org.junit.jupiter.api.Assertions; diff --git a/src/test/java/qupath/ext/imglib2/bufferedimageaccesses/TestDoubleRasterAccess.java b/src/test/java/qupath/ext/imglib2/accesses/TestDoubleRasterAccess.java similarity index 96% rename from src/test/java/qupath/ext/imglib2/bufferedimageaccesses/TestDoubleRasterAccess.java rename to src/test/java/qupath/ext/imglib2/accesses/TestDoubleRasterAccess.java index 501013e..7f97c1a 100644 --- a/src/test/java/qupath/ext/imglib2/bufferedimageaccesses/TestDoubleRasterAccess.java +++ b/src/test/java/qupath/ext/imglib2/accesses/TestDoubleRasterAccess.java @@ -1,4 +1,4 @@ -package qupath.ext.imglib2.bufferedimageaccesses; +package qupath.ext.imglib2.accesses; import net.imglib2.img.basictypeaccess.DoubleAccess; import org.junit.jupiter.api.Assertions; diff --git a/src/test/java/qupath/ext/imglib2/bufferedimageaccesses/TestFloatRasterAccess.java b/src/test/java/qupath/ext/imglib2/accesses/TestFloatRasterAccess.java similarity index 96% rename from src/test/java/qupath/ext/imglib2/bufferedimageaccesses/TestFloatRasterAccess.java rename to src/test/java/qupath/ext/imglib2/accesses/TestFloatRasterAccess.java index 2b8106c..2cb7818 100644 --- a/src/test/java/qupath/ext/imglib2/bufferedimageaccesses/TestFloatRasterAccess.java +++ b/src/test/java/qupath/ext/imglib2/accesses/TestFloatRasterAccess.java @@ -1,4 +1,4 @@ -package qupath.ext.imglib2.bufferedimageaccesses; +package qupath.ext.imglib2.accesses; import net.imglib2.img.basictypeaccess.FloatAccess; import org.junit.jupiter.api.Assertions; diff --git a/src/test/java/qupath/ext/imglib2/bufferedimageaccesses/TestIntRasterAccess.java b/src/test/java/qupath/ext/imglib2/accesses/TestIntRasterAccess.java similarity index 96% rename from src/test/java/qupath/ext/imglib2/bufferedimageaccesses/TestIntRasterAccess.java rename to src/test/java/qupath/ext/imglib2/accesses/TestIntRasterAccess.java index 78ec6c3..4abb06b 100644 --- a/src/test/java/qupath/ext/imglib2/bufferedimageaccesses/TestIntRasterAccess.java +++ b/src/test/java/qupath/ext/imglib2/accesses/TestIntRasterAccess.java @@ -1,4 +1,4 @@ -package qupath.ext.imglib2.bufferedimageaccesses; +package qupath.ext.imglib2.accesses; import net.imglib2.img.basictypeaccess.IntAccess; import org.junit.jupiter.api.Assertions; diff --git a/src/test/java/qupath/ext/imglib2/bufferedimageaccesses/TestShortRasterAccess.java b/src/test/java/qupath/ext/imglib2/accesses/TestShortRasterAccess.java similarity index 97% rename from src/test/java/qupath/ext/imglib2/bufferedimageaccesses/TestShortRasterAccess.java rename to src/test/java/qupath/ext/imglib2/accesses/TestShortRasterAccess.java index 0b31476..29355ca 100644 --- a/src/test/java/qupath/ext/imglib2/bufferedimageaccesses/TestShortRasterAccess.java +++ b/src/test/java/qupath/ext/imglib2/accesses/TestShortRasterAccess.java @@ -1,4 +1,4 @@ -package qupath.ext.imglib2.bufferedimageaccesses; +package qupath.ext.imglib2.accesses; import net.imglib2.img.basictypeaccess.ShortAccess; import org.junit.jupiter.api.Assertions; From e702a424f82510c9a7b251c3a55a6e5c1fdb9d45 Mon Sep 17 00:00:00 2001 From: lleplat Date: Thu, 20 Nov 2025 16:12:49 +0000 Subject: [PATCH 3/6] Rename image creation functions --- .../java/qupath/ext/imglib2/ImgBuilder.java | 18 +++++----- .../qupath/ext/imglib2/TestImgBuilder.java | 36 +++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/main/java/qupath/ext/imglib2/ImgBuilder.java b/src/main/java/qupath/ext/imglib2/ImgBuilder.java index 900b02c..e9ba613 100644 --- a/src/main/java/qupath/ext/imglib2/ImgBuilder.java +++ b/src/main/java/qupath/ext/imglib2/ImgBuilder.java @@ -39,7 +39,7 @@ /** * A class to create {@link Img} or {@link RandomAccessibleInterval} from an {@link ImageServer}. *

- * Use a {@link #create(ImageServer)} or {@link #create(ImageServer, NativeType)} to create an instance of this class. + * Use a {@link #createBuilder(ImageServer)} or {@link #createBuilder(ImageServer, NativeType)} to create an instance of this class. *

* This class is thread-safe. * @@ -94,13 +94,13 @@ private ImgBuilder(ImageServer server, T type, Function * The type of the output image is not checked, which might lead to problems later when accessing pixel values of the - * returned accessibles of this class. It is recommended to use {@link #create(ImageServer, NativeType)} instead. + * returned accessibles of this class. It is recommended to use {@link #createBuilder(ImageServer, NativeType)} instead. * * @param server the input image * @return a builder to create an instance of this class * @throws IllegalArgumentException if the provided image has less than one channel */ - public static ImgBuilder create(ImageServer server) { + public static ImgBuilder createBuilder(ImageServer server) { if (server.isRGB()) { return new ImgBuilder<>(server, new ARGBType(), ArgbBufferedImageAccess::new); } else { @@ -201,7 +201,7 @@ private ImgBuilder(ImageServer server, T type, Function & NumericType> ImgBuilder create(ImageServer server, T type) { + public static & NumericType> ImgBuilder createBuilder(ImageServer server, T type) { checkType(server, type); if (server.isRGB()) { @@ -245,7 +245,7 @@ public ImgBuilder cellCache(CellCache cellCache) { */ public List> buildForAllLevels() { return IntStream.range(0, server.getMetadata().nLevels()) - .mapToObj(this::createForLevel) + .mapToObj(this::buildForLevel) .toList(); } @@ -264,7 +264,7 @@ public List> buildForAllLevels() { * @return an {@link Img} corresponding to the provided level of the input image * @throws IllegalArgumentException if the provided level does not match with a level of the input image */ - public Img createForLevel(int level) { + public Img buildForLevel(int level) { if (level < 0 || level >= server.getMetadata().nLevels()) { throw new IllegalArgumentException(String.format( "The provided level %d is not within 0 and %d", @@ -316,7 +316,7 @@ public Img createForLevel(int level) { * @return a {@link RandomAccessibleInterval} corresponding to the input image with the provided downsample applied * @throws IllegalArgumentException if the provided downsample is not greater than 0 */ - public RandomAccessibleInterval createForDownsample(double downsample) { + public RandomAccessibleInterval buildForDownsample(double downsample) { if (downsample <= 0) { throw new IllegalArgumentException(String.format("The provided downsample %f is not greater than 0", downsample)); } @@ -324,9 +324,9 @@ public RandomAccessibleInterval createForDownsample(double downsample) { int level = ServerTools.getPreferredResolutionLevel(server, downsample); if (server.getMetadata().getChannelType() == ImageServerMetadata.ChannelType.CLASSIFICATION) { - return AccessibleScaler.scaleWithNearestNeighborInterpolation(createForLevel(level), server.getDownsampleForResolution(level) / downsample); + return AccessibleScaler.scaleWithNearestNeighborInterpolation(buildForLevel(level), server.getDownsampleForResolution(level) / downsample); } else { - return AccessibleScaler.scaleWithLinearInterpolation(createForLevel(level), server.getDownsampleForResolution(level) / downsample); + return AccessibleScaler.scaleWithLinearInterpolation(buildForLevel(level), server.getDownsampleForResolution(level) / downsample); } } diff --git a/src/test/java/qupath/ext/imglib2/TestImgBuilder.java b/src/test/java/qupath/ext/imglib2/TestImgBuilder.java index 38cce46..b524604 100644 --- a/src/test/java/qupath/ext/imglib2/TestImgBuilder.java +++ b/src/test/java/qupath/ext/imglib2/TestImgBuilder.java @@ -44,7 +44,7 @@ void Check_Rgb_Server() throws Exception { PixelType pixelType = PixelType.UINT8; ImageServer imageServer = new GenericImageServer(isRgb, pixelType); - Img img = ImgBuilder.create(imageServer, new ARGBType()).createForLevel(0); + Img img = ImgBuilder.createBuilder(imageServer, new ARGBType()).buildForLevel(0); Utils.assertArgbRandomAccessibleEquals(img, (x, y, channel, z, t) -> ARGBType.rgba(255, 0, 0, 0), 1); @@ -57,7 +57,7 @@ void Check_Uint8_Server() throws Exception { PixelType pixelType = PixelType.UINT8; ImageServer imageServer = new GenericImageServer(isRgb, pixelType); - Img img = ImgBuilder.create(imageServer, new UnsignedByteType()).createForLevel(0); + Img img = ImgBuilder.createBuilder(imageServer, new UnsignedByteType()).buildForLevel(0); Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); @@ -70,7 +70,7 @@ void Check_Int8_Server() throws Exception { PixelType pixelType = PixelType.INT8; ImageServer imageServer = new GenericImageServer(isRgb, pixelType); - Img img = ImgBuilder.create(imageServer, new ByteType()).createForLevel(0); + Img img = ImgBuilder.createBuilder(imageServer, new ByteType()).buildForLevel(0); Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); @@ -83,7 +83,7 @@ void Check_Uint16_Server() throws Exception { PixelType pixelType = PixelType.UINT16; ImageServer imageServer = new GenericImageServer(isRgb, pixelType); - Img img = ImgBuilder.create(imageServer, new UnsignedShortType()).createForLevel(0); + Img img = ImgBuilder.createBuilder(imageServer, new UnsignedShortType()).buildForLevel(0); Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); @@ -96,7 +96,7 @@ void Check_Int16_Server() throws Exception { PixelType pixelType = PixelType.INT16; ImageServer imageServer = new GenericImageServer(isRgb, pixelType); - Img img = ImgBuilder.create(imageServer, new ShortType()).createForLevel(0); + Img img = ImgBuilder.createBuilder(imageServer, new ShortType()).buildForLevel(0); Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); @@ -109,7 +109,7 @@ void Check_Uint32_Server() throws Exception { PixelType pixelType = PixelType.UINT32; ImageServer imageServer = new GenericImageServer(isRgb, pixelType); - Img img = ImgBuilder.create(imageServer, new UnsignedIntType()).createForLevel(0); + Img img = ImgBuilder.createBuilder(imageServer, new UnsignedIntType()).buildForLevel(0); Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); @@ -122,7 +122,7 @@ void Check_Int32_Server() throws Exception { PixelType pixelType = PixelType.INT32; ImageServer imageServer = new GenericImageServer(isRgb, pixelType); - Img img = ImgBuilder.create(imageServer, new IntType()).createForLevel(0); + Img img = ImgBuilder.createBuilder(imageServer, new IntType()).buildForLevel(0); Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); @@ -135,7 +135,7 @@ void Check_Float32_Server() throws Exception { PixelType pixelType = PixelType.FLOAT32; ImageServer imageServer = new GenericImageServer(isRgb, pixelType); - Img img = ImgBuilder.create(imageServer, new FloatType()).createForLevel(0); + Img img = ImgBuilder.createBuilder(imageServer, new FloatType()).buildForLevel(0); Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); @@ -148,7 +148,7 @@ void Check_Float64_Server() throws Exception { PixelType pixelType = PixelType.FLOAT64; ImageServer imageServer = new GenericImageServer(isRgb, pixelType); - Img img = ImgBuilder.create(imageServer, new DoubleType()).createForLevel(0); + Img img = ImgBuilder.createBuilder(imageServer, new DoubleType()).buildForLevel(0); Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); @@ -159,7 +159,7 @@ void Check_Float64_Server() throws Exception { void Check_X_Dimension_Size() throws Exception { ImageServer imageServer = new ComplexDoubleImageServer(); int expectedSize = imageServer.getWidth(); - Img img = ImgBuilder.create(imageServer).createForLevel(0); + Img img = ImgBuilder.createBuilder(imageServer).buildForLevel(0); int dimensionIndex = ImgBuilder.AXIS_X; Assertions.assertEquals(expectedSize, img.dimension(dimensionIndex)); @@ -171,7 +171,7 @@ void Check_X_Dimension_Size() throws Exception { void Check_Y_Dimension_Size() throws Exception { ImageServer imageServer = new ComplexDoubleImageServer(); int expectedSize = imageServer.getHeight(); - Img img = ImgBuilder.create(imageServer).createForLevel(0); + Img img = ImgBuilder.createBuilder(imageServer).buildForLevel(0); int dimensionIndex = ImgBuilder.AXIS_Y; Assertions.assertEquals(expectedSize, img.dimension(dimensionIndex)); @@ -183,7 +183,7 @@ void Check_Y_Dimension_Size() throws Exception { void Check_Channel_Dimension_Size() throws Exception { ImageServer imageServer = new ComplexDoubleImageServer(); int expectedSize = imageServer.nChannels(); - Img img = ImgBuilder.create(imageServer).createForLevel(0); + Img img = ImgBuilder.createBuilder(imageServer).buildForLevel(0); int dimensionIndex = ImgBuilder.AXIS_CHANNEL; Assertions.assertEquals(expectedSize, img.dimension(dimensionIndex)); @@ -195,7 +195,7 @@ void Check_Channel_Dimension_Size() throws Exception { void Check_Z_Dimension_Size() throws Exception { ImageServer imageServer = new ComplexDoubleImageServer(); int expectedSize = imageServer.getMetadata().getSizeZ(); - Img img = ImgBuilder.create(imageServer).createForLevel(0); + Img img = ImgBuilder.createBuilder(imageServer).buildForLevel(0); int dimensionIndex = ImgBuilder.AXIS_Z; Assertions.assertEquals(expectedSize, img.dimension(dimensionIndex)); @@ -207,7 +207,7 @@ void Check_Z_Dimension_Size() throws Exception { void Check_Time_Dimension_Size() throws Exception { ImageServer imageServer = new ComplexDoubleImageServer(); int expectedSize = imageServer.getMetadata().getSizeT(); - Img img = ImgBuilder.create(imageServer).createForLevel(0); + Img img = ImgBuilder.createBuilder(imageServer).buildForLevel(0); int dimensionIndex = ImgBuilder.AXIS_TIME; Assertions.assertEquals(expectedSize, img.dimension(dimensionIndex)); @@ -221,7 +221,7 @@ void Check_Pixels_Of_Level_0() throws Exception { ImageServer imageServer = new ComplexDoubleImageServer(); double downsample = imageServer.getDownsampleForResolution(level); - Img img = ImgBuilder.create(imageServer, new DoubleType()).createForLevel(level); + Img img = ImgBuilder.createBuilder(imageServer, new DoubleType()).buildForLevel(level); Utils.assertRandomAccessibleEquals(img, ComplexDoubleImageServer::getPixel, downsample); @@ -234,7 +234,7 @@ void Check_Pixels_Of_Level_1() throws Exception { ImageServer imageServer = new ComplexDoubleImageServer(); double downsample = imageServer.getDownsampleForResolution(level); - Img img = ImgBuilder.create(imageServer, new DoubleType()).createForLevel(level); + Img img = ImgBuilder.createBuilder(imageServer, new DoubleType()).buildForLevel(level); Utils.assertRandomAccessibleEquals(img, ComplexDoubleImageServer::getPixel, downsample); @@ -247,7 +247,7 @@ void Check_Pixels_Of_Downsample_1() throws Exception { ImageServer imageServer = new ComplexDoubleImageServer(); double downsample = imageServer.getDownsampleForResolution(level); - RandomAccessibleInterval img = ImgBuilder.create(imageServer, new DoubleType()).createForDownsample(downsample); + RandomAccessibleInterval img = ImgBuilder.createBuilder(imageServer, new DoubleType()).buildForDownsample(downsample); Utils.assertRandomAccessibleEquals(img, ComplexDoubleImageServer::getPixel, downsample); @@ -260,7 +260,7 @@ void Check_Pixels_Of_Downsample_4() throws Exception { ImageServer imageServer = new ComplexDoubleImageServer(); double downsample = imageServer.getDownsampleForResolution(level); - RandomAccessibleInterval img = ImgBuilder.create(imageServer, new DoubleType()).createForDownsample(downsample); + RandomAccessibleInterval img = ImgBuilder.createBuilder(imageServer, new DoubleType()).buildForDownsample(downsample); Utils.assertRandomAccessibleEquals(img, ComplexDoubleImageServer::getPixel, downsample); From aaecc3c19e48e391b78b2f366c7a825328e6891e Mon Sep 17 00:00:00 2001 From: lleplat Date: Thu, 20 Nov 2025 16:16:22 +0000 Subject: [PATCH 4/6] Add buildForDownsamples() function --- .../java/qupath/ext/imglib2/ImgBuilder.java | 49 ++++++++++++++++--- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/src/main/java/qupath/ext/imglib2/ImgBuilder.java b/src/main/java/qupath/ext/imglib2/ImgBuilder.java index e9ba613..426ea54 100644 --- a/src/main/java/qupath/ext/imglib2/ImgBuilder.java +++ b/src/main/java/qupath/ext/imglib2/ImgBuilder.java @@ -198,8 +198,8 @@ private ImgBuilder(ImageServer server, T type, Function the type corresponding to the provided image - * @throws IllegalArgumentException if the provided type is not compatible with the input image (see above), or if the provided image - * has less than one channel + * @throws IllegalArgumentException if the provided type is not compatible with the input image (see above), or if + * the provided image has less than one channel */ public static & NumericType> ImgBuilder createBuilder(ImageServer server, T type) { checkType(server, type); @@ -218,8 +218,9 @@ private ImgBuilder(ImageServer server, T type, Function buildForLevel(int level) { ); } + /** + * Create a list of {@link RandomAccessibleInterval} from the input image and the provided downsamples. + *

+ * The {@link RandomAccessibleInterval} returned by this class are immutable. This means that any attempt to write + * data to them will result in an {@link UnsupportedOperationException}. + *

+ * See {@link #AXIS_X}, {@link #AXIS_Y}, {@link #AXIS_CHANNEL}, {@link #AXIS_Z}, and {@link #AXIS_TIME} to get the physical + * interpretation of the dimensions of the returned {@link RandomAccessibleInterval}. + *

+ * Values of the returned images are lazily fetched. + *

+ * If the input image has to be scaled and its {@link ImageServerMetadata#getChannelType() channel type} is + * {@link ImageServerMetadata.ChannelType#CLASSIFICATION}, then the nearest neighbor interpolation is used. + * Otherwise, the linear interpolation is used. + * + * @param downsamples the downsamples to apply to the input image. Must be greater than 0 + * @return a list of {@link RandomAccessibleInterval} corresponding to the input image with the provided downsamples + * applied. The ith returned {@link RandomAccessibleInterval} corresponds to the ith provided downsample + * @throws IllegalArgumentException if one of the provided downsamples is not greater than 0 + */ + public List> buildForDownsamples(List downsamples) { + return downsamples.stream() + .map(this::buildForDownsample) + .toList(); + } + /** * Create a {@link RandomAccessibleInterval} from the input image and the provided downsample. *

- * The {@link RandomAccessibleInterval} returned by this class is immutable. This means that any attempt to write data to it will result in an - * {@link UnsupportedOperationException}. + * The {@link RandomAccessibleInterval} returned by this class is immutable. This means that any attempt to write + * data to it will result in an {@link UnsupportedOperationException}. *

* See {@link #AXIS_X}, {@link #AXIS_Y}, {@link #AXIS_CHANNEL}, {@link #AXIS_Z}, and {@link #AXIS_TIME} to get the physical * interpretation of the dimensions of the returned {@link RandomAccessibleInterval}. @@ -324,9 +351,15 @@ public RandomAccessibleInterval buildForDownsample(double downsample) { int level = ServerTools.getPreferredResolutionLevel(server, downsample); if (server.getMetadata().getChannelType() == ImageServerMetadata.ChannelType.CLASSIFICATION) { - return AccessibleScaler.scaleWithNearestNeighborInterpolation(buildForLevel(level), server.getDownsampleForResolution(level) / downsample); + return AccessibleScaler.scaleWithNearestNeighborInterpolation( + buildForLevel(level), + server.getDownsampleForResolution(level) / downsample + ); } else { - return AccessibleScaler.scaleWithLinearInterpolation(buildForLevel(level), server.getDownsampleForResolution(level) / downsample); + return AccessibleScaler.scaleWithLinearInterpolation( + buildForLevel(level), + server.getDownsampleForResolution(level) / downsample + ); } } From a2791cea06570bd6c72c12fceb6a2ae4c851209f Mon Sep 17 00:00:00 2001 From: lleplat Date: Thu, 20 Nov 2025 17:08:10 +0000 Subject: [PATCH 5/6] Return RandomAccessibleInterval instead of Img --- .../java/qupath/ext/imglib2/ImgBuilder.java | 6 ++-- .../qupath/ext/imglib2/TestImgBuilder.java | 33 +++++++++---------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/main/java/qupath/ext/imglib2/ImgBuilder.java b/src/main/java/qupath/ext/imglib2/ImgBuilder.java index 426ea54..93b6eaa 100644 --- a/src/main/java/qupath/ext/imglib2/ImgBuilder.java +++ b/src/main/java/qupath/ext/imglib2/ImgBuilder.java @@ -244,7 +244,7 @@ public ImgBuilder cellCache(CellCache cellCache) { * * @return a list of {@link RandomAccessibleInterval} corresponding to each level of the input image */ - public List> buildForAllLevels() { + public List> buildForAllLevels() { return IntStream.range(0, server.getMetadata().nLevels()) .mapToObj(this::buildForLevel) .toList(); @@ -265,7 +265,7 @@ public List> buildForAllLevels() { * @return an {@link Img} corresponding to the provided level of the input image * @throws IllegalArgumentException if the provided level does not match with a level of the input image */ - public Img buildForLevel(int level) { + public RandomAccessibleInterval buildForLevel(int level) { if (level < 0 || level >= server.getMetadata().nLevels()) { throw new IllegalArgumentException(String.format( "The provided level %d is not within 0 and %d", @@ -318,7 +318,7 @@ public Img buildForLevel(int level) { * applied. The ith returned {@link RandomAccessibleInterval} corresponds to the ith provided downsample * @throws IllegalArgumentException if one of the provided downsamples is not greater than 0 */ - public List> buildForDownsamples(List downsamples) { + public List> buildForDownsamples(List downsamples) { return downsamples.stream() .map(this::buildForDownsample) .toList(); diff --git a/src/test/java/qupath/ext/imglib2/TestImgBuilder.java b/src/test/java/qupath/ext/imglib2/TestImgBuilder.java index b524604..5a489c1 100644 --- a/src/test/java/qupath/ext/imglib2/TestImgBuilder.java +++ b/src/test/java/qupath/ext/imglib2/TestImgBuilder.java @@ -1,7 +1,6 @@ package qupath.ext.imglib2; import net.imglib2.RandomAccessibleInterval; -import net.imglib2.img.Img; import net.imglib2.type.numeric.ARGBType; import net.imglib2.type.numeric.integer.ByteType; import net.imglib2.type.numeric.integer.IntType; @@ -44,7 +43,7 @@ void Check_Rgb_Server() throws Exception { PixelType pixelType = PixelType.UINT8; ImageServer imageServer = new GenericImageServer(isRgb, pixelType); - Img img = ImgBuilder.createBuilder(imageServer, new ARGBType()).buildForLevel(0); + RandomAccessibleInterval img = ImgBuilder.createBuilder(imageServer, new ARGBType()).buildForLevel(0); Utils.assertArgbRandomAccessibleEquals(img, (x, y, channel, z, t) -> ARGBType.rgba(255, 0, 0, 0), 1); @@ -57,7 +56,7 @@ void Check_Uint8_Server() throws Exception { PixelType pixelType = PixelType.UINT8; ImageServer imageServer = new GenericImageServer(isRgb, pixelType); - Img img = ImgBuilder.createBuilder(imageServer, new UnsignedByteType()).buildForLevel(0); + RandomAccessibleInterval img = ImgBuilder.createBuilder(imageServer, new UnsignedByteType()).buildForLevel(0); Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); @@ -70,7 +69,7 @@ void Check_Int8_Server() throws Exception { PixelType pixelType = PixelType.INT8; ImageServer imageServer = new GenericImageServer(isRgb, pixelType); - Img img = ImgBuilder.createBuilder(imageServer, new ByteType()).buildForLevel(0); + RandomAccessibleInterval img = ImgBuilder.createBuilder(imageServer, new ByteType()).buildForLevel(0); Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); @@ -83,7 +82,7 @@ void Check_Uint16_Server() throws Exception { PixelType pixelType = PixelType.UINT16; ImageServer imageServer = new GenericImageServer(isRgb, pixelType); - Img img = ImgBuilder.createBuilder(imageServer, new UnsignedShortType()).buildForLevel(0); + RandomAccessibleInterval img = ImgBuilder.createBuilder(imageServer, new UnsignedShortType()).buildForLevel(0); Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); @@ -96,7 +95,7 @@ void Check_Int16_Server() throws Exception { PixelType pixelType = PixelType.INT16; ImageServer imageServer = new GenericImageServer(isRgb, pixelType); - Img img = ImgBuilder.createBuilder(imageServer, new ShortType()).buildForLevel(0); + RandomAccessibleInterval img = ImgBuilder.createBuilder(imageServer, new ShortType()).buildForLevel(0); Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); @@ -109,7 +108,7 @@ void Check_Uint32_Server() throws Exception { PixelType pixelType = PixelType.UINT32; ImageServer imageServer = new GenericImageServer(isRgb, pixelType); - Img img = ImgBuilder.createBuilder(imageServer, new UnsignedIntType()).buildForLevel(0); + RandomAccessibleInterval img = ImgBuilder.createBuilder(imageServer, new UnsignedIntType()).buildForLevel(0); Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); @@ -122,7 +121,7 @@ void Check_Int32_Server() throws Exception { PixelType pixelType = PixelType.INT32; ImageServer imageServer = new GenericImageServer(isRgb, pixelType); - Img img = ImgBuilder.createBuilder(imageServer, new IntType()).buildForLevel(0); + RandomAccessibleInterval img = ImgBuilder.createBuilder(imageServer, new IntType()).buildForLevel(0); Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); @@ -135,7 +134,7 @@ void Check_Float32_Server() throws Exception { PixelType pixelType = PixelType.FLOAT32; ImageServer imageServer = new GenericImageServer(isRgb, pixelType); - Img img = ImgBuilder.createBuilder(imageServer, new FloatType()).buildForLevel(0); + RandomAccessibleInterval img = ImgBuilder.createBuilder(imageServer, new FloatType()).buildForLevel(0); Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); @@ -148,7 +147,7 @@ void Check_Float64_Server() throws Exception { PixelType pixelType = PixelType.FLOAT64; ImageServer imageServer = new GenericImageServer(isRgb, pixelType); - Img img = ImgBuilder.createBuilder(imageServer, new DoubleType()).buildForLevel(0); + RandomAccessibleInterval img = ImgBuilder.createBuilder(imageServer, new DoubleType()).buildForLevel(0); Utils.assertRandomAccessibleEquals(img, (x, y, channel, z, t) -> 1, 1); @@ -159,7 +158,7 @@ void Check_Float64_Server() throws Exception { void Check_X_Dimension_Size() throws Exception { ImageServer imageServer = new ComplexDoubleImageServer(); int expectedSize = imageServer.getWidth(); - Img img = ImgBuilder.createBuilder(imageServer).buildForLevel(0); + RandomAccessibleInterval img = ImgBuilder.createBuilder(imageServer).buildForLevel(0); int dimensionIndex = ImgBuilder.AXIS_X; Assertions.assertEquals(expectedSize, img.dimension(dimensionIndex)); @@ -171,7 +170,7 @@ void Check_X_Dimension_Size() throws Exception { void Check_Y_Dimension_Size() throws Exception { ImageServer imageServer = new ComplexDoubleImageServer(); int expectedSize = imageServer.getHeight(); - Img img = ImgBuilder.createBuilder(imageServer).buildForLevel(0); + RandomAccessibleInterval img = ImgBuilder.createBuilder(imageServer).buildForLevel(0); int dimensionIndex = ImgBuilder.AXIS_Y; Assertions.assertEquals(expectedSize, img.dimension(dimensionIndex)); @@ -183,7 +182,7 @@ void Check_Y_Dimension_Size() throws Exception { void Check_Channel_Dimension_Size() throws Exception { ImageServer imageServer = new ComplexDoubleImageServer(); int expectedSize = imageServer.nChannels(); - Img img = ImgBuilder.createBuilder(imageServer).buildForLevel(0); + RandomAccessibleInterval img = ImgBuilder.createBuilder(imageServer).buildForLevel(0); int dimensionIndex = ImgBuilder.AXIS_CHANNEL; Assertions.assertEquals(expectedSize, img.dimension(dimensionIndex)); @@ -195,7 +194,7 @@ void Check_Channel_Dimension_Size() throws Exception { void Check_Z_Dimension_Size() throws Exception { ImageServer imageServer = new ComplexDoubleImageServer(); int expectedSize = imageServer.getMetadata().getSizeZ(); - Img img = ImgBuilder.createBuilder(imageServer).buildForLevel(0); + RandomAccessibleInterval img = ImgBuilder.createBuilder(imageServer).buildForLevel(0); int dimensionIndex = ImgBuilder.AXIS_Z; Assertions.assertEquals(expectedSize, img.dimension(dimensionIndex)); @@ -207,7 +206,7 @@ void Check_Z_Dimension_Size() throws Exception { void Check_Time_Dimension_Size() throws Exception { ImageServer imageServer = new ComplexDoubleImageServer(); int expectedSize = imageServer.getMetadata().getSizeT(); - Img img = ImgBuilder.createBuilder(imageServer).buildForLevel(0); + RandomAccessibleInterval img = ImgBuilder.createBuilder(imageServer).buildForLevel(0); int dimensionIndex = ImgBuilder.AXIS_TIME; Assertions.assertEquals(expectedSize, img.dimension(dimensionIndex)); @@ -221,7 +220,7 @@ void Check_Pixels_Of_Level_0() throws Exception { ImageServer imageServer = new ComplexDoubleImageServer(); double downsample = imageServer.getDownsampleForResolution(level); - Img img = ImgBuilder.createBuilder(imageServer, new DoubleType()).buildForLevel(level); + RandomAccessibleInterval img = ImgBuilder.createBuilder(imageServer, new DoubleType()).buildForLevel(level); Utils.assertRandomAccessibleEquals(img, ComplexDoubleImageServer::getPixel, downsample); @@ -234,7 +233,7 @@ void Check_Pixels_Of_Level_1() throws Exception { ImageServer imageServer = new ComplexDoubleImageServer(); double downsample = imageServer.getDownsampleForResolution(level); - Img img = ImgBuilder.createBuilder(imageServer, new DoubleType()).buildForLevel(level); + RandomAccessibleInterval img = ImgBuilder.createBuilder(imageServer, new DoubleType()).buildForLevel(level); Utils.assertRandomAccessibleEquals(img, ComplexDoubleImageServer::getPixel, downsample); From 47e180c187bc378f4281cce8a7c0621881df5953 Mon Sep 17 00:00:00 2001 From: lleplat Date: Fri, 21 Nov 2025 14:16:07 +0000 Subject: [PATCH 6/6] Remove ImgCreator --- README.md | 14 +- .../java/qupath/ext/imglib2/ImgCreator.java | 406 --------------- .../ext/imglib2/ImgLib2ImageServer.java | 132 ++--- .../java/qupath/ext/imglib2/package-info.java | 2 +- .../qupath/ext/imglib2/TestImgCreator.java | 490 ------------------ src/test/java/qupath/ext/imglib2/Utils.java | 40 +- 6 files changed, 95 insertions(+), 989 deletions(-) delete mode 100644 src/main/java/qupath/ext/imglib2/ImgCreator.java delete mode 100644 src/test/java/qupath/ext/imglib2/TestImgCreator.java diff --git a/README.md b/README.md index 7c177cd..dea5702 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A Java library to link QuPath with ImgLib2. Here is a sample script that shows how to use the library from QuPath: ```groovy -import qupath.ext.imglib2.ImgCreator +import qupath.ext.imglib2.ImgBuilder import qupath.ext.imglib2.ImgLib2ImageServer import net.imglib2.type.numeric.ARGBType @@ -16,13 +16,13 @@ var server = getCurrentServer() // Create Img from level var level = 0 -var img = ImgCreator.builder(server).build().createForLevel(level) +var img = ImgBuilder.createBuilder(server).build().createForLevel(level) println img // Create RandomAccessibleInterval from downsample var downsample = 1 -var randomAccessible = ImgCreator.builder(server).build().createForDownsample(downsample) +var randomAccessible = ImgBuilder.createBuilder(server).build().createForDownsample(downsample) println randomAccessible @@ -37,7 +37,7 @@ var type = new ARGBType() // only valid if server represents a RGB image. Othe // net.imglib2.type.numeric.integer.IntType for INT32 images // net.imglib2.type.numeric.real.FloatType for FLOAT32 images // net.imglib2.type.numeric.real.DoubleType for FLOAT64 images -var safeImg = ImgCreator.builder(server, type).build().createForLevel(level) +var safeImg = ImgBuilder.createBuilder(server, type).build().createForLevel(level) println safeImg @@ -45,9 +45,9 @@ println safeImg // For example, to read the pixel located at [x:1, y:2; c:0; z:0; t:0]: var randomAccess = randomAccessible.randomAccess() -var position = new long[ImgCreator.NUMBER_OF_AXES] -position[ImgCreator.AXIS_X] = 1 -position[ImgCreator.AXIS_Y] = 2 +var position = new long[ImgBuilder.NUMBER_OF_AXES] +position[ImgBuilder.AXIS_X] = 1 +position[ImgBuilder.AXIS_Y] = 2 var pixel = randomAccess.setPositionAndGet(position) println pixel diff --git a/src/main/java/qupath/ext/imglib2/ImgCreator.java b/src/main/java/qupath/ext/imglib2/ImgCreator.java deleted file mode 100644 index 066d396..0000000 --- a/src/main/java/qupath/ext/imglib2/ImgCreator.java +++ /dev/null @@ -1,406 +0,0 @@ -package qupath.ext.imglib2; - -import net.imglib2.RandomAccessibleInterval; -import net.imglib2.img.Img; -import net.imglib2.img.cell.Cell; -import net.imglib2.img.cell.CellGrid; -import net.imglib2.img.cell.LazyCellImg; -import net.imglib2.type.NativeType; -import net.imglib2.type.numeric.ARGBType; -import net.imglib2.type.numeric.NumericType; -import net.imglib2.type.numeric.integer.ByteType; -import net.imglib2.type.numeric.integer.IntType; -import net.imglib2.type.numeric.integer.ShortType; -import net.imglib2.type.numeric.integer.UnsignedByteType; -import net.imglib2.type.numeric.integer.UnsignedIntType; -import net.imglib2.type.numeric.integer.UnsignedShortType; -import net.imglib2.type.numeric.real.DoubleType; -import net.imglib2.type.numeric.real.FloatType; -import qupath.ext.imglib2.accesses.ArgbBufferedImageAccess; -import qupath.ext.imglib2.accesses.ByteRasterAccess; -import qupath.ext.imglib2.accesses.DoubleRasterAccess; -import qupath.ext.imglib2.accesses.FloatRasterAccess; -import qupath.ext.imglib2.accesses.IntRasterAccess; -import qupath.ext.imglib2.accesses.ShortRasterAccess; -import qupath.lib.images.servers.ImageServer; -import qupath.lib.images.servers.ImageServerMetadata; -import qupath.lib.images.servers.ServerTools; -import qupath.lib.images.servers.TileRequest; -import qupath.lib.images.servers.PixelType; - -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.function.Function; - -/** - * A class to create {@link Img} or {@link RandomAccessibleInterval} from an {@link ImageServer}. - *

- * Use a {@link #builder(ImageServer)} or {@link #builder(ImageServer, NativeType)} to create an instance of this class. - *

- * This class is thread-safe. - * - * @param the type of the returned accessibles - * @param the type contained in the input image - */ -public class ImgCreator & NumericType, A extends SizableDataAccess> { - - /** - * The index of the X axis of accessibles returned by functions of this class - */ - public static final int AXIS_X = 0; - /** - * The index of the Y axis of accessibles returned by functions of this class - */ - public static final int AXIS_Y = 1; - /** - * The index of the channel axis of accessibles returned by functions of this class - */ - public static final int AXIS_CHANNEL = 2; - /** - * The index of the Z axis of accessibles returned by functions of this class - */ - public static final int AXIS_Z = 3; - /** - * The index of the time axis of accessibles returned by functions of this class - */ - public static final int AXIS_TIME = 4; - /** - * The number of axes of accessibles returned by functions of this class - */ - public static final int NUMBER_OF_AXES = 5; - private final ImageServer server; - private final T type; - private final CellCache cellCache; - private final Function cellCreator; - private final int numberOfChannels; - - private ImgCreator(Builder builder, Function cellCreator) { - this.server = builder.server; - this.type = builder.type; - this.cellCache = builder.cellCache; - this.cellCreator = cellCreator; - this.numberOfChannels = server.isRGB() ? 1 : server.nChannels(); - } - - /** - * Create a builder from an {@link ImageServer}. This doesn't create any accessibles yet. - *

- * The type of the output image is not checked, which might lead to problems later when accessing pixel values of the - * returned accessibles of this class. It is recommended to use {@link #builder(ImageServer, NativeType)} instead. - * - * @param server the input image - * @return a builder to create an instance of this class - * @throws IllegalArgumentException if the provided image has less than one channel - * @param the type of the output image - */ - public static & NumericType> Builder builder(ImageServer server) { - // Despite the potential warning, T is necessary, otherwise a cannot infer type arguments error occurs - return new Builder(server, getTypeOfServer(server)); - } - - /** - * Create a builder from an {@link ImageServer}. This doesn't create any accessibles yet. - *

- * The provided type must be compatible with the input image: - *

- * - * @param server the input image - * @param type the expected type of the output image - * @return a builder to create an instance of this class - * @param the type corresponding to the provided image - * @throws IllegalArgumentException if the provided type is not compatible with the input image (see above), or if the provided image - * has less than one channel - */ - public static & NumericType> Builder builder(ImageServer server, T type) { - return new Builder<>(server, type); - } - - /** - * Create an {@link Img} from the input image and the provided level. - *

- * The {@link Img} returned by this class is immutable. This means that any attempt to write data to it will result in an - * {@link UnsupportedOperationException}. - *

- * See {@link #AXIS_X}, {@link #AXIS_Y}, {@link #AXIS_CHANNEL}, {@link #AXIS_Z}, and {@link #AXIS_TIME} to get the physical - * interpretation of the dimensions of the returned {@link Img}. - *

- * Pixels of the returned image are lazily fetched. - * - * @param level the level to consider - * @return an {@link Img} corresponding to the provided level of the input image - * @throws IllegalArgumentException if the provided level does not match with a level of the input image - */ - public Img createForLevel(int level) { - if (level < 0 || level >= server.getMetadata().nLevels()) { - throw new IllegalArgumentException(String.format( - "The provided level %d is not within 0 and %d", - level, - server.getMetadata().nLevels() - 1 - )); - } - - List tiles = new ArrayList<>(server.getTileRequestManager().getTileRequestsForLevel(level)); - - return new LazyCellImg<>( - new CellGrid( - new long[] { - server.getMetadata().getLevel(level).getWidth(), - server.getMetadata().getLevel(level).getHeight(), - numberOfChannels, - server.nZSlices(), - server.nTimepoints() - }, - new int[] { - server.getMetadata().getPreferredTileWidth(), - server.getMetadata().getPreferredTileHeight(), - numberOfChannels, - 1, - 1 - } - ), - type, - cellIndex -> cellCache.getCell(tiles.get(Math.toIntExact(cellIndex)), this::createCell) - ); - } - - /** - * Create a {@link RandomAccessibleInterval} from the input image and the provided downsample. - *

- * The {@link RandomAccessibleInterval} returned by this class is immutable. This means that any attempt to write data to it will result in an - * {@link UnsupportedOperationException}. - *

- * See {@link #AXIS_X}, {@link #AXIS_Y}, {@link #AXIS_CHANNEL}, {@link #AXIS_Z}, and {@link #AXIS_TIME} to get the physical - * interpretation of the dimensions of the returned {@link RandomAccessibleInterval}. - *

- * Values of the returned image are lazily fetched. - *

- * If the input image has to be scaled and its {@link ImageServerMetadata#getChannelType() channel type} is - * {@link ImageServerMetadata.ChannelType#CLASSIFICATION}, then the nearest neighbor interpolation is used. - * Otherwise, the linear interpolation is used. - * - * @param downsample the downsample to apply to the input image. Must be greater than 0 - * @return a {@link RandomAccessibleInterval} corresponding to the input image with the provided downsample applied - * @throws IllegalArgumentException if the provided downsample is not greater than 0 - */ - public RandomAccessibleInterval createForDownsample(double downsample) { - if (downsample <= 0) { - throw new IllegalArgumentException(String.format("The provided downsample %f is not greater than 0", downsample)); - } - - int level = ServerTools.getPreferredResolutionLevel(server, downsample); - - if (server.getMetadata().getChannelType() == ImageServerMetadata.ChannelType.CLASSIFICATION) { - return AccessibleScaler.scaleWithNearestNeighborInterpolation(createForLevel(level), server.getDownsampleForResolution(level) / downsample); - } else { - return AccessibleScaler.scaleWithLinearInterpolation(createForLevel(level), server.getDownsampleForResolution(level) / downsample); - } - } - - /** - * A builder to create an instance of {@link ImgCreator}. - * - * @param the type of the returned accessibles of {@link ImgCreator} should have - */ - public static class Builder & NumericType> { - - private static final CellCache defaultCellCache = new CellCache((int) (Runtime.getRuntime().maxMemory() * 0.5 / (1024 * 1024))); - private final ImageServer server; - private final T type; - private CellCache cellCache = defaultCellCache; - - private Builder(ImageServer server, T type) { - checkType(server, type); - if (server.nChannels() <= 0) { - throw new IllegalArgumentException(String.format("The provided image has less than one channel (%d)", server.nChannels())); - } - - this.server = server; - this.type = type; - } - - /** - * Accessibles returned by this class will be divided into cells, which will be cached to gain performance. This function sets the - * cache to use. By default, a static cache of maximal size half the amount of the {@link Runtime#maxMemory() max memory} is used. - * - * @param cellCache the cache to use - * @return this builder - * @throws NullPointerException if the provided cache is null - */ - public Builder cellCache(CellCache cellCache) { - this.cellCache = Objects.requireNonNull(cellCache); - return this; - } - - /** - * Build an instance of {@link ImgCreator}. - * - * @return a new instance of {@link ImgCreator} - */ - public ImgCreator build() { - if (server.isRGB()) { - return new ImgCreator<>(this, ArgbBufferedImageAccess::new); - } else { - return switch (server.getPixelType()) { - case UINT8, INT8 -> new ImgCreator<>(this, image -> new ByteRasterAccess(image.getRaster())); - case UINT16, INT16 -> new ImgCreator<>(this, image -> new ShortRasterAccess(image.getRaster())); - case UINT32, INT32 -> new ImgCreator<>(this, image -> new IntRasterAccess(image.getRaster())); - case FLOAT32 -> new ImgCreator<>(this, image -> new FloatRasterAccess(image.getRaster())); - case FLOAT64 -> new ImgCreator<>(this, image -> new DoubleRasterAccess(image.getRaster())); - }; - } - } - - private static void checkType(ImageServer server, T type) { - if (server.isRGB()) { - if (!(type instanceof ARGBType)) { - throw new IllegalArgumentException(String.format( - "The provided type %s is not an ARGBType, which is the one expected for RGB images", - type - )); - } - } else { - switch (server.getPixelType()) { - case UINT8 -> { - if (!(type instanceof UnsignedByteType)) { - throw new IllegalArgumentException(String.format( - "The provided type %s is not a ByteType, which is the one expected for non-RGB UINT8 images", - type - )); - } - } - case INT8 -> { - if (!(type instanceof ByteType)) { - throw new IllegalArgumentException(String.format( - "The provided type %s is not a UnsignedByteType, which is the one expected for non-RGB INT8 images", - type - )); - } - } - case UINT16 -> { - if (!(type instanceof UnsignedShortType)) { - throw new IllegalArgumentException(String.format( - "The provided type %s is not a UnsignedShortType, which is the one expected for non-RGB UINT16 images", - type - )); - } - } - case INT16 -> { - if (!(type instanceof ShortType)) { - throw new IllegalArgumentException(String.format( - "The provided type %s is not a ShortType, which is the one expected for non-RGB INT16 images", - type - )); - } - } - case UINT32 -> { - if (!(type instanceof UnsignedIntType)) { - throw new IllegalArgumentException(String.format( - "The provided type %s is not a UnsignedIntType, which is the one expected for non-RGB UINT32 images", - type - )); - } - } - case INT32 -> { - if (!(type instanceof IntType)) { - throw new IllegalArgumentException(String.format( - "The provided type %s is not a IntType, which is the one expected for non-RGB INT32 images", - type - )); - } - } - case FLOAT32 -> { - if (!(type instanceof FloatType)) { - throw new IllegalArgumentException(String.format( - "The provided type %s is not a FloatType, which is the one expected for non-RGB FLOAT32 images", - type - )); - } - } - case FLOAT64 -> { - if (!(type instanceof DoubleType)) { - throw new IllegalArgumentException(String.format( - "The provided type %s is not a DoubleType, which is the one expected for non-RGB FLOAT64 images", - type - )); - } - } - } - } - } - } - - @SuppressWarnings("unchecked") - private static & NumericType> T getTypeOfServer(ImageServer server) { - if (server.isRGB()) { - return (T) new ARGBType(); - } - - return switch (server.getPixelType()) { - case UINT8 -> (T) new UnsignedByteType(); - case INT8 -> (T) new ByteType(); - case UINT16 -> (T) new UnsignedShortType(); - case INT16 -> (T) new ShortType(); - case UINT32 -> (T) new UnsignedIntType(); - case INT32 -> (T) new IntType(); - case FLOAT32 -> (T) new FloatType(); - case FLOAT64 -> (T) new DoubleType(); - }; - } - - private Cell createCell(TileRequest tile) { - BufferedImage image; - try { - image = server.readRegion(tile.getRegionRequest()); - } catch (IOException e) { - throw new RuntimeException(e); - } - - return new Cell<>( - new int[]{ image.getWidth(), image.getHeight(), numberOfChannels, 1, 1 }, - new long[]{ tile.getTileX(), tile.getTileY(), 0, tile.getZ(), tile.getT()}, - cellCreator.apply(image) - ); - } -} diff --git a/src/main/java/qupath/ext/imglib2/ImgLib2ImageServer.java b/src/main/java/qupath/ext/imglib2/ImgLib2ImageServer.java index 9dcd295..7f0b2eb 100644 --- a/src/main/java/qupath/ext/imglib2/ImgLib2ImageServer.java +++ b/src/main/java/qupath/ext/imglib2/ImgLib2ImageServer.java @@ -63,23 +63,23 @@ private ImgLib2ImageServer(List> accessibl RandomAccessibleInterval firstAccessible = accessibles.getFirst(); T value = firstAccessible.firstElement(); this.metadata = new ImageServerMetadata.Builder(metadata) - .width((int) firstAccessible.dimension(ImgCreator.AXIS_X)) - .height((int) firstAccessible.dimension(ImgCreator.AXIS_Y)) + .width((int) firstAccessible.dimension(ImgBuilder.AXIS_X)) + .height((int) firstAccessible.dimension(ImgBuilder.AXIS_Y)) .rgb(value instanceof ARGBType) .pixelType(pixelType) .levels(createResolutionLevels(accessibles)) - .sizeZ((int) firstAccessible.dimension(ImgCreator.AXIS_Z)) - .sizeT((int) firstAccessible.dimension(ImgCreator.AXIS_TIME)) + .sizeZ((int) firstAccessible.dimension(ImgBuilder.AXIS_Z)) + .sizeT((int) firstAccessible.dimension(ImgBuilder.AXIS_TIME)) .build(); - this.numberOfChannelsInAccessibles = (int) firstAccessible.dimension(ImgCreator.AXIS_CHANNEL); + this.numberOfChannelsInAccessibles = (int) firstAccessible.dimension(ImgBuilder.AXIS_CHANNEL); } /** * Create a {@link ImgLib2ImageServer} builder. *

- * The provided accessibles must correspond to the ones returned by functions of {@link ImgCreator}: they must have - * {@link ImgCreator#NUMBER_OF_AXES} dimensions, the X-axes must correspond to {@link ImgCreator#AXIS_X}, and so on. + * The provided accessibles must correspond to the ones returned by functions of {@link ImgBuilder}: they must have + * {@link ImgBuilder#NUMBER_OF_AXES} dimensions, the X-axes must correspond to {@link ImgBuilder#AXIS_X}, and so on. *

* All dimensions of the provided accessibles must contain {@link Integer#MAX_VALUE} pixels or less. *

@@ -90,10 +90,12 @@ private ImgLib2ImageServer(List> accessibl * @param accessibles one accessible for each resolution level the image server should have, from highest to lowest * resolution. Must not be empty. Each accessible must have the same number of channels, z-stacks, * and timepoints + * @return a new builder from the provided accessibles + * @param the pixel type of the provided accessibles * @throws NullPointerException if the provided list is null or contain a null element * @throws IllegalArgumentException if the provided list is empty, if the accessible type is not among the list * mentioned above, if a dimension of a provided accessible contain more than {@link Integer#MAX_VALUE} pixels, - * if the provided accessibles do not have {@link ImgCreator#NUMBER_OF_AXES} axes, if the provided accessibles + * if the provided accessibles do not have {@link ImgBuilder#NUMBER_OF_AXES} axes, if the provided accessibles * do not have the same number of channels, z-stacks, or timepoints, or if the accessible type is {@link ARGBType} * and the number of channels of the accessibles is not 1 */ @@ -104,16 +106,16 @@ public static & NumericType> Builder builder(List @Override protected BufferedImage readTile(TileRequest tileRequest) { RandomAccessibleInterval tile = getImgLib2Tile(tileRequest); - int minTileX = Math.toIntExact(tile.min(ImgCreator.AXIS_X)); - int minTileY = Math.toIntExact(tile.min(ImgCreator.AXIS_Y)); - int minTileC = Math.toIntExact(tile.min(ImgCreator.AXIS_CHANNEL)); + int minTileX = Math.toIntExact(tile.min(ImgBuilder.AXIS_X)); + int minTileY = Math.toIntExact(tile.min(ImgBuilder.AXIS_Y)); + int minTileC = Math.toIntExact(tile.min(ImgBuilder.AXIS_CHANNEL)); Cursor cursor = tile.localizingCursor(); if (isRGB()) { return createArgbImage(tileRequest, cursor, minTileX, minTileY); } else { - int xyPlaneSize = Math.toIntExact(tile.dimension(ImgCreator.AXIS_X) * tile.dimension(ImgCreator.AXIS_Y)); + int xyPlaneSize = Math.toIntExact(tile.dimension(ImgBuilder.AXIS_X) * tile.dimension(ImgBuilder.AXIS_Y)); DataBuffer dataBuffer = switch (metadata.getPixelType()) { case UINT8 -> createUint8DataBuffer(cursor, xyPlaneSize, tileRequest.getTileWidth(), minTileX, minTileY, minTileC); @@ -210,7 +212,7 @@ private Builder(List> accessibles) { .height(1) // the height will be ignored, but it must be > 0 to avoid an exception when calling build() .channels(value instanceof ARGBType ? ImageChannel.getDefaultRGBChannels() : - ImageChannel.getDefaultChannelList((int) firstAccessible.dimension(ImgCreator.AXIS_CHANNEL)) + ImageChannel.getDefaultChannelList((int) firstAccessible.dimension(ImgBuilder.AXIS_CHANNEL)) ) .preferredTileSize(DEFAULT_TILE_SIZE, DEFAULT_TILE_SIZE) .build(); @@ -351,19 +353,19 @@ private static & NumericType> void checkAccessibles( } for (RandomAccessibleInterval accessible: accessibles) { - if (accessible.numDimensions() != ImgCreator.NUMBER_OF_AXES) { + if (accessible.numDimensions() != ImgBuilder.NUMBER_OF_AXES) { throw new IllegalArgumentException(String.format( "The provided accessible %s does not have %d dimensions", accessible, - ImgCreator.NUMBER_OF_AXES + ImgBuilder.NUMBER_OF_AXES )); } } Map axes = Map.of( - ImgCreator.AXIS_CHANNEL, "number of channels", - ImgCreator.AXIS_Z, "number of z-stacks", - ImgCreator.AXIS_TIME, "number of timepoints" + ImgBuilder.AXIS_CHANNEL, "number of channels", + ImgBuilder.AXIS_Z, "number of z-stacks", + ImgBuilder.AXIS_TIME, "number of timepoints" ); for (var axis: axes.entrySet()) { List numberOfElements = accessibles.stream() @@ -381,11 +383,11 @@ private static & NumericType> void checkAccessibles( } RandomAccessibleInterval firstAccessible = accessibles.getFirst(); - if (firstAccessible.firstElement() instanceof ARGBType && firstAccessible.dimension(ImgCreator.AXIS_CHANNEL) != 1) { + if (firstAccessible.firstElement() instanceof ARGBType && firstAccessible.dimension(ImgBuilder.AXIS_CHANNEL) != 1) { throw new IllegalArgumentException(String.format( "The provided accessibles %s have the ARGB type, but not one channel (found %d)", accessibles, - firstAccessible.dimension(ImgCreator.AXIS_CHANNEL) + firstAccessible.dimension(ImgBuilder.AXIS_CHANNEL) )); } } @@ -408,12 +410,12 @@ private static & NumericType> void checkChannels( )); } } else { - if (accessibles.getFirst().dimension(ImgCreator.AXIS_CHANNEL) != channels.size()) { + if (accessibles.getFirst().dimension(ImgBuilder.AXIS_CHANNEL) != channels.size()) { throw new IllegalArgumentException(String.format( "There are %d provided channels, but the current accessibles %s contain %s channels", channels.size(), accessibles, - accessibles.getFirst().dimension(ImgCreator.AXIS_CHANNEL) + accessibles.getFirst().dimension(ImgBuilder.AXIS_CHANNEL) )); } } @@ -422,14 +424,14 @@ private static & NumericType> void checkChannels( private static List createResolutionLevels(List> accessibles) { ImageServerMetadata.ImageResolutionLevel.Builder builder = new ImageServerMetadata.ImageResolutionLevel.Builder( - (int) accessibles.getFirst().dimension(ImgCreator.AXIS_X), - (int) accessibles.getFirst().dimension(ImgCreator.AXIS_Y) + (int) accessibles.getFirst().dimension(ImgBuilder.AXIS_X), + (int) accessibles.getFirst().dimension(ImgBuilder.AXIS_Y) ); for (RandomAccessibleInterval accessible: accessibles) { builder.addLevel( - (int) accessible.dimension(ImgCreator.AXIS_X), - (int) accessible.dimension(ImgCreator.AXIS_Y) + (int) accessible.dimension(ImgBuilder.AXIS_X), + (int) accessible.dimension(ImgBuilder.AXIS_Y) ); } @@ -439,22 +441,22 @@ private static List createResolutionLe private RandomAccessibleInterval getImgLib2Tile(TileRequest tileRequest) { RandomAccessibleInterval wholeLevel = accessibles.get(tileRequest.getLevel()); - long[] minWholeLevel = new long[ImgCreator.NUMBER_OF_AXES]; + long[] minWholeLevel = new long[ImgBuilder.NUMBER_OF_AXES]; wholeLevel.min(minWholeLevel); - long[] min = new long[ImgCreator.NUMBER_OF_AXES]; - min[ImgCreator.AXIS_X] = minWholeLevel[ImgCreator.AXIS_X] + tileRequest.getTileX(); - min[ImgCreator.AXIS_Y] = minWholeLevel[ImgCreator.AXIS_Y] + tileRequest.getTileY(); - min[ImgCreator.AXIS_CHANNEL] = minWholeLevel[ImgCreator.AXIS_CHANNEL]; - min[ImgCreator.AXIS_Z] = minWholeLevel[ImgCreator.AXIS_Z] + tileRequest.getZ(); - min[ImgCreator.AXIS_TIME] = minWholeLevel[ImgCreator.AXIS_TIME] + tileRequest.getT(); + long[] min = new long[ImgBuilder.NUMBER_OF_AXES]; + min[ImgBuilder.AXIS_X] = minWholeLevel[ImgBuilder.AXIS_X] + tileRequest.getTileX(); + min[ImgBuilder.AXIS_Y] = minWholeLevel[ImgBuilder.AXIS_Y] + tileRequest.getTileY(); + min[ImgBuilder.AXIS_CHANNEL] = minWholeLevel[ImgBuilder.AXIS_CHANNEL]; + min[ImgBuilder.AXIS_Z] = minWholeLevel[ImgBuilder.AXIS_Z] + tileRequest.getZ(); + min[ImgBuilder.AXIS_TIME] = minWholeLevel[ImgBuilder.AXIS_TIME] + tileRequest.getT(); - long[] max = new long[ImgCreator.NUMBER_OF_AXES]; // max is inclusive, hence the -1 - max[ImgCreator.AXIS_X] = min[ImgCreator.AXIS_X] + tileRequest.getTileWidth() - 1; - max[ImgCreator.AXIS_Y] = min[ImgCreator.AXIS_Y] + tileRequest.getTileHeight() - 1; - max[ImgCreator.AXIS_CHANNEL] = min[ImgCreator.AXIS_CHANNEL] + numberOfChannelsInAccessibles - 1; - max[ImgCreator.AXIS_Z] = min[ImgCreator.AXIS_Z]; - max[ImgCreator.AXIS_TIME] = min[ImgCreator.AXIS_TIME]; + long[] max = new long[ImgBuilder.NUMBER_OF_AXES]; // max is inclusive, hence the -1 + max[ImgBuilder.AXIS_X] = min[ImgBuilder.AXIS_X] + tileRequest.getTileWidth() - 1; + max[ImgBuilder.AXIS_Y] = min[ImgBuilder.AXIS_Y] + tileRequest.getTileHeight() - 1; + max[ImgBuilder.AXIS_CHANNEL] = min[ImgBuilder.AXIS_CHANNEL] + numberOfChannelsInAccessibles - 1; + max[ImgBuilder.AXIS_Z] = min[ImgBuilder.AXIS_Z]; + max[ImgBuilder.AXIS_TIME] = min[ImgBuilder.AXIS_TIME]; return Views.interval(wholeLevel, min, max); } @@ -466,8 +468,8 @@ private BufferedImage createArgbImage(TileRequest tileRequest, Cursor cursor, while (cursor.hasNext()) { ARGBType value = (ARGBType) cursor.next(); - int xy = cursor.getIntPosition(ImgCreator.AXIS_X) - minTileX + - (cursor.getIntPosition(ImgCreator.AXIS_Y) - minTileY) * tileRequest.getTileWidth(); + int xy = cursor.getIntPosition(ImgBuilder.AXIS_X) - minTileX + + (cursor.getIntPosition(ImgBuilder.AXIS_Y) - minTileY) * tileRequest.getTileWidth(); buffer.setElem(xy, value.get()); } @@ -481,9 +483,9 @@ private DataBuffer createUint8DataBuffer(Cursor cursor, int xyPlaneSize, int while (cursor.hasNext()) { UnsignedByteType value = (UnsignedByteType) cursor.next(); - int c = cursor.getIntPosition(ImgCreator.AXIS_CHANNEL) - minTileC; - int xy = cursor.getIntPosition(ImgCreator.AXIS_X) - minTileX + - (cursor.getIntPosition(ImgCreator.AXIS_Y) - minTileY) * tileWidth; + int c = cursor.getIntPosition(ImgBuilder.AXIS_CHANNEL) - minTileC; + int xy = cursor.getIntPosition(ImgBuilder.AXIS_X) - minTileX + + (cursor.getIntPosition(ImgBuilder.AXIS_Y) - minTileY) * tileWidth; pixels[c][xy] = value.getByte(); } @@ -497,9 +499,9 @@ private DataBuffer createInt8DataBuffer(Cursor cursor, int xyPlaneSize, int t while (cursor.hasNext()) { ByteType value = (ByteType) cursor.next(); - int c = cursor.getIntPosition(ImgCreator.AXIS_CHANNEL) - minTileC; - int xy = cursor.getIntPosition(ImgCreator.AXIS_X) - minTileX + - (cursor.getIntPosition(ImgCreator.AXIS_Y) - minTileY) * tileWidth; + int c = cursor.getIntPosition(ImgBuilder.AXIS_CHANNEL) - minTileC; + int xy = cursor.getIntPosition(ImgBuilder.AXIS_X) - minTileX + + (cursor.getIntPosition(ImgBuilder.AXIS_Y) - minTileY) * tileWidth; pixels[c][xy] = value.getByte(); } @@ -513,9 +515,9 @@ private DataBuffer createUint16DataBuffer(Cursor cursor, int xyPlaneSize, int while (cursor.hasNext()) { UnsignedShortType value = (UnsignedShortType) cursor.next(); - int c = cursor.getIntPosition(ImgCreator.AXIS_CHANNEL) - minTileC; - int xy = cursor.getIntPosition(ImgCreator.AXIS_X) - minTileX + - (cursor.getIntPosition(ImgCreator.AXIS_Y) - minTileY) * tileWidth; + int c = cursor.getIntPosition(ImgBuilder.AXIS_CHANNEL) - minTileC; + int xy = cursor.getIntPosition(ImgBuilder.AXIS_X) - minTileX + + (cursor.getIntPosition(ImgBuilder.AXIS_Y) - minTileY) * tileWidth; pixels[c][xy] = value.getShort(); } @@ -529,9 +531,9 @@ private DataBuffer createInt16DataBuffer(Cursor cursor, int xyPlaneSize, int while (cursor.hasNext()) { ShortType value = (ShortType) cursor.next(); - int c = cursor.getIntPosition(ImgCreator.AXIS_CHANNEL) - minTileC; - int xy = cursor.getIntPosition(ImgCreator.AXIS_X) - minTileX + - (cursor.getIntPosition(ImgCreator.AXIS_Y) - minTileY) * tileWidth; + int c = cursor.getIntPosition(ImgBuilder.AXIS_CHANNEL) - minTileC; + int xy = cursor.getIntPosition(ImgBuilder.AXIS_X) - minTileX + + (cursor.getIntPosition(ImgBuilder.AXIS_Y) - minTileY) * tileWidth; pixels[c][xy] = value.getShort(); } @@ -545,9 +547,9 @@ private DataBuffer createUint32DataBuffer(Cursor cursor, int xyPlaneSize, int while (cursor.hasNext()) { UnsignedIntType value = (UnsignedIntType) cursor.next(); - int c = cursor.getIntPosition(ImgCreator.AXIS_CHANNEL) - minTileC; - int xy = cursor.getIntPosition(ImgCreator.AXIS_X) - minTileX + - (cursor.getIntPosition(ImgCreator.AXIS_Y) - minTileY) * tileWidth; + int c = cursor.getIntPosition(ImgBuilder.AXIS_CHANNEL) - minTileC; + int xy = cursor.getIntPosition(ImgBuilder.AXIS_X) - minTileX + + (cursor.getIntPosition(ImgBuilder.AXIS_Y) - minTileY) * tileWidth; pixels[c][xy] = value.getInt(); } @@ -561,9 +563,9 @@ private DataBuffer createInt32DataBuffer(Cursor cursor, int xyPlaneSize, int while (cursor.hasNext()) { IntType value = (IntType) cursor.next(); - int c = cursor.getIntPosition(ImgCreator.AXIS_CHANNEL) - minTileC; - int xy = cursor.getIntPosition(ImgCreator.AXIS_X) - minTileX + - (cursor.getIntPosition(ImgCreator.AXIS_Y) - minTileY) * tileWidth; + int c = cursor.getIntPosition(ImgBuilder.AXIS_CHANNEL) - minTileC; + int xy = cursor.getIntPosition(ImgBuilder.AXIS_X) - minTileX + + (cursor.getIntPosition(ImgBuilder.AXIS_Y) - minTileY) * tileWidth; pixels[c][xy] = value.getInt(); } @@ -577,9 +579,9 @@ private DataBuffer createFloat32DataBuffer(Cursor cursor, int xyPlaneSize, in while (cursor.hasNext()) { FloatType value = (FloatType) cursor.next(); - int c = cursor.getIntPosition(ImgCreator.AXIS_CHANNEL) - minTileC; - int xy = cursor.getIntPosition(ImgCreator.AXIS_X) - minTileX + - (cursor.getIntPosition(ImgCreator.AXIS_Y) - minTileY) * tileWidth; + int c = cursor.getIntPosition(ImgBuilder.AXIS_CHANNEL) - minTileC; + int xy = cursor.getIntPosition(ImgBuilder.AXIS_X) - minTileX + + (cursor.getIntPosition(ImgBuilder.AXIS_Y) - minTileY) * tileWidth; pixels[c][xy] = value.get(); } @@ -593,9 +595,9 @@ private DataBuffer createFloat64DataBuffer(Cursor cursor, int xyPlaneSize, in while (cursor.hasNext()) { DoubleType value = (DoubleType) cursor.next(); - int c = cursor.getIntPosition(ImgCreator.AXIS_CHANNEL) - minTileC; - int xy = cursor.getIntPosition(ImgCreator.AXIS_X) - minTileX + - (cursor.getIntPosition(ImgCreator.AXIS_Y) - minTileY) * tileWidth; + int c = cursor.getIntPosition(ImgBuilder.AXIS_CHANNEL) - minTileC; + int xy = cursor.getIntPosition(ImgBuilder.AXIS_X) - minTileX + + (cursor.getIntPosition(ImgBuilder.AXIS_Y) - minTileY) * tileWidth; pixels[c][xy] = value.get(); } diff --git a/src/main/java/qupath/ext/imglib2/package-info.java b/src/main/java/qupath/ext/imglib2/package-info.java index 33d00a5..5806ab3 100644 --- a/src/main/java/qupath/ext/imglib2/package-info.java +++ b/src/main/java/qupath/ext/imglib2/package-info.java @@ -2,7 +2,7 @@ * This package contains classes to create and work with ImgLib2 accessibles from QuPath {@link qupath.lib.images.servers.ImageServer}: *