package ie.dcu.segment; import ie.dcu.matrix.ByteMatrix; //import ie.dcu.segment.annotate.Annotation; import ie.dcu.segment.annotate.AnnotationManager; //import ie.dcu.segment.annotate.AnnotationType; import ie.dcu.apps.ist.labelling.OntologyTerm; import java.io.*; import org.eclipse.swt.*; import org.eclipse.swt.graphics.*; /** * Class that represents a segmentation mask. * * A segmentation mask is a two dimensional matrix of byte values. It is used * to represents which pixels in an image that are part of the foreground, and * which are part of the background. * * @author Kevin McGuinness */ public class SegmentationMask extends ByteMatrix{ /** * Serialization UID */ private static final long serialVersionUID = 4110412128756091726L; /** * Constant value for an unknown pixel. */ public static final byte UNKNOWN = 0; /** * Constant value for a foreground pixel. */ public static final byte FOREGROUND = 1; /** * Constant value of a background pixel. */ public static final byte BACKGROUND = 2; /** * The width of the segmentation mask. */ public final int width; /** * The height of the segmentation mask. */ public final int height; // Annotations associated with Mask objects private AnnotationManager annotations; //The total number of layers formed. public static int numberOfLayers; //The layering number of the segmentation mask. public int layerNumber; //The color of the mask public String maskColor; @Deprecated //The name(label) associated with each segment. - replaced with Term object public String segmentName; @Deprecated // accession id associated with the segmentName - replaced with Term object public String accessionId; // holds the accessionId and name of the ontology term associated with this segment public OntologyTerm ontologyTerm; //The maskImage each segment. public Image maskImage; //The maskImageData each segment. public ImageData maskImageData; //Whether the segment needs to be enabled(highlighted) or not ? public boolean enabled; /** * Create a segmentation mask using the specified dimensions. All pixels in * the mask are initially set to {@link SegmentationMask#UNKNOWN}. * * @param width * The width. * @param height * The height. */ public SegmentationMask(int width, int height) { super(height, width); this.width = width; this.height = height; this.layerNumber = ++numberOfLayers; this.segmentName = ""; this.ontologyTerm = new OntologyTerm(); this.ontologyTerm.setName(""); this.enabled = false; clear(); } /** * Create a segmentation mask using the specified dimensions. All pixels in * the mask are initially set to {@link SegmentationMask#UNKNOWN}. * * @param bounds * The bounds, only the width and height are used. */ public SegmentationMask(Rectangle bounds) { this(bounds.width, bounds.height); } /** * Set all pixels in the mask to UNKNOWN. */ public final void clear() { fill(UNKNOWN); } /** * Get the offset of pixel (x, y). * * @param x * The x-coordinate. * @param y * The y-coordinate. * @return The offset of pixel at (x, y). */ public final int offsetOfPixel(int x, int y) { return x + y * width; } /** * Set the pixel at (x,y) to the given type. The type must be one of: * *
*
* @param x
* The x-coordinate.
* @param y
* The y-coordinate.
* @param type
* The type.
*/
public final void setPixel(int x, int y, byte type) {
values[x + y * width] = type;
}
/**
* Get the type of pixel at position (x, y).
*
* @see SegmentationMask#setPixel(int, int, byte)
*
* @param x
* The x-coordinate.
* @param y
* The y-coordinate.
* @return The type of pixel.
*/
public final byte getPixel(int x, int y) {
return values[x + y * width];
}
/**
* Returns true if the mask doesn't contain any foreground
* or background pixels (All pixels are UNKNOWN)
*
* @return true for an unknown mask.
*/
public final boolean isUnknown() {
for (int i = 0; i < size; i++) {
switch (values[i]) {
case FOREGROUND:
case BACKGROUND:
return false;
}
}
return true;
}
/**
* Check if the image contains valid pixel values.
*
* @return true if the image contains valid pixel values.
*/
public final boolean check() {
for (int i = 0; i < size; i++) {
switch (values[i]) {
case UNKNOWN:
case FOREGROUND:
case BACKGROUND:
continue;
default:
return false;
}
}
return true;
}
/**
* Get the bounds of the mask.
*
* @return The bounds.
*/
public final Rectangle getBounds() {
return new Rectangle(0, 0, width, height);
}
public void setAnnotations(AnnotationManager annotations) {
this.annotations = annotations;
}
public AnnotationManager getAnnotations() {
if (annotations == null) {
annotations = new AnnotationManager();
}
return annotations;
}
/**
* Returns a string representation of the mask.
*/
public String toString() {
return String.format("SegmentationMask{ %d, %d }", width, height);
}
/**
* Save the segmentation mask as a PNG image.
*
* @param file
* The file to save it to.
* @throws IOException
* If the mask cannot be saved for some reason.
*/
public void save(File file) throws IOException {
// Check if file is a directory
if (file.isDirectory()) {
throw new FileNotFoundException(String.format("%s is a directory", file));
}
// Check if we can write to file
if (file.exists()) {
if (!file.canWrite()) {
throw new IOException(String.format("Cannot write to %s", file));
}
}
// Check pixels are ok
if (!check()) {
throw new IOException("Image contains invalid pixels, cannot save");
}
// Create image loader
ImageLoader loader = new ImageLoader();
// Set loader data
loader.data = new ImageData[] { createImageData() };
try {
// Save image
loader.save(file.getAbsolutePath(), SWT.IMAGE_PNG);
} catch (SWTException e) {
// Change unchecked exception to checked one
throw new IOException("SWT Exception: " + e.getMessage());
}
// Done
return;
}
/**
* Store the segmentation mask to an output stream as a PNG image.
*
* @param out
* The output stream.
* @throws IOException
* If an error occurs while saving.
*/
public void save(OutputStream out) throws IOException {
// Check pixels are ok
if (!check()) {
throw new IOException("Image contains invalid pixels, cannot save");
}
// Create image loader
ImageLoader loader = new ImageLoader();
// Set loader data
loader.data = new ImageData[] { createImageData() };
try {
// Save image
loader.save(out, SWT.IMAGE_PNG);
} catch (SWTException e) {
// Change unchecked exception to checked one
throw new IOException("SWT Exception: " + e.getMessage());
}
}
/**
* Create an SWT ImageData object from the mask.
*/
private ImageData createImageData() {
// Create palette
PaletteData palette = getPalette();
// Create 8 bit indexed image
ImageData data = new ImageData(width, height, 8, palette);
// Blit pixels
for (int y = 0; y < data.height; y++) {
int off1 = y * width;
int off2 = y * data.bytesPerLine;
System.arraycopy(this.values, off1, data.data, off2, width);
}
return data;
}
/**
* Load the mask from a PNG image.
*
* @param file
* The file to load from.
* @throws IOException
* If the mask cannot be loaded for some reason.
*/
public void load(File file) throws IOException {
load(loadImageData(file));
}
public Image getImage() {
return this.maskImage;
}
public ImageData getImageData() {
return this.maskImageData;
}
/**
* Load the mask from a PNG image.
*
* @param file
* The file to load from.
* @return The mask
* @throws IOException
* If the mask cannot be loaded for some reason.
*/
public static SegmentationMask read(File file) throws IOException {
// Load image data
ImageData data = loadImageData(file);
// Create compatible mask
SegmentationMask mask = new SegmentationMask(data.width, data.height);
// Load data into the mask
mask.load(data);
// Done
return mask;
}
/**
* Load the mask from a stream containing a PNG image.
*
* @param in
* The stream.
* @return The mask
* @throws IOException
* If the mask cannot be loaded for some reason.
*/
public static SegmentationMask read(InputStream in) throws IOException {
// Load image data
ImageData data = loadImageData(in);
// Create compatible mask
SegmentationMask mask = new SegmentationMask(data.width, data.height);
// Load data into the mask
mask.load(data);
// Done
return mask;
}
public static SegmentationMask read(InputStream in,int layerNumber) throws IOException {
// Load image data
ImageData data = loadImageData(in);
// Create compatible mask
SegmentationMask mask = new SegmentationMask(data.width, data.height);
// Load data into the mask
mask.load(data);
// Done
return mask;
}
private static ImageData loadImageData(File file) throws IOException {
// Ensure file exists
if (!file.exists()) {
throw new FileNotFoundException(String.format("%s does not exist", file));
}
ImageLoader loader = new ImageLoader();
// Load pixels
ImageData[] images;
try {
images = loader.load(file.getAbsolutePath());
} catch (SWTException e) {
// Change unchecked exception to checked one
throw new IOException("SWTException: " + e.getMessage());
}
// Ensure we have at least one image
if (images.length < 1) {
throw new IOException(String.format("No images found in %s", file));
}
// Grab image data
return images[0];
}
private static ImageData loadImageData(InputStream in) throws IOException {
ImageLoader loader = new ImageLoader();
// Load pixels
ImageData[] images;
try {
images = loader.load(in);
} catch (SWTException e) {
// Change unchecked exception to checked one
throw new IOException("SWTException: " + e.getMessage());
}
// Ensure we have at least one image
if (images.length < 1) {
throw new IOException(String.format("No images found in stream"));
}
// Grab image data
return images[0];
}
private void load(ImageData data) throws IOException {
// Check dimensions
if (width != data.width || height != data.height) {
throw new IOException(
String.format("Invalid dimensions (%d,%d)", data.width, data.height));
}
// Check if we have the original format
if (data.palette.isDirect || data.depth != 8) {
// Okay we definitely don't, do it the slow way
paletteTranslateLoad(data);
} else {
// Assume that we do have the original pixels and copy pixels
blitLoad(data);
// Test our assumption
if (!check()) {
// We were wrong
paletteTranslateLoad(data);
}
}
// Check pixels are valid
if (!check()) {
throw new IOException("Image contains invalid pixels");
}
}
private void blitLoad(ImageData data) {
for (int y = 0; y < data.height; y++) {
int off1 = y * data.bytesPerLine;
int off2 = y * width;
System.arraycopy(data.data, off1, this.values, off2, width);
}
}
private void paletteTranslateLoad(ImageData data) {
PaletteData palette = getPalette();
for (int y = 0; y < height; y++) {
int yoff = y * width;
for (int x = 0; x < width; x++) {
// Palette translation
int pel = data.getPixel(x, y);
RGB rgb = data.palette.getRGB(pel);
// Check for valid rgb, use background pixel on fail
int val = BACKGROUND;
for (int i = 0; i < palette.colors.length; i++) {
if (palette.colors[i].equals(rgb)) {
val = i;
break;
}
}
// Set
this.values[x+yoff] = (byte) val;
}
}
}
/*public void load(InputStream is) throws IOException {
// In case container is being reused, clear old data first
annotations.clear();
redos.clear();
// Create and buffer data input stream
DataInputStream in = new DataInputStream(
new BufferedInputStream(is)
);
// Check tag
String tag = in.readUTF();
if (!tag.equals(FILE_TAG)) {
throw new IOException("Unrecognized file format");
}
// Read annotations
int n = in.readInt();
for (int i = 0; i < n; i++) {
// Read parameters
int typeid = in.readInt();
int lineWidth = in.readInt();
int npoints = in.readInt();
// Create annotation
AnnotationType type = AnnotationType.valueOf(typeid);
Annotation a = new Annotation(type, lineWidth);
// Read points
for (int j = 0; j < npoints; j++) {
int x = in.readInt();
int y = in.readInt();
a.add(new Point(x, y));
}
//Add annotation
add(a);
}
}*/
private PaletteData getPalette() {
RGB[] colors = new RGB[] {
new RGB(128,128,128),
new RGB(255, 255, 255),
new RGB(0,0,0)
};
// Create palette
return new PaletteData(colors);
}
}