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/ImgBuilder.java b/src/main/java/qupath/ext/imglib2/ImgBuilder.java new file mode 100644 index 0000000..93b6eaa --- /dev/null +++ b/src/main/java/qupath/ext/imglib2/ImgBuilder.java @@ -0,0 +1,458 @@ +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.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 #createBuilder(ImageServer)} or {@link #createBuilder(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 #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 createBuilder(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 createBuilder(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::buildForLevel) + .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 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", + 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 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}. + *

+ * 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 buildForDownsample(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( + buildForLevel(level), + server.getDownsampleForResolution(level) / downsample + ); + } else { + return AccessibleScaler.scaleWithLinearInterpolation( + buildForLevel(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/main/java/qupath/ext/imglib2/ImgCreator.java b/src/main/java/qupath/ext/imglib2/ImgCreator.java deleted file mode 100644 index ddf8dc8..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.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.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/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/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}: *