Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 196 additions & 59 deletions src/main/java/qupath/ext/imglib2/ImgBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -39,14 +41,16 @@
/**
* A class to create {@link Img} or {@link RandomAccessibleInterval} from an {@link ImageServer}.
* <p>
* 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.
* <p>
* This class is thread-safe.
*
* @param <T> the type of the returned accessibles
* @param <A> the type contained in the input image
*/
public class ImgBuilder<T extends NativeType<T> & NumericType<T>, A extends SizableDataAccess> {
public class ImgBuilder<T extends NumericType<T> & NativeType<T>, A extends SizableDataAccess> {

/**
* The index of the X axis of accessibles returned by functions of this class
Expand Down Expand Up @@ -79,13 +83,13 @@ public class ImgBuilder<T extends NativeType<T> & NumericType<T>, A extends Siza
private final T type;
private CellCache cellCache = DEFAULT_CELL_CACHE;

private ImgBuilder(ImageServer<BufferedImage> server, T type, Function<BufferedImage, A> cellCreator) {
private ImgBuilder(ImageServer<BufferedImage> server, T type, Function<BufferedImage, A> 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;
}
Expand All @@ -94,58 +98,18 @@ private ImgBuilder(ImageServer<BufferedImage> server, T type, Function<BufferedI
* Create a builder from an {@link ImageServer}. This doesn't create any accessibles yet.
* <p>
* 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
* @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<BufferedImage> server) {
public static ImgBuilder<? extends NumericType<?>, ?> createBuilder(ImageServer<BufferedImage> 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())
);
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())
);
};
return createRealBuilderFromNonRgbServer(server);
}
}

Expand All @@ -154,7 +118,10 @@ private ImgBuilder(ImageServer<BufferedImage> server, T type, Function<BufferedI
* <p>
* The provided type must be compatible with the input image:
* <ul>
* <li>If the input image is {@link ImageServer#isRGB() RGB}, the type must be {@link ARGBType}.</li>
* <li>
* 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.
* </li>
* <li>
* Else:
* <ul>
Expand Down Expand Up @@ -201,18 +168,122 @@ private ImgBuilder(ImageServer<BufferedImage> server, T type, Function<BufferedI
* @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 <T extends NativeType<T> & NumericType<T>> ImgBuilder<T, ?> createBuilder(ImageServer<BufferedImage> server, T type) {
public static <T extends NumericType<T> & NativeType<T>> ImgBuilder<T, ?> createBuilder(ImageServer<BufferedImage> server, T type) {
checkType(server, type);

if (server.isRGB()) {
return new ImgBuilder<>(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()));
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()
);
};
}
}

/**
* Create a builder from an {@link ImageServer}. This doesn't create any accessibles yet.
* <p>
* 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, RealType)}
* 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<? extends RealType<?>, ?> createRealBuilder(ImageServer<BufferedImage> server) {
if (server.isRGB()) {
return new ImgBuilder<>(server, new UnsignedByteType(), ByteBufferedImageAccess::new, 3);
} else {
return createRealBuilderFromNonRgbServer(server);
}
}

/**
* Create a builder from an {@link ImageServer}. This doesn't create any accessibles yet.
* <p>
* The provided type must be compatible with the input image:
* <ul>
* <li>
* If the input image is {@link ImageServer#isRGB() RGB}, the type must be {@link UnsignedByteType}. Images
* created by the returned builder will have 3 channels (RGB).
* </li>
* <li>Else, see {@link #createBuilder(ImageServer, NumericType)}.</li>
* </ul>
*
* @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 <T> 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 <T extends RealType<T> & NativeType<T>> ImgBuilder<T, ?> createRealBuilder(ImageServer<BufferedImage> server, T type) {
checkRealType(server, type);

if (server.isRGB()) {
return new ImgBuilder<>(server, type, ByteBufferedImageAccess::new, 3);
} 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()
);
};
}
}
Expand Down Expand Up @@ -363,6 +434,59 @@ public RandomAccessibleInterval<T> buildForDownsample(double downsample) {
}
}

private static ImgBuilder<? extends RealType<?>, ?> createRealBuilderFromNonRgbServer(ImageServer<BufferedImage> 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 <T> void checkType(ImageServer<?> server, T type) {
if (server.isRGB()) {
if (!(type instanceof ARGBType)) {
Expand All @@ -376,15 +500,15 @@ private static <T> 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 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()
));
}
Expand Down Expand Up @@ -441,6 +565,19 @@ private static <T> void checkType(ImageServer<?> server, T type) {
}
}

private static <T> 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 {
checkType(server, type);
}
}

private Cell<A> createCell(TileRequest tile) {
BufferedImage image;
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading