package edu.stanford.rsl.tutorial.RotationalAngiography.ResidualMotionCompensation.morphology; import ij.*; import ij.process.*; import java.lang.*; import java.io.*; import java.util.Arrays; /** * StructElement. Implements a flat Structuring Element for mathematical morphology. * Used in conjunction with utility methods in org.ajdecon.morphology.Morphology. * Uses the ImageJ ImagePlus as an image representation object. * * For more information on mathematical morphology: http://en.wikipedia.org/wiki/Mathematical_morphology * For ImageJ: http://rsbweb.nih.gov/ij/ * @author Adam DeConinck @version 0.1 * */ public class StructuringElement{ /* * Constants and properties. */ public static final String CIRCLE = "circle"; public static final String SQUARE = "square"; public static final String RECT = "rect"; public static final String RING = "ring"; public static final String LINE = "line"; private int bgValue = 255; private ImagePlus contents; private ObjectWindow object; /* * Constructors! */ /** * * Structuring element with arbitrary shape: constructor takes ImagePlus which defines neighborhood with * black and white pixels. * @param im ImagePlus defining the structuring element. Auto-thresholded so only maximum pixels (255) are seen as white. * @param bgWhite Defines whether the background is white (true) or black (false). * @return A StructElement based on the structure. * */ public StructuringElement(ImagePlus im, boolean bgWhite) { setBgWhite(bgWhite); try { // Ensure we have an image with odd width and height. if ( (im.getWidth() % 2 == 0) || (im.getHeight() % 2 == 0) ) { throw new Exception("Structuring elements must have odd-numbered height and width!"); } // Make our ImagePlus contents an 8-bit image. contents = new ImagePlus("structuring element", im.getProcessor()); ImageConverter ic = new ImageConverter(contents); ic.convertToGray8(); // Then threshold so only 255 pixels are white. This // should already be the case, but let's make sure, shall we? contents.getProcessor().threshold(254); } catch(Exception e) { e.printStackTrace(); } } /** * When one size is supplied, StructElement can be a StructElement.CIRCLE or a * StructElement.SQUARE. * @param name Defines the type of structuring element. @param size The radius of a circle, or the width of a square. @param bgWhite Defines whether the background is white (true) or black (false). @return Square or Circle StructElement. * */ public StructuringElement(String name, int size, boolean bgWhite) { setBgWhite(bgWhite); try { if (name.equalsIgnoreCase(CIRCLE)) { contents = makeCircle(size); } else if (name.equalsIgnoreCase(SQUARE)) { if (size % 2 == 0) { throw new Exception("Size of structuring element must be an odd number of pixels!"); } contents = makeRect(size, size); } else { throw new Exception("Unknown type of structuring element!"); } } catch(Exception e) { e.printStackTrace(); } } /** * With two sizes, produces a rectangular, ring-shaped or line-shaped structuring element. * @param name Defines the type of structuring element. @param size1 Defines the width of a rectangle, the inner radius of a ring or the length of a line. @param size2 Defines the height of a rectangle, the outer radius of a ring or the angle of a line in degrees. @param bgWhite Defines whether the background is white (true) or black (false). @return A rectangular, ring-shaped or line-shaped structuring element. * */ public StructuringElement(String name, int size1, int size2, boolean bgWhite) { setBgWhite(bgWhite); try { if (name.equalsIgnoreCase(RING)) { contents = makeRing(size1, size2); } else if (name.equalsIgnoreCase(RECT)) { if ( (size1 % 2 == 0) || (size2 % 2 == 0) ) { throw new Exception("Structuring elements must have odd-numbered height and width!"); } contents = makeRect(size1, size2); } else if (name.equalsIgnoreCase(LINE)) { // if (size1 % 2 == 0) { // throw new Exception("Size of structuring element must be an odd number of pixels!"); // } contents = makeLine(size1, size2); } else { throw new Exception("Unknown type of structuring element!"); } } catch(Exception e) { e.printStackTrace(); } } /* * private parts of constructors: build new images. */ private ImagePlus makeCircle(int radius) { ByteProcessor bp = new ByteProcessor(2*radius+1, 2*radius+1); int width = 2*radius+1; for (int x=-radius; x<=radius; x++) { for (int y=-radius; y<=radius; y++) { if (inRadius(radius,x,y)) { bp.set(x+radius,y+radius,0); } else { bp.set(x+radius,y+radius,255); } } } if (!(isBgWhite())) { bp.invert(); } ImagePlus result = new ImagePlus("circle structuring element", bp); return result; } private ImagePlus makeRect(int width, int height) { ByteProcessor bp = new ByteProcessor(width, height); for (int x=0; x<width; x++) { for (int y=0; y<height; y++) { bp.set(x,y,0); } } if (!(isBgWhite())) { bp.invert(); } ImagePlus result = new ImagePlus("rect structuring element", bp); return result; } private ImagePlus makeLine(int length, int angle) { double radians = Math.PI/2 + angle*Math.PI/180.0; // Produce a square ByteProcessor big enough to hold our line SE. int height = (int)Math.ceil( ((double)length) * Math.sin(radians) ); if (height % 2 == 0) { height+=1; } if (height <= length) { height=length; } else { length = height; } ByteProcessor bp = new ByteProcessor(length,height); // Initialize to background. for (int x=0;x<length;x++) { for (int y=0;y<height; y++) { bp.set(x,y,255); } } // Create straight line. for (int i=0;i<length;i++) { bp.set(i,(int)Math.floor(height/2),0); } // Rotate. bp.rotate((double)angle); // Invert if needed. if (!(isBgWhite())) { bp.invert(); } ImagePlus result = new ImagePlus("rect structuring element", bp); return result; } private ImagePlus makeRing(int inside, int outside) { ByteProcessor bp = new ByteProcessor(2*outside+1, 2*outside+1); int width = 2*outside+1; for (int x=-outside; x<=outside; x++) { for (int y=-outside; y<=outside; y++) { if (inRadius(outside,x,y)) { if (inRadius(inside,x,y)) { bp.set(x+outside,y+outside,255); } else { bp.set(x+outside,y+outside,0); } } else { bp.set(x+outside,y+outside,255); } } } if (!(isBgWhite())) { bp.invert(); } ImagePlus result = new ImagePlus("ring structuring element", bp); return result; } /* * Methods */ /** * Is the background white? */ public boolean isBgWhite() { if (bgValue==255) { return true; } return false; } /** * Set the background to white (true) or black (false). */ public void setBgWhite(boolean yes) { if (yes) { if (bgValue!=255) { if (contents!=null) { contents.getProcessor().invert(); } } bgValue=255; } else { if (bgValue!=0) { if (contents!=null) { contents.getProcessor().invert(); } } bgValue=0; } } /** * @return An ImagePlus representing the structuring element. */ public ImagePlus getImage() { return contents; } /** * @return The ImageProcessor of the internal structuring element. */ public ImageProcessor getProcessor() { return contents.getProcessor(); } /** * Store an internal representation of the object image to be operated on by * this structuring element. * @param im Image to be operated on. @param symmetric Determines if boundary conditions are symmetric or if the edges are padded with background. * */ public void setObject(ImagePlus im, boolean symmetric) { object = new ObjectWindow(im, this, bgValue, symmetric); } /** * Return a list of pixel values corresponding to the object image's contents when the structuring element * overlaps the specified coordinates. * */ public int[] window(int x0, int y0) { return object.view(x0,y0); } /** * @return The size of the structuring element's foreground in pixels. */ public int getSize() { int[][] se = contents.getProcessor().getIntArray(); int sewidth=contents.getWidth(); int seheight=contents.getHeight(); int counter=0; for (int x=0; x<sewidth; x++) { for (int y=0; y<seheight; y++) { if (se[x][y]!=bgValue) { counter += 1; } } } return counter; } /** * Print a character-based representation of the structuring element. * @param pr PrintStream to use. */ public void printStructure(PrintStream pr) { int[][] pixels = contents.getProcessor().getIntArray(); for (int y=0; y<pixels.length; y++) { for (int x=0;x<pixels[y].length; x++) { if (pixels[y][x]==bgValue) { pr.print(0); } else { pr.print(1); } pr.print(" "); } pr.println(); } } public void printStructure() { printStructure(System.out); } /* * miscellaneous functions * */ private boolean inRadius(int r, int x, int y) { double rsq = Math.pow((double)r,2); double dist = Math.pow((double)x,2) + Math.pow((double)y,2); if (dist<=rsq) { return true; } return false; } } /** * Container for the object image which can return windowed views where the structuring element overlaps. * @author Adam DeConinck @version 0.1 */ class ObjectWindow { private int[][] se; private int[][] object; int bgValue; private int sewidth, seheight, width, height, dx, dy, size; boolean symmetric; /** * Construct an ObjectWindow based on the supplied image and structuring element. * @param im ImagePlus containing the object image. @param s Structuring element used to generate the windows. @param bg Background value. @param sym Determines if boundary conditions are symmetric. */ public ObjectWindow(ImagePlus im, StructuringElement s, int bg, boolean sym) { se = s.getImage().getProcessor().getIntArray(); object = im.getProcessor().getIntArray(); bgValue = bg; sewidth=s.getImage().getWidth(); seheight=s.getImage().getHeight(); width=im.getWidth(); height=im.getHeight(); dx=sewidth/2; // int division truncates dy=seheight/2; symmetric = sym; size = s.getSize(); } /** * Produce a list of pixel values which are overlapped by the structuring element when centered at a given * coordinate. */ public int[] view(int x0, int y0) { int[] result = new int[size]; int k=0; for (int x=-dx; x<=dx; x++) { for (int y=-dx; y<=dx; y++) { // Check if we are in a SE foreground pixel. if (se[x+dx][y+dy]!=bgValue) { // Coordinates in the object image. int xc=x0+x; int yc=y0+y; // Check x boundary conditions. if (xc<0) { if (!(symmetric)) { result[k]=bgValue; k=k+1; continue; } else { xc=width+xc; } } else if (xc >= width ) { if (!(symmetric)) { result[k]=bgValue; k=k+1; continue; } else { xc=xc-width; } } // Check y boundary conditions. if (yc<0) { //System.out.print("Ymin "); //System.out.print(x0); System.out.print(" "); System.out.print(y0); //System.out.println(); if (!(symmetric)) { result[k]=bgValue; k=k+1; continue; } else { yc=height+yc; } } else if (yc >= height ) { //System.out.print("Ymax yc=");System.out.print(yc);System.out.println(); //System.out.print(x0); System.out.print(" "); System.out.print(y0); //System.out.println(); if (!(symmetric)) { result[k]=bgValue; k=k+1; continue; } else { yc=yc-height; } } // Add this pixel to the result window. result[k]=object[xc][yc]; k=k+1; } } } return result; } }