From 6e812fbe63097e1ec6ff74cf54e4285118d9d084 Mon Sep 17 00:00:00 2001 From: lleplat Date: Fri, 28 Nov 2025 11:35:19 +0000 Subject: [PATCH 1/4] Add Realtype builders --- .../java/qupath/ext/imglib2/ImgBuilder.java | 321 ++++++++++++++++-- .../accesses/ArgbBufferedImageAccess.java | 3 +- .../accesses/ByteBufferedImageAccess.java | 92 +++++ 3 files changed, 394 insertions(+), 22 deletions(-) create mode 100644 src/main/java/qupath/ext/imglib2/accesses/ByteBufferedImageAccess.java diff --git a/src/main/java/qupath/ext/imglib2/ImgBuilder.java b/src/main/java/qupath/ext/imglib2/ImgBuilder.java index 17325cc..ece62a5 100644 --- a/src/main/java/qupath/ext/imglib2/ImgBuilder.java +++ b/src/main/java/qupath/ext/imglib2/ImgBuilder.java @@ -8,6 +8,7 @@ import net.imglib2.type.NativeType; import net.imglib2.type.numeric.ARGBType; import net.imglib2.type.numeric.NumericType; +import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.ByteType; import net.imglib2.type.numeric.integer.IntType; import net.imglib2.type.numeric.integer.ShortType; @@ -17,6 +18,7 @@ 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.ByteBufferedImageAccess; import qupath.ext.imglib2.accesses.ByteRasterAccess; import qupath.ext.imglib2.accesses.DoubleRasterAccess; import qupath.ext.imglib2.accesses.FloatRasterAccess; @@ -79,13 +81,13 @@ public class ImgBuilder & NumericType, A extends Siza private final T type; private CellCache cellCache = DEFAULT_CELL_CACHE; - private ImgBuilder(ImageServer server, T type, Function cellCreator) { + private ImgBuilder(ImageServer server, T type, Function cellCreator, int numberOfChannels) { 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.numberOfChannels = numberOfChannels; this.cellCreator = cellCreator; this.type = type; } @@ -95,6 +97,7 @@ 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 #createBuilder(ImageServer, NativeType)} instead. + * See also this function to know which pixel type is used. * * @param server the input image * @return a builder to create an instance of this class @@ -102,48 +105,56 @@ private ImgBuilder(ImageServer server, T type, Function createBuilder(ImageServer server) { if (server.isRGB()) { - return new ImgBuilder<>(server, new ARGBType(), ArgbBufferedImageAccess::new); + return new ImgBuilder<>(server, new ARGBType(), ArgbBufferedImageAccess::new, 1); } else { return switch (server.getPixelType()) { case UINT8 -> new ImgBuilder<>( server, new UnsignedByteType(), - image -> new ByteRasterAccess(image.getRaster()) + image -> new ByteRasterAccess(image.getRaster()), + server.nChannels() ); case INT8 -> new ImgBuilder<>( server, new ByteType(), - image -> new ByteRasterAccess(image.getRaster()) + image -> new ByteRasterAccess(image.getRaster()), + server.nChannels() ); case UINT16 -> new ImgBuilder<>( server, new UnsignedShortType(), - image -> new ShortRasterAccess(image.getRaster()) + image -> new ShortRasterAccess(image.getRaster()), + server.nChannels() ); case INT16 -> new ImgBuilder<>( server, new ShortType(), - image -> new ShortRasterAccess(image.getRaster()) + image -> new ShortRasterAccess(image.getRaster()), + server.nChannels() ); case UINT32 -> new ImgBuilder<>( server, new UnsignedIntType(), - image -> new IntRasterAccess(image.getRaster()) + image -> new IntRasterAccess(image.getRaster()), + server.nChannels() ); case INT32 -> new ImgBuilder<>( server, new IntType(), - image -> new IntRasterAccess(image.getRaster()) + image -> new IntRasterAccess(image.getRaster()), + server.nChannels() ); case FLOAT32 -> new ImgBuilder<>( server, new FloatType(), - image -> new FloatRasterAccess(image.getRaster()) + image -> new FloatRasterAccess(image.getRaster()), + server.nChannels() ); case FLOAT64 -> new ImgBuilder<>( server, new DoubleType(), - image -> new DoubleRasterAccess(image.getRaster()) + image -> new DoubleRasterAccess(image.getRaster()), + server.nChannels() ); }; } @@ -154,7 +165,10 @@ private ImgBuilder(ImageServer server, T type, Function * The provided type must be compatible with the input image: *
    - *
  • If the input image is {@link ImageServer#isRGB() RGB}, the type must be {@link ARGBType}.
  • + *
  • + * If the input image is {@link ImageServer#isRGB() RGB}, the type must be {@link ARGBType}. Images created + * by the returned builder will have one channel. + *
  • *
  • * Else: *
      @@ -205,14 +219,203 @@ private ImgBuilder(ImageServer server, T type, Function(server, type, ArgbBufferedImageAccess::new); + return new ImgBuilder<>(server, type, ArgbBufferedImageAccess::new, 1); + } else { + return switch (server.getPixelType()) { + case UINT8, INT8 -> new ImgBuilder<>( + server, + type, + image -> new ByteRasterAccess(image.getRaster()), + server.nChannels() + ); + case UINT16, INT16 -> new ImgBuilder<>( + server, + type, + image -> new ShortRasterAccess(image.getRaster()), + server.nChannels() + ); + case UINT32, INT32 -> new ImgBuilder<>( + server, + type, + image -> new IntRasterAccess(image.getRaster()), + server.nChannels() + ); + case FLOAT32 -> new ImgBuilder<>( + server, + type, + image -> new FloatRasterAccess(image.getRaster()), + server.nChannels() + ); + case FLOAT64 -> new ImgBuilder<>( + server, + type, + image -> new DoubleRasterAccess(image.getRaster()), + 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 #createRealBuilder(ImageServer, NativeType)} + * instead. See also this function to know which pixel type is used. + * + * @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 createRealBuilder(ImageServer server) { + if (server.isRGB()) { + return new ImgBuilder<>(server, new ARGBType(), ByteBufferedImageAccess::new, 4); + } else { + return switch (server.getPixelType()) { + case UINT8 -> new ImgBuilder<>( + server, + new UnsignedByteType(), + image -> new ByteRasterAccess(image.getRaster()), + server.nChannels() + ); + case INT8 -> new ImgBuilder<>( + server, + new ByteType(), + image -> new ByteRasterAccess(image.getRaster()), + server.nChannels() + ); + case UINT16 -> new ImgBuilder<>( + server, + new UnsignedShortType(), + image -> new ShortRasterAccess(image.getRaster()), + server.nChannels() + ); + case INT16 -> new ImgBuilder<>( + server, + new ShortType(), + image -> new ShortRasterAccess(image.getRaster()), + server.nChannels() + ); + case UINT32 -> new ImgBuilder<>( + server, + new UnsignedIntType(), + image -> new IntRasterAccess(image.getRaster()), + server.nChannels() + ); + case INT32 -> new ImgBuilder<>( + server, + new IntType(), + image -> new IntRasterAccess(image.getRaster()), + server.nChannels() + ); + case FLOAT32 -> new ImgBuilder<>( + server, + new FloatType(), + image -> new FloatRasterAccess(image.getRaster()), + server.nChannels() + ); + case FLOAT64 -> new ImgBuilder<>( + server, + new DoubleType(), + image -> new DoubleRasterAccess(image.getRaster()), + server.nChannels() + ); + }; + } + } + + /** + * Create a builder from an {@link ImageServer}. This doesn't create any accessibles yet. + *

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

        + *
      • + * If the input image is {@link ImageServer#isRGB() RGB}, the type must be {@link UnsignedByteType}. Images + * created by the returned builder will have 4 channels (RGBA). + *
      • + *
      • + * Else: + *
          + *
        • + * If the input image has the {@link PixelType#UINT8} {@link ImageServer#getPixelType() pixel type}, + * the type must be {@link UnsignedByteType}. + *
        • + *
        • + * If the input image has the {@link PixelType#INT8} {@link ImageServer#getPixelType() pixel type}, + * the type must be {@link ByteType}. + *
        • + *
        • + * If the input image has the {@link PixelType#UINT16} {@link ImageServer#getPixelType() pixel type}, + * the type must be {@link UnsignedShortType}. + *
        • + *
        • + * If the input image has the {@link PixelType#INT16} {@link ImageServer#getPixelType() pixel type}, + * the type must be {@link ShortType}. + *
        • + *
        • + * If the input image has the {@link PixelType#UINT32} {@link ImageServer#getPixelType() pixel type}, + * the type must be {@link UnsignedIntType}. + *
        • + *
        • + * If the input image has the {@link PixelType#INT32} {@link ImageServer#getPixelType() pixel type}, + * the type must be {@link IntType}. + *
        • + *
        • + * If the input image has the {@link PixelType#FLOAT32} {@link ImageServer#getPixelType() pixel type}, + * the type must be {@link FloatType}. + *
        • + *
        • + * If the input image has the {@link PixelType#FLOAT64} {@link ImageServer#getPixelType() pixel type}, + * the type must be {@link DoubleType}. + *
        • + *
        + *
      • + *
      + * + * @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 & RealType> ImgBuilder createRealBuilder(ImageServer server, T type) { + checkRealType(server, type); + + if (server.isRGB()) { + return new ImgBuilder<>(server, type, ByteBufferedImageAccess::new, 4); } 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())); + case UINT8, INT8 -> new ImgBuilder<>( + server, + type, + image -> new ByteRasterAccess(image.getRaster()), + server.nChannels() + ); + case UINT16, INT16 -> new ImgBuilder<>( + server, + type, + image -> new ShortRasterAccess(image.getRaster()), + server.nChannels() + ); + case UINT32, INT32 -> new ImgBuilder<>( + server, + type, + image -> new IntRasterAccess(image.getRaster()), + server.nChannels() + ); + case FLOAT32 -> new ImgBuilder<>( + server, + type, + image -> new FloatRasterAccess(image.getRaster()), + server.nChannels() + ); + case FLOAT64 -> new ImgBuilder<>( + server, + type, + image -> new DoubleRasterAccess(image.getRaster()), + server.nChannels() + ); }; } } @@ -376,7 +579,85 @@ private static void checkType(ImageServer server, T type) { 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", + "The provided type %s is not a UnsignedByteType, which is the one expected for non-RGB UINT8 images", + type.getClass() + )); + } + } + case INT8 -> { + if (!(type instanceof ByteType)) { + throw new IllegalArgumentException(String.format( + "The provided type %s is not a ByteType, which is the one expected for non-RGB INT8 images", + type.getClass() + )); + } + } + 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.getClass() + )); + } + } + 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.getClass() + )); + } + } + 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.getClass() + )); + } + } + 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.getClass() + )); + } + } + 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.getClass() + )); + } + } + 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.getClass() + )); + } + } + } + } + } + + private static void checkRealType(ImageServer server, T type) { + if (server.isRGB()) { + if (!(type instanceof UnsignedByteType)) { + throw new IllegalArgumentException(String.format( + "The provided type %s is not an UnsignedByteType, which is the one expected for RGB images", + type.getClass() + )); + } + } else { + switch (server.getPixelType()) { + case UINT8 -> { + if (!(type instanceof UnsignedByteType)) { + throw new IllegalArgumentException(String.format( + "The provided type %s is not a UnsignedByteType, which is the one expected for non-RGB UINT8 images", type.getClass() )); } @@ -384,7 +665,7 @@ private static void checkType(ImageServer server, T 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", + "The provided type %s is not a ByteType, which is the one expected for non-RGB INT8 images", type.getClass() )); } diff --git a/src/main/java/qupath/ext/imglib2/accesses/ArgbBufferedImageAccess.java b/src/main/java/qupath/ext/imglib2/accesses/ArgbBufferedImageAccess.java index 7150526..6c359ea 100644 --- a/src/main/java/qupath/ext/imglib2/accesses/ArgbBufferedImageAccess.java +++ b/src/main/java/qupath/ext/imglib2/accesses/ArgbBufferedImageAccess.java @@ -53,11 +53,10 @@ public ArgbBufferedImageAccess(BufferedImage image) { @Override public int getValue(int index) { - int b = index / planeSize; int xyIndex = index % planeSize; if (canUseDataBuffer) { - int pixel = dataBuffer.getElem(b, xyIndex); + int pixel = dataBuffer.getElem(0, xyIndex); if (alphaProvided) { return pixel; diff --git a/src/main/java/qupath/ext/imglib2/accesses/ByteBufferedImageAccess.java b/src/main/java/qupath/ext/imglib2/accesses/ByteBufferedImageAccess.java new file mode 100644 index 0000000..c702b97 --- /dev/null +++ b/src/main/java/qupath/ext/imglib2/accesses/ByteBufferedImageAccess.java @@ -0,0 +1,92 @@ +package qupath.ext.imglib2.accesses; + +import net.imglib2.img.basictypeaccess.ByteAccess; +import net.imglib2.img.basictypeaccess.volatiles.VolatileAccess; +import qupath.ext.imglib2.SizableDataAccess; +import qupath.lib.common.ColorTools; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferInt; +import java.awt.image.SinglePixelPackedSampleModel; + +/** + * An {@link ByteAccess} whose elements are computed from an (A)RGB {@link BufferedImage}. + *

      + * If the alpha component is not provided (e.g. if the {@link BufferedImage} has the {@link BufferedImage#TYPE_INT_RGB} type), + * then the alpha component of each pixel is considered to be 255. + *

      + * This {@link ByteAccess} is immutable; any attempt to changes its values will result in a + * {@link UnsupportedOperationException}. + *

      + * This data access is marked as volatile but always contain valid data. + */ +public class ByteBufferedImageAccess implements ByteAccess, SizableDataAccess, VolatileAccess { + + private final BufferedImage image; + private final DataBuffer dataBuffer; + private final int width; + private final int planeSize; + private final boolean canUseDataBuffer; + private final boolean alphaProvided; + private final int size; + + /** + * Create the byte buffered image access. + * + * @param image the image containing the values to return. It is expected to be (A)RGB + * @throws NullPointerException if the provided image is null + */ + public ByteBufferedImageAccess(BufferedImage image) { + this.image = image; + this.dataBuffer = this.image.getRaster().getDataBuffer(); + + this.width = this.image.getWidth(); + this.planeSize = width * this.image.getHeight(); + + this.canUseDataBuffer = image.getRaster().getDataBuffer() instanceof DataBufferInt && + image.getRaster().getSampleModel() instanceof SinglePixelPackedSampleModel; + this.alphaProvided = image.getType() == BufferedImage.TYPE_INT_ARGB; + + this.size = AccessTools.getSizeOfDataBufferInBytes(this.dataBuffer); + } + + @Override + public byte getValue(int index) { + int channel = index / planeSize; + int xyIndex = index % planeSize; + + int pixel = canUseDataBuffer ? + dataBuffer.getElem(0, xyIndex) : + image.getRGB(xyIndex % width, xyIndex / width); + + return switch (channel) { + case 0 -> (byte) ColorTools.red(pixel); + case 1 -> (byte) ColorTools.green(pixel); + case 2 -> (byte) ColorTools.blue(pixel); + case 3 -> { + if (alphaProvided) { + yield (byte) ColorTools.alpha(pixel); + } else { + yield (byte) 255; + } + } + default -> throw new IllegalArgumentException(String.format("The provided index %d is out of bounds", index)); + }; + } + + @Override + public void setValue(int index, byte value) { + throw new UnsupportedOperationException("This access is not mutable"); + } + + @Override + public int getSizeBytes() { + return size; + } + + @Override + public boolean isValid() { + return true; + } +} From 277ccc1c523049826e47f3e52dffb0dac1b1122e Mon Sep 17 00:00:00 2001 From: lleplat Date: Fri, 28 Nov 2025 14:32:15 +0000 Subject: [PATCH 2/4] Remove alpha component --- src/main/java/qupath/ext/imglib2/ImgBuilder.java | 8 ++++---- .../imglib2/accesses/ByteBufferedImageAccess.java | 14 ++------------ 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/main/java/qupath/ext/imglib2/ImgBuilder.java b/src/main/java/qupath/ext/imglib2/ImgBuilder.java index ece62a5..8885d9d 100644 --- a/src/main/java/qupath/ext/imglib2/ImgBuilder.java +++ b/src/main/java/qupath/ext/imglib2/ImgBuilder.java @@ -103,7 +103,7 @@ private ImgBuilder(ImageServer server, T type, Function createBuilder(ImageServer server) { + public static ImgBuilder, ?> createBuilder(ImageServer server) { if (server.isRGB()) { return new ImgBuilder<>(server, new ARGBType(), ArgbBufferedImageAccess::new, 1); } else { @@ -267,9 +267,9 @@ private ImgBuilder(ImageServer server, T type, Function createRealBuilder(ImageServer server) { + public static ImgBuilder, ?> createRealBuilder(ImageServer server) { if (server.isRGB()) { - return new ImgBuilder<>(server, new ARGBType(), ByteBufferedImageAccess::new, 4); + return new ImgBuilder<>(server, new UnsignedByteType(), ByteBufferedImageAccess::new, 3); } else { return switch (server.getPixelType()) { case UINT8 -> new ImgBuilder<>( @@ -383,7 +383,7 @@ private ImgBuilder(ImageServer server, T type, Function(server, type, ByteBufferedImageAccess::new, 4); + return new ImgBuilder<>(server, type, ByteBufferedImageAccess::new, 3); } else { return switch (server.getPixelType()) { case UINT8, INT8 -> new ImgBuilder<>( diff --git a/src/main/java/qupath/ext/imglib2/accesses/ByteBufferedImageAccess.java b/src/main/java/qupath/ext/imglib2/accesses/ByteBufferedImageAccess.java index c702b97..67d33d7 100644 --- a/src/main/java/qupath/ext/imglib2/accesses/ByteBufferedImageAccess.java +++ b/src/main/java/qupath/ext/imglib2/accesses/ByteBufferedImageAccess.java @@ -11,10 +11,9 @@ import java.awt.image.SinglePixelPackedSampleModel; /** - * An {@link ByteAccess} whose elements are computed from an (A)RGB {@link BufferedImage}. + * An {@link ByteAccess} whose elements are computed from an RGB {@link BufferedImage}. *

      - * If the alpha component is not provided (e.g. if the {@link BufferedImage} has the {@link BufferedImage#TYPE_INT_RGB} type), - * then the alpha component of each pixel is considered to be 255. + * The alpha component is not taken into account. *

      * This {@link ByteAccess} is immutable; any attempt to changes its values will result in a * {@link UnsupportedOperationException}. @@ -28,7 +27,6 @@ public class ByteBufferedImageAccess implements ByteAccess, SizableDataAccess, V private final int width; private final int planeSize; private final boolean canUseDataBuffer; - private final boolean alphaProvided; private final int size; /** @@ -46,7 +44,6 @@ public ByteBufferedImageAccess(BufferedImage image) { this.canUseDataBuffer = image.getRaster().getDataBuffer() instanceof DataBufferInt && image.getRaster().getSampleModel() instanceof SinglePixelPackedSampleModel; - this.alphaProvided = image.getType() == BufferedImage.TYPE_INT_ARGB; this.size = AccessTools.getSizeOfDataBufferInBytes(this.dataBuffer); } @@ -64,13 +61,6 @@ public byte getValue(int index) { case 0 -> (byte) ColorTools.red(pixel); case 1 -> (byte) ColorTools.green(pixel); case 2 -> (byte) ColorTools.blue(pixel); - case 3 -> { - if (alphaProvided) { - yield (byte) ColorTools.alpha(pixel); - } else { - yield (byte) 255; - } - } default -> throw new IllegalArgumentException(String.format("The provided index %d is out of bounds", index)); }; } From f81de53ea09fb40149f8cea219f4b5ebd257decc Mon Sep 17 00:00:00 2001 From: lleplat Date: Fri, 28 Nov 2025 14:58:24 +0000 Subject: [PATCH 3/4] Remove duplicate code --- .../java/qupath/ext/imglib2/ImgBuilder.java | 262 ++++-------------- 1 file changed, 58 insertions(+), 204 deletions(-) diff --git a/src/main/java/qupath/ext/imglib2/ImgBuilder.java b/src/main/java/qupath/ext/imglib2/ImgBuilder.java index 8885d9d..472925d 100644 --- a/src/main/java/qupath/ext/imglib2/ImgBuilder.java +++ b/src/main/java/qupath/ext/imglib2/ImgBuilder.java @@ -107,56 +107,7 @@ private ImgBuilder(ImageServer server, T type, Function(server, new ARGBType(), ArgbBufferedImageAccess::new, 1); } else { - return switch (server.getPixelType()) { - case UINT8 -> new ImgBuilder<>( - server, - new UnsignedByteType(), - image -> new ByteRasterAccess(image.getRaster()), - server.nChannels() - ); - case INT8 -> new ImgBuilder<>( - server, - new ByteType(), - image -> new ByteRasterAccess(image.getRaster()), - server.nChannels() - ); - case UINT16 -> new ImgBuilder<>( - server, - new UnsignedShortType(), - image -> new ShortRasterAccess(image.getRaster()), - server.nChannels() - ); - case INT16 -> new ImgBuilder<>( - server, - new ShortType(), - image -> new ShortRasterAccess(image.getRaster()), - server.nChannels() - ); - case UINT32 -> new ImgBuilder<>( - server, - new UnsignedIntType(), - image -> new IntRasterAccess(image.getRaster()), - server.nChannels() - ); - case INT32 -> new ImgBuilder<>( - server, - new IntType(), - image -> new IntRasterAccess(image.getRaster()), - server.nChannels() - ); - case FLOAT32 -> new ImgBuilder<>( - server, - new FloatType(), - image -> new FloatRasterAccess(image.getRaster()), - server.nChannels() - ); - case FLOAT64 -> new ImgBuilder<>( - server, - new DoubleType(), - image -> new DoubleRasterAccess(image.getRaster()), - server.nChannels() - ); - }; + return createRealBuilderFromNonRgbServer(server); } } @@ -271,56 +222,7 @@ private ImgBuilder(ImageServer server, T type, Function(server, new UnsignedByteType(), ByteBufferedImageAccess::new, 3); } else { - return switch (server.getPixelType()) { - case UINT8 -> new ImgBuilder<>( - server, - new UnsignedByteType(), - image -> new ByteRasterAccess(image.getRaster()), - server.nChannels() - ); - case INT8 -> new ImgBuilder<>( - server, - new ByteType(), - image -> new ByteRasterAccess(image.getRaster()), - server.nChannels() - ); - case UINT16 -> new ImgBuilder<>( - server, - new UnsignedShortType(), - image -> new ShortRasterAccess(image.getRaster()), - server.nChannels() - ); - case INT16 -> new ImgBuilder<>( - server, - new ShortType(), - image -> new ShortRasterAccess(image.getRaster()), - server.nChannels() - ); - case UINT32 -> new ImgBuilder<>( - server, - new UnsignedIntType(), - image -> new IntRasterAccess(image.getRaster()), - server.nChannels() - ); - case INT32 -> new ImgBuilder<>( - server, - new IntType(), - image -> new IntRasterAccess(image.getRaster()), - server.nChannels() - ); - case FLOAT32 -> new ImgBuilder<>( - server, - new FloatType(), - image -> new FloatRasterAccess(image.getRaster()), - server.nChannels() - ); - case FLOAT64 -> new ImgBuilder<>( - server, - new DoubleType(), - image -> new DoubleRasterAccess(image.getRaster()), - server.nChannels() - ); - }; + return createRealBuilderFromNonRgbServer(server); } } @@ -331,45 +233,9 @@ private ImgBuilder(ImageServer server, T type, Function *

    • * If the input image is {@link ImageServer#isRGB() RGB}, the type must be {@link UnsignedByteType}. Images - * created by the returned builder will have 4 channels (RGBA). - *
    • - *
    • - * Else: - *
        - *
      • - * If the input image has the {@link PixelType#UINT8} {@link ImageServer#getPixelType() pixel type}, - * the type must be {@link UnsignedByteType}. - *
      • - *
      • - * If the input image has the {@link PixelType#INT8} {@link ImageServer#getPixelType() pixel type}, - * the type must be {@link ByteType}. - *
      • - *
      • - * If the input image has the {@link PixelType#UINT16} {@link ImageServer#getPixelType() pixel type}, - * the type must be {@link UnsignedShortType}. - *
      • - *
      • - * If the input image has the {@link PixelType#INT16} {@link ImageServer#getPixelType() pixel type}, - * the type must be {@link ShortType}. - *
      • - *
      • - * If the input image has the {@link PixelType#UINT32} {@link ImageServer#getPixelType() pixel type}, - * the type must be {@link UnsignedIntType}. - *
      • - *
      • - * If the input image has the {@link PixelType#INT32} {@link ImageServer#getPixelType() pixel type}, - * the type must be {@link IntType}. - *
      • - *
      • - * If the input image has the {@link PixelType#FLOAT32} {@link ImageServer#getPixelType() pixel type}, - * the type must be {@link FloatType}. - *
      • - *
      • - * If the input image has the {@link PixelType#FLOAT64} {@link ImageServer#getPixelType() pixel type}, - * the type must be {@link DoubleType}. - *
      • - *
      + * created by the returned builder will have 3 channels (RGB). *
    • + *
    • Else, see {@link #createBuilder(ImageServer, NativeType)}.
    • *
    * * @param server the input image @@ -566,6 +432,59 @@ public RandomAccessibleInterval buildForDownsample(double downsample) { } } + private static ImgBuilder, ?> createRealBuilderFromNonRgbServer(ImageServer server) { + return switch (server.getPixelType()) { + case UINT8 -> new ImgBuilder<>( + server, + new UnsignedByteType(), + image -> new ByteRasterAccess(image.getRaster()), + server.nChannels() + ); + case INT8 -> new ImgBuilder<>( + server, + new ByteType(), + image -> new ByteRasterAccess(image.getRaster()), + server.nChannels() + ); + case UINT16 -> new ImgBuilder<>( + server, + new UnsignedShortType(), + image -> new ShortRasterAccess(image.getRaster()), + server.nChannels() + ); + case INT16 -> new ImgBuilder<>( + server, + new ShortType(), + image -> new ShortRasterAccess(image.getRaster()), + server.nChannels() + ); + case UINT32 -> new ImgBuilder<>( + server, + new UnsignedIntType(), + image -> new IntRasterAccess(image.getRaster()), + server.nChannels() + ); + case INT32 -> new ImgBuilder<>( + server, + new IntType(), + image -> new IntRasterAccess(image.getRaster()), + server.nChannels() + ); + case FLOAT32 -> new ImgBuilder<>( + server, + new FloatType(), + image -> new FloatRasterAccess(image.getRaster()), + server.nChannels() + ); + case FLOAT64 -> new ImgBuilder<>( + server, + new DoubleType(), + image -> new DoubleRasterAccess(image.getRaster()), + server.nChannels() + ); + }; + } + private static void checkType(ImageServer server, T type) { if (server.isRGB()) { if (!(type instanceof ARGBType)) { @@ -653,72 +572,7 @@ private static void checkRealType(ImageServer server, T type) { )); } } else { - switch (server.getPixelType()) { - case UINT8 -> { - if (!(type instanceof UnsignedByteType)) { - throw new IllegalArgumentException(String.format( - "The provided type %s is not a UnsignedByteType, which is the one expected for non-RGB UINT8 images", - type.getClass() - )); - } - } - case INT8 -> { - if (!(type instanceof ByteType)) { - throw new IllegalArgumentException(String.format( - "The provided type %s is not a ByteType, which is the one expected for non-RGB INT8 images", - type.getClass() - )); - } - } - 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.getClass() - )); - } - } - 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.getClass() - )); - } - } - 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.getClass() - )); - } - } - 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.getClass() - )); - } - } - 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.getClass() - )); - } - } - 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.getClass() - )); - } - } - } + checkType(server, type); } } From 65c1343b5ae26969b486cd394d2fa7d96a1e1f23 Mon Sep 17 00:00:00 2001 From: lleplat Date: Fri, 28 Nov 2025 15:02:44 +0000 Subject: [PATCH 4/4] Change order of types --- src/main/java/qupath/ext/imglib2/ImgBuilder.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/qupath/ext/imglib2/ImgBuilder.java b/src/main/java/qupath/ext/imglib2/ImgBuilder.java index 472925d..a71ef18 100644 --- a/src/main/java/qupath/ext/imglib2/ImgBuilder.java +++ b/src/main/java/qupath/ext/imglib2/ImgBuilder.java @@ -41,14 +41,16 @@ /** * 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. + * Use {@link #createBuilder(ImageServer)}, {@link #createBuilder(ImageServer, NumericType)}, + * {@link #createRealBuilder(ImageServer)} or {@link #createRealBuilder(ImageServer, RealType)} 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> { +public class ImgBuilder & NativeType, A extends SizableDataAccess> { /** * The index of the X axis of accessibles returned by functions of this class @@ -96,7 +98,7 @@ 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 #createBuilder(ImageServer, NativeType)} instead. + * returned accessibles of this class. It is recommended to use {@link #createBuilder(ImageServer, NumericType)} instead. * See also this function to know which pixel type is used. * * @param server the input image @@ -166,7 +168,7 @@ private ImgBuilder(ImageServer server, T type, Function & NumericType> ImgBuilder createBuilder(ImageServer server, T type) { + public static & NativeType> ImgBuilder createBuilder(ImageServer server, T type) { checkType(server, type); if (server.isRGB()) { @@ -211,7 +213,7 @@ 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 #createRealBuilder(ImageServer, NativeType)} + * returned accessibles of this class. It is recommended to use {@link #createRealBuilder(ImageServer, RealType)} * instead. See also this function to know which pixel type is used. * * @param server the input image @@ -235,7 +237,7 @@ private ImgBuilder(ImageServer server, T type, Function - *

  • Else, see {@link #createBuilder(ImageServer, NativeType)}.
  • + *
  • Else, see {@link #createBuilder(ImageServer, NumericType)}.
  • *
* * @param server the input image @@ -245,7 +247,7 @@ private ImgBuilder(ImageServer server, T type, Function & RealType> ImgBuilder createRealBuilder(ImageServer server, T type) { + public static & NativeType> ImgBuilder createRealBuilder(ImageServer server, T type) { checkRealType(server, type); if (server.isRGB()) {