ImageChannelTools.java
// SPDX-FileCopyrightText: 2024 Carlo Castoldi <carlo.castoldi@outlook.com>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
package qupath.ext.braian;
import ij.CompositeImage;
import ij.ImagePlus;
import ij.process.ImageProcessor;
import ij.process.ImageStatistics;
import qupath.imagej.tools.IJTools;
import qupath.lib.images.ImageData;
import qupath.lib.images.PathImage;
import qupath.lib.images.servers.ImageChannel;
import qupath.lib.images.servers.ImageServer;
import qupath.lib.images.servers.ImageServerMetadata;
import qupath.lib.objects.hierarchy.PathObjectHierarchy;
import qupath.lib.regions.RegionRequest;
import java.awt.image.BufferedImage;
import java.io.IOException;
class IllegalChannelName extends RuntimeException {
public IllegalChannelName(String name) {
super(String.format("Cannot find a channel named '"+name+"'!"));
}
}
public class ImageChannelTools {
private final String name;
private final ImageData<BufferedImage> imageData;
private final ImageServer<BufferedImage> server;
private final int nChannel;
/**
* Creates an {@link ImageChannelTools} from the given channel name and {@link ImageServer}
* @param name name of the channel
* @param server image server to which the channel is referring to
*/
public ImageChannelTools(String name, ImageServer<BufferedImage> server) {
this.name = name;
this.imageData = null;
this.server = server;
this.nChannel = this.findNChannel();
}
/**
* Crates an {@link ImageChannelTools} from the given channel name and {@link ImageServer}
* @param name name of the channel
* @param imageData data of the image to which the channel is referring to
*/
public ImageChannelTools(String name, ImageData<BufferedImage> imageData) {
this.name = name;
this.imageData = imageData;
this.server = null;
this.nChannel = this.findNChannel();
}
private ImageServer<BufferedImage> getServer() {
if (this.server != null)
return this.server;
return this.imageData.getServer();
}
private ImageServerMetadata getMetadata() {
return this.imageData.getServerMetadata();
}
private int findNChannel() {
int nChannel = 0;
for (ImageChannel ch: this.getMetadata().getChannels()) {
if(ch.getName().equals(this.name))
return nChannel;
nChannel++;
}
throw new IllegalChannelName(this.name);
}
/**
* @return index of the corresponding channel used by QuPath
*/
public int getnChannel() {
return this.nChannel;
}
/**
* Computes the {@link ChannelHistogram} of the current channel at the given resolution.
* @param resolutionLevel Resolution level, If it's bigger than {@link ImageServer#nResolutions()}-1,
* than it uses the igven n-th resolution.
* @return the histogram of the given channel
* @throws IOException when it fails to read the image file to build the histogram
* @see #getChannelStats(int)
* @see ImageServer#getDownsampleForResolution(int)
*/
public ChannelHistogram getHistogram(int resolutionLevel) throws IOException {
return new ChannelHistogram(this.name, this.getImageProcessor(resolutionLevel));
}
/**
* Retrieves the corresponding {@link ImageProcessor} of the current channel at the given resolution.
* @param resolutionLevel Resolution level, If it's bigger than {@link ImageServer#nResolutions()}-1,
* than it uses the given n-th resolution.
* @return the image channel as processed by ImageJ at the given resolution
* @throws IOException when it fails to read the image file
* @see #getChannelStats(int)
* @see ImageServer#getDownsampleForResolution(int)
*/
public ImageProcessor getImageProcessor(int resolutionLevel) throws IOException {
ImageServer<BufferedImage> server = this.getServer();
double downsample = server.getDownsampleForResolution(Math.min(server.nResolutions()-1, resolutionLevel));
RegionRequest request = RegionRequest.createInstance(server, downsample);
PathImage<ImagePlus> pathImage = IJTools.convertToImagePlus(server, request);
ImagePlus ci = pathImage.getImage();
int ijChannel = this.nChannel+1; // ij.CompositeImage uses 1-based channels
ci.setC(ijChannel);
ImageProcessor ip = ci.getChannelProcessor();
ip = ip.duplicate();
ip.resetRoi();
return ip;
}
/**
* Computes the {@link ImageStatistics} of the current channel at the given resolution.
* @param resolutionLevel Resolution level, If it's bigger than {@link ImageServer#nResolutions()}-1,
* than it uses the given n-th resolution.
* @return the statistics of the channel computed by ImageJ at the given resolution
* @throws IOException when it fails to read the image file
* @see #getChannelStats(int)
* @see #getImageProcessor(int)
* @see ImageServer#getDownsampleForResolution(int)
*/
@Deprecated(since = "1.0.4")
public ImageStatistics getChannelStats(int resolutionLevel) throws IOException {
return this.getImageProcessor(resolutionLevel).getStats();
}
/**
* @return the name of the associated image channel
*/
public String getName() {
return this.name;
}
/**
* returns an instance of {@link ChannelDetections}, if existing in the current hierarchy
* @param hierarchy where to find the detections
* @throws NoCellContainersFoundException if no pre-computed detection was found in the given hierarchy
* @see ChannelDetections
*/
public ChannelDetections getDetections(PathObjectHierarchy hierarchy) throws NoCellContainersFoundException {
return new ChannelDetections(this, hierarchy);
}
}