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;
}
}