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: * * * * Note: This method does not check the type for correctness. *

* * @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); } }