Skip to content
Open
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
9 changes: 4 additions & 5 deletions src/main/java/qupath/ext/imglib2/AccessibleScaler.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import net.imglib2.realtransform.RealViews;
import net.imglib2.realtransform.Scale2D;
import net.imglib2.realtransform.Translation2D;
import net.imglib2.type.NativeType;
import net.imglib2.type.numeric.NumericType;
import net.imglib2.view.Views;

Expand Down Expand Up @@ -40,7 +39,7 @@ private AccessibleScaler() {
* @throws IllegalArgumentException if the input interval has at least one minimum different from 0, if the provided scale is less
* than or equal to 0, or if the input interval has less than two dimensions
*/
public static <T extends NativeType<T> & NumericType<T>> RandomAccessibleInterval<T> scaleWithLinearInterpolation(
public static <T extends NumericType<T>> RandomAccessibleInterval<T> scaleWithLinearInterpolation(
RandomAccessibleInterval<T> input,
double scale
) {
Expand All @@ -59,7 +58,7 @@ public static <T extends NativeType<T> & NumericType<T>> RandomAccessibleInterva
* @throws IllegalArgumentException if the input interval has at least one minimum different from 0, if the provided scale is less
* than or equal to 0, or if the input interval has less than two dimensions
*/
public static <T extends NativeType<T> & NumericType<T>> RandomAccessibleInterval<T> scaleWithNearestNeighborInterpolation(
public static <T extends NumericType<T>> RandomAccessibleInterval<T> scaleWithNearestNeighborInterpolation(
RandomAccessibleInterval<T> input,
double scale
) {
Expand All @@ -79,7 +78,7 @@ public static <T extends NativeType<T> & NumericType<T>> RandomAccessibleInterva
* @throws IllegalArgumentException if the input interval has at least one minimum different from 0, if the provided scale is less
* than or equal to 0, or if the input interval has less than two dimensions
*/
public static <T extends NativeType<T> & NumericType<T>> RandomAccessibleInterval<T> scale(
public static <T extends NumericType<T>> RandomAccessibleInterval<T> scale(
RandomAccessibleInterval<T> input,
double scale,
InterpolatorFactory<T, RandomAccessible<T>> interpolatorFactory
Expand All @@ -104,7 +103,7 @@ public static <T extends NativeType<T> & NumericType<T>> RandomAccessibleInterva
}
}

private static <T extends NativeType<T> & NumericType<T>> RandomAccessibleInterval<T> scaleWithoutChecks(
private static <T extends NumericType<T>> RandomAccessibleInterval<T> scaleWithoutChecks(
RandomAccessibleInterval<T> input,
double scale,
Scale2D scale2D,
Expand Down
185 changes: 21 additions & 164 deletions src/main/java/qupath/ext/imglib2/ImgCreator.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
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;
Expand All @@ -38,14 +37,13 @@
/**
* A class to create {@link Img} or {@link RandomAccessibleInterval} from an {@link ImageServer}.
* <p>
* Use a {@link #builder(ImageServer)} or {@link #builder(ImageServer, NativeType)} to create an instance of this class.
* Use a {@link #builder(ImageServer)} or {@link #builder(ImageServer)} 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 ImgCreator<T extends NativeType<T> & NumericType<T>, A extends SizableDataAccess> {
public class ImgCreator<A extends SizableDataAccess> {

/**
* The index of the X axis of accessibles returned by functions of this class
Expand All @@ -72,89 +70,27 @@ public class ImgCreator<T extends NativeType<T> & NumericType<T>, A extends Siza
*/
public static final int NUMBER_OF_AXES = 5;
private final ImageServer<BufferedImage> server;
private final T type;
private final CellCache cellCache;
private final Function<BufferedImage, A> cellCreator;
private final int numberOfChannels;

private ImgCreator(Builder<T> builder, Function<BufferedImage, A> cellCreator) {
private ImgCreator(Builder builder, Function<BufferedImage, A> 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.
* <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 #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 <T> the type of the output image
*/
public static <T extends NativeType<T> & NumericType<T>> Builder<T> builder(ImageServer<BufferedImage> server) {
public static Builder builder(ImageServer<BufferedImage> server) {
// Despite the potential warning, T is necessary, otherwise a cannot infer type arguments error occurs
return new Builder<T>(server, getTypeOfServer(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 ARGBType}.</li>
* <li>
* Else:
* <ul>
* <li>
* If the input image has the {@link PixelType#UINT8} {@link ImageServer#getPixelType() pixel type},
* the type must be {@link UnsignedByteType}.
* </li>
* <li>
* If the input image has the {@link PixelType#INT8} {@link ImageServer#getPixelType() pixel type},
* the type must be {@link ByteType}.
* </li>
* <li>
* If the input image has the {@link PixelType#UINT16} {@link ImageServer#getPixelType() pixel type},
* the type must be {@link UnsignedShortType}.
* </li>
* <li>
* If the input image has the {@link PixelType#INT16} {@link ImageServer#getPixelType() pixel type},
* the type must be {@link ShortType}.
* </li>
* <li>
* If the input image has the {@link PixelType#UINT32} {@link ImageServer#getPixelType() pixel type},
* the type must be {@link UnsignedIntType}.
* </li>
* <li>
* If the input image has the {@link PixelType#INT32} {@link ImageServer#getPixelType() pixel type},
* the type must be {@link IntType}.
* </li>
* <li>
* If the input image has the {@link PixelType#FLOAT32} {@link ImageServer#getPixelType() pixel type},
* the type must be {@link FloatType}.
* </li>
* <li>
* If the input image has the {@link PixelType#FLOAT64} {@link ImageServer#getPixelType() pixel type},
* the type must be {@link DoubleType}.
* </li>
* </ul>
* </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 NativeType<T> & NumericType<T>> Builder<T> builder(ImageServer<BufferedImage> server, T type) {
return new Builder<>(server, type);
return new Builder(server);
}

/**
Expand All @@ -171,8 +107,9 @@ public static <T extends NativeType<T> & NumericType<T>> Builder<T> builder(Imag
* @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
* @param <T> Generic parameter for the image type.
*/
public Img<T> createForLevel(int level) {
public <T extends NumericType<T> & NativeType<T>> Img<T> 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",
Expand All @@ -183,16 +120,17 @@ public Img<T> createForLevel(int level) {

List<TileRequest> tiles = new ArrayList<>(server.getTileRequestManager().getTileRequestsForLevel(level));

T type = getTypeOfServer(server);
return new LazyCellImg<>(
new CellGrid(
new long[] {
new long[]{
server.getMetadata().getLevel(level).getWidth(),
server.getMetadata().getLevel(level).getHeight(),
numberOfChannels,
server.nZSlices(),
server.nTimepoints()
},
new int[] {
new int[]{
server.getMetadata().getPreferredTileWidth(),
server.getMetadata().getPreferredTileHeight(),
numberOfChannels,
Expand Down Expand Up @@ -223,41 +161,37 @@ public Img<T> createForLevel(int level) {
* @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
* @param <T> Generic parameter for the image type.
*/
public RandomAccessibleInterval<T> createForDownsample(double downsample) {
public <T extends NumericType<T> & NativeType<T>> RandomAccessibleInterval<T> 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);
Img<T> img = createForLevel(level);

if (server.getMetadata().getChannelType() == ImageServerMetadata.ChannelType.CLASSIFICATION) {
return AccessibleScaler.scaleWithNearestNeighborInterpolation(createForLevel(level), server.getDownsampleForResolution(level) / downsample);
return AccessibleScaler.scaleWithNearestNeighborInterpolation(img, server.getDownsampleForResolution(level) / downsample);
} else {
return AccessibleScaler.scaleWithLinearInterpolation(createForLevel(level), server.getDownsampleForResolution(level) / downsample);
return AccessibleScaler.scaleWithLinearInterpolation(img, server.getDownsampleForResolution(level) / downsample);
}
}

/**
* A builder to create an instance of {@link ImgCreator}.
*
* @param <T> the type of the returned accessibles of {@link ImgCreator} should have
*/
public static class Builder<T extends NativeType<T> & NumericType<T>> {
public static class Builder {

private static final CellCache defaultCellCache = new CellCache((int) (Runtime.getRuntime().maxMemory() * 0.5 / (1024 * 1024)));
private final ImageServer<BufferedImage> server;
private final T type;
private CellCache cellCache = defaultCellCache;

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

/**
Expand All @@ -268,7 +202,7 @@ private Builder(ImageServer<BufferedImage> server, T type) {
* @return this builder
* @throws NullPointerException if the provided cache is null
*/
public Builder<T> cellCache(CellCache cellCache) {
public Builder cellCache(CellCache cellCache) {
this.cellCache = Objects.requireNonNull(cellCache);
return this;
}
Expand All @@ -278,7 +212,7 @@ public Builder<T> cellCache(CellCache cellCache) {
*
* @return a new instance of {@link ImgCreator}
*/
public ImgCreator<T, ?> build() {
public ImgCreator<?> build() {
if (server.isRGB()) {
return new ImgCreator<>(this, ArgbBufferedImageAccess::new);
} else {
Expand All @@ -291,88 +225,11 @@ public Builder<T> cellCache(CellCache cellCache) {
};
}
}

private static <T> 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 <T extends NativeType<T> & NumericType<T>> T getTypeOfServer(ImageServer<?> server) {
private static <T extends NumericType<T> & NativeType<T>> T getTypeOfServer(ImageServer<?> server) {
if (server.isRGB()) {
return (T) new ARGBType();
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/qupath/ext/imglib2/ImgLib2ImageServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ private ImgLib2ImageServer(List<? extends RandomAccessibleInterval<T>> accessibl
* if the provided accessibles do not have {@link ImgCreator#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
* @return the builder
* @param <T> Generic parameter for the image type.
*/
public static <T extends NativeType<T> & NumericType<T>> Builder<T> builder(List<? extends RandomAccessibleInterval<T>> accessibles) {
return new Builder<>(accessibles);
Expand Down
Loading