PixelLoader.java
package ac.essex.ooechs.imaging.commons;
import ac.essex.ooechs.imaging.commons.texture.Line;
import ac.essex.ooechs.imaging.commons.texture.Perimeter;
import ac.essex.ooechs.imaging.commons.fast.FastStatistics;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
/**
* Loads Pixels from an image file.
*
* @version 1.2 March 2008. A number of performance enhancements to make it run
* more quickly.
*/
public class PixelLoader implements Cloneable, SimpleImage {
protected BufferedImage img;
protected File file = null;
public boolean loadedOK = true;
/**
* Private constructor for the pixel loader
*/
private PixelLoader() {
loadedOK = true;
}
/**
* Load a blank PixelLoader image that can subsequently
* be manipulated.
*/
public PixelLoader(int width, int height) {
this.file = null;
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
loadedOK = true;
}
public PixelLoader(String filename) throws Exception {
this(new File(filename));
}
public PixelLoader(File imageFile) throws Exception {
// ensure that the file actually exists before proceeding
if (!imageFile.exists()) throw new Exception("File does not exist: " + imageFile.getAbsolutePath());
try {
this.file = imageFile;
// Use the Java ImageIO library to open the file as a buffered image.
img = javax.imageio.ImageIO.read(imageFile);
} catch (javax.imageio.IIOException e1) {
e1.printStackTrace();
loadedOK = false;
} catch (Exception e) {
e.printStackTrace();
loadedOK = false;
}
if (img == null) {
throw new RuntimeException("No Buffered Image in Pixel Loader. Image may not have loaded OK. Try a different file type?");
}
}
public PixelLoader(BufferedImage img) {
this.img = img;
loadedOK = true;
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
protected Perimeter perimeter1;
protected Perimeter perimeter2;
public Perimeter getPerimeter1() {
if (perimeter1 == null) {
perimeter1 = new Perimeter(2);
}
return perimeter1;
}
public Perimeter getPerimeter2() {
if (perimeter2 == null) {
perimeter2 = new Perimeter(5);
}
return perimeter2;
}
protected Line hLine1, hLine2;
protected Line vLine1, vLine2;
public Line getHLine1() {
if (hLine1 == null) {
hLine1 = new Line(2, Line.HORIZONTAL);
}
return hLine1;
}
public Line getHLine2() {
if (hLine2 == null) {
hLine2 = new Line(5, Line.HORIZONTAL);
}
return hLine2;
}
public Line getVLine1() {
if (vLine1 == null) {
vLine1 = new Line(2, Line.VERTICAL);
}
return vLine1;
}
public Line getVLine2() {
if (vLine2 == null) {
vLine2 = new Line(5, Line.VERTICAL);
}
return vLine2;
}
private int lightnessMean = -1;
private double lightnessStdDeviation = -1;
private int hueMean = -1;
private double hueStdDeviation = -1;
private int satMean = -1;
private double satStdDeviation = -1;
private double greyStdDeviation = -1;
public int getLightnessMean() {
if (lightnessMean == -1) calculateImageStatistics();
return lightnessMean;
}
public double getLightnessStdDeviation() {
if (lightnessStdDeviation == -1) calculateImageStatistics();
return lightnessStdDeviation;
}
public int getHueMean() {
if (hueMean == -1) calculateImageStatistics();
return hueMean;
}
public double getHueStdDeviation() {
if (hueStdDeviation == -1) calculateImageStatistics();
return hueStdDeviation;
}
public int getSatMean() {
if (satMean == -1) calculateImageStatistics();
return satMean;
}
public double getSatStdDeviation() {
if (satStdDeviation == -1) calculateImageStatistics();
return satStdDeviation;
}
/**
* Gets the standard deviation of greyness of the whole image.
* Optimised. Was 51ms, now 7ms.
*/
public double getStdDeviation() {
if (greyStdDeviation == -1) {
FastStatistics s = new FastStatistics();
for (int y = 0; y < getHeight(); y += 1) {
for (int x = 0; x < getWidth(); x += 1) {
s.addData(getGreyValue(x, y));
}
}
greyStdDeviation = s.getStandardDeviation();
}
return greyStdDeviation;
}
/**
* Optimised.
* Was 461ms, now 17ms (!)
*/
private void calculateImageStatistics() {
FastStatistics hueSolver = new FastStatistics();
FastStatistics saturationSolver = new FastStatistics();
FastStatistics lightnessSolver = new FastStatistics();
for (int y = 0; y < getHeight(); y += 1) {
for (int x = 0; x < getWidth(); x += 1) {
hueSolver.addData(getHue(x, y));
saturationSolver.addData(getSaturation(x, y));
lightnessSolver.addData(getLightness(x, y));
}
}
lightnessMean = (int) lightnessSolver.getMean();
lightnessStdDeviation = (int) lightnessSolver.getStandardDeviation();
hueMean = (int) hueSolver.getMean();
hueStdDeviation = (int) hueSolver.getStandardDeviation();
satMean = (int) saturationSolver.getMean();
satStdDeviation = (int) saturationSolver.getStandardDeviation();
}
/**
* Converts the image to a saturation map.
*/
public PixelLoader getSaturationMap() {
PixelLoader newImage = new PixelLoader();
newImage.file = this.file;
newImage.img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < getHeight(); y += 1) {
for (int x = 0; x < getWidth(); x += 1) {
// get the colour
Color c = getColor(x, y);
// get hue/sat/lightness
int[] hsl = ColourConvertor.RGB2HSL(c.getRed(), c.getGreen(), c.getBlue());
// get saturation
int saturation = hsl[1];
// convert back to greyscale RGB
c = new Color(saturation, saturation, saturation);
// save the pixel
newImage.setRGB(x, y, c.getRGB());
}
}
return newImage;
}
public PixelLoader getSubImage(SegmentedArea area) {
PixelLoader newImage = new PixelLoader();
newImage.file = this.file;
newImage.img = this.img.getSubimage(area.left, area.top, area.width, area.height);
return newImage;
}
/**
* Gets the buffered image which the PixelLoader is wrapping.
*/
public BufferedImage getBufferedImage() {
return img;
}
/**
* Returns statistics about a particular channel.
*/
public FastStatistics getStatistics(int channel) {
FastStatistics solver = new FastStatistics();
for (int y = 0; y < getHeight(); y += 1) {
for (int x = 0; x < getWidth(); x += 1) {
switch (channel) {
case ColourChannels.GREYSCALE:
solver.addData(getGreyValue(x, y));
break;
case ColourChannels.RED:
solver.addData(getRed(x, y));
break;
case ColourChannels.GREEN:
solver.addData(getGreen(x, y));
break;
case ColourChannels.BLUE:
solver.addData(getBlue(x, y));
break;
}
}
}
return solver;
}
/**
* Gets the file from which the image was loaded.
*/
public File getFile() {
return file;
}
private int[][] greyColourCache;
private int[][] redColourCache;
private int[][] greenColourCache;
private int[][] blueColourCache;
/**
* Gets the colour at the point X, Y, as a java.awt.Color object
*/
public Color getColor(int x, int y) {
return new Color(img.getRGB(x, y));
}
/**
* Gets the raw RGB color at the point X, Y.
*/
public int getRGB(int x, int y) {
return img.getRGB(x, y);
}
/**
* Gets the grayscale value of the pixel
*/
public int getGreyValue(int x, int y) throws RuntimeException {
// this is called many times for each pixel. Hence we'll do the maths once only.
if (greyColourCache == null) createRGBCache();
return greyColourCache[y][x];
}
public int getRed(int x, int y) throws RuntimeException {
// this is called many times for each pixel. Hence we'll do the maths once only.
if (redColourCache == null) createRGBCache();
return redColourCache[y][x];
}
public int getGreen(int x, int y) throws RuntimeException {
// this is called many times for each pixel. Hence we'll do the maths once only.
if (greenColourCache == null) createRGBCache();
return greenColourCache[y][x];
}
public int getBlue(int x, int y) throws RuntimeException {
// this is called many times for each pixel. Hence we'll do the maths once only.
if (blueColourCache == null) createRGBCache();
return blueColourCache[y][x];
}
/**
* Optimised.
* Before: 140ms on Lenna.png; after: 82 ms.
*/
private void createRGBCache() {
final double rx = 0.3;
final double gx = 0.59;
final double bx = 0.11;
greyColourCache = new int[img.getHeight()][img.getWidth()];
redColourCache = greyColourCache.clone();
greenColourCache = greyColourCache.clone();
blueColourCache = greyColourCache.clone();
for (int y = 0; y < img.getHeight(); y++) {
for (int x = 0; x < img.getWidth(); x++) {
// get a colour object, which saves us having to shift bits and other stuff.
int rgb = img.getRGB(x, y);
// extract the colours
int red = (rgb >> 16) & 0xFF;
int green = (rgb >> 8) & 0xFF;
int blue = rgb & 0xFF;
greyColourCache[y][x] = (int) ((red * rx) + (green * gx) + (blue * bx));
redColourCache[y][x] = red;
greenColourCache[y][x] = green;
blueColourCache[y][x] = blue;
}
}
}
private int[][] hueCache;
private int[][] satCache;
private int[][] lightnessCache;
public int getHue(int x, int y) {
if (hueCache == null) createHSLCache();
return hueCache[y][x];
}
public int getSaturation(int x, int y) {
if (satCache == null) createHSLCache();
return satCache[y][x];
}
public int getLightness(int x, int y) {
if (lightnessCache == null) createHSLCache();
return lightnessCache[y][x];
}
/**
* Optimised.
* Previous: 168ms; After: 141ms
*/
private void createHSLCache() {
hueCache = new int[img.getHeight()][img.getWidth()];
satCache = hueCache.clone();
lightnessCache = hueCache.clone();
for (int yPos = 0; yPos < img.getHeight(); yPos++) {
for (int xPos = 0; xPos < img.getWidth(); xPos++) {
int[] hsl = ColourConvertor.RGB2HSL(img.getRGB(xPos, yPos));
hueCache[yPos][xPos] = hsl[0];
satCache[yPos][xPos] = hsl[1];
lightnessCache[yPos][xPos] = hsl[2];
}
}
}
private ConvolutionMatrix verticalSobel;
private ConvolutionMatrix horizontalSobel;
private ConvolutionMatrix laplacian;
private int[][] vsobelCache;
private int[][] hsobelCache;
private int[][] laplacianCache;
public int getVerticalSobel(int x, int y) {
if (vsobelCache == null) {
vsobelCache = new int[getWidth()][getHeight()];
for (int dY = 0; dY < getHeight(); dY++)
for (int dX = 0; dX < getWidth(); dX++) {
vsobelCache[dX][dY] = -1;
}
verticalSobel = new ConvolutionMatrix(ConvolutionMatrix.VERTICAL_SOBEL);
}
if (vsobelCache[x][y] == -1) {
vsobelCache[x][y] = getConvolved(x, y, verticalSobel);
}
return vsobelCache[x][y];
}
public int getHorizontalSobel(int x, int y) {
if (hsobelCache == null) {
hsobelCache = new int[getWidth()][getHeight()];
for (int dY = 0; dY < getHeight(); dY++)
for (int dX = 0; dX < getWidth(); dX++) {
hsobelCache[dX][dY] = -1;
}
horizontalSobel = new ConvolutionMatrix(ConvolutionMatrix.HORIZONTAL_SOBEL);
}
if (hsobelCache[x][y] == -1) {
hsobelCache[x][y] = getConvolved(x, y, horizontalSobel);
}
return hsobelCache[x][y];
}
public int getLaplacian(int x, int y) {
if (laplacianCache == null) {
laplacianCache = new int[getWidth()][getHeight()];
for (int dY = 0; dY < getHeight(); dY++)
for (int dX = 0; dX < getWidth(); dX++) {
laplacianCache[dX][dY] = -1;
}
laplacian = new ConvolutionMatrix(ConvolutionMatrix.LAPLACIAN);
}
if (laplacianCache[x][y] == -1) {
laplacianCache[x][y] = getConvolved(x, y, laplacian);
}
return laplacianCache[x][y];
}
public float[][] varianceCache;
public static void main(String[] args) throws Exception {
PixelLoader image = new PixelLoader("/home/ooechs/Desktop/Lenna.png");
FastStatistics s = new FastStatistics();
for (int i = 0; i <= 20; i++) {
long start = System.currentTimeMillis();
image.get3x3Mean(10, 10);
image.meanCache = null;
long end = System.currentTimeMillis();
long time = end - start;
if (i > 0) s.addData(time);
System.out.println(time);
}
System.out.println(s.getMean() + " | " + s.getStandardDeviation());
}
/**
* Gets the variance in grey value about a 3x3 window, centering on
* a specific point at (x, y). Pixels about the edge are ignored. Results cached for performance.
* Optimised. Was 248ms, now 87ms.
*/
public float get3x3Variance(int x, int y) {
if (varianceCache == null) {
FastStatistics solver = new FastStatistics();
varianceCache = new float[getHeight()][getWidth()];
for (int ny = size; ny < getHeight() - size; ny++)
for (int nx = size; nx < getWidth() - size; nx++) {
solver.reset();
for (int dy = -size; dy <= size; dy++) {
int newY = ny + dy;
for (int dx = -size; dx <= size; dx++) {
solver.addData(getGreyValue(nx + dx, newY));
}
}
varianceCache[ny][nx] = solver.getVariance();
}
}
return varianceCache[y][x];
}
/**
* The size of the nxn caches.
* Size 2 creates 5x5 ranges.
*/
private final int size = 2;
private int[][] rangeCache;
/**
* Optimised.
* Took 206 ms to run on Lenna.png, now takes 43ms.
*/
public int get3x3Range(int x, int y) {
if (rangeCache == null) {
rangeCache = new int[getHeight()][getWidth()];
for (int ny = size; ny < getHeight() - size; ny++) {
for (int nx = size; nx < getWidth() - size; nx++) {
// calculate the mean without using the statistics solver
int high = 0;
int low = 256;
for (int dy = -size; dy <= size; dy++) {
int newY = ny + dy;
for (int dx = -size; dx <= size; dx++) {
int value = getGreyValue(nx + dx, newY);
if (value > high) high = value;
if (value < low) low = value;
}
}
rangeCache[ny][nx] = high - low;
}
}
}
return rangeCache[y][x];
}
private float[][] meanCache;
/**
* Optimised.
* Was 246ms to run on Lenna.png, now is 57ms.
*/
public float get3x3Mean(int x, int y) {
if (meanCache == null) {
// precalculate the number of elements to be averaged
final int count = ((size * 2) + 1) * ((size * 2) + 1);
meanCache = new float[getHeight()][getWidth()];
for (int ny = size; ny < getHeight() - size; ny++) {
for (int nx = size; nx < getWidth() - size; nx++) {
// calculate the mean without using the statistics solver
float total = 0;
for (int dy = -size; dy <= size; dy++) {
int newY = ny + dy;
for (int dx = -size; dx <= size; dx++) {
total += getGreyValue(nx + dx, newY);
}
}
meanCache[ny][nx] = total / count;
}
}
}
return meanCache[y][x];
}
public static final int HARALICK_CONTRAST = 1;
public static final int HARALICK_DISSIMILARITY = 2;
public static final int HARALICK_UNIFORMITY = 3;
public static final int HARALICK_MAXIMUM_PROBABILITY = 4;
public static final int HARALICK_ENTROPY = 5;
public static final int VARIANCE = 6;
public BufferedImage getProcessedImage(final int haralickType) {
BufferedImage image = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
double min = Double.MAX_VALUE;
double max = 0;
double[][] saved = new double[img.getWidth()][img.getHeight()];
// run once to find min and max
int border = HARALICK_SIZE + 1;
for (int yPos = border; yPos < img.getHeight() - border; yPos++) {
for (int xPos = border; xPos < img.getWidth() - border; xPos++) {
double c = 0;
switch (haralickType) {
case HARALICK_CONTRAST:
c = getContrast(xPos, yPos);
break;
case HARALICK_DISSIMILARITY:
c = getDissimilarity(xPos, yPos);
break;
case HARALICK_UNIFORMITY:
c = getUniformity(xPos, yPos);
break;
case HARALICK_ENTROPY:
c = getEntropy(xPos, yPos);
break;
case HARALICK_MAXIMUM_PROBABILITY:
c = getMaximumProbability(xPos, yPos);
break;
case VARIANCE:
c = get3x3Variance(xPos, yPos);
}
if (c < min) min = c;
else if (c > max) max = c;
saved[xPos][yPos] = c;
}
}
// find the difference between min and max. This will be the scaling factor.
double range = max - min;
for (int yPos = border; yPos < img.getHeight() - border; yPos++) {
for (int xPos = border; xPos < img.getWidth() - border; xPos++) {
// histogram stretch to emphasize contrast
int value = (int) (((saved[xPos][yPos] - min) / range) * 255);
// drawPixels on the image
image.setRGB(xPos, yPos, new Color(value, value, value).getRGB());
}
}
return image;
}
public static final int HARALICK_SIZE = 8;
/**
* Gets the contrast from the gray level co-occurence matrix
*/
public double getContrast(int x, int y) {
double[][] glcm = getHorizontalGLCM(x, y, HARALICK_SIZE);
double total = 0;
for (int dy = 0; dy < GLCM_SIZE; dy++)
for (int dx = 0; dx < GLCM_SIZE; dx++) {
total += glcm[dx][dy] * ((dx - dy) ^ 2);
}
return total;
}
/**
* Gets the dissimilarity. Similar to contrast but uses
* a non exponential scale.
*/
public double getDissimilarity(int x, int y) {
double[][] glcm = getHorizontalGLCM(x, y, HARALICK_SIZE);
double total = 0;
for (int dy = 0; dy < GLCM_SIZE; dy++)
for (int dx = 0; dx < GLCM_SIZE; dx++) {
total += glcm[dx][dy] * (Math.abs(dx - dy));
}
return total;
}
/**
* Gets the uniformity
*/
public double getUniformity(int x, int y) {
double[][] glcm = getHorizontalGLCM(x, y, HARALICK_SIZE);
double total = 0;
for (int dy = 0; dy < GLCM_SIZE; dy++)
for (int dx = 0; dx < GLCM_SIZE; dx++) {
total += glcm[dx][dy] * glcm[dx][dy];
}
return total;
}
/**
* Gets the entropy
*/
public double getEntropy(int x, int y) {
double[][] glcm = getHorizontalGLCM(x, y, HARALICK_SIZE);
double total = 0;
for (int dy = 0; dy < GLCM_SIZE; dy++)
for (int dx = 0; dx < GLCM_SIZE; dx++) {
total += glcm[dx][dy] * (-Math.log1p(glcm[dx][dy]));
}
return total;
}
/**
* Gets the maximum probability
*/
public double getMaximumProbability(int x, int y) {
double[][] glcm = getHorizontalGLCM(x, y, HARALICK_SIZE);
double max = 0;
for (int dy = 0; dy < GLCM_SIZE; dy++)
for (int dx = 0; dx < GLCM_SIZE; dx++) {
if (glcm[dx][dy] > max) {
max = glcm[dx][dy];
}
}
return max;
}
final int GLCM_SIZE = 16;
final int GLCM_QUANTISATION = 256 / GLCM_SIZE;
public int quantize(int value) {
if (value > 240) value = 240;
value -= 210;
if (value < 0) value = 0;
return value / GLCM_QUANTISATION;
}
/**
* Produces a Gray Level Co-occurence Matrix for a window
* centered at (x,y) with a width and height of twice half size + 1
* The GLCM is based on a 4 bit image quantization resulting in a
* 16x16 array of 256 co-occurence values.
*/
public double[][] getHorizontalGLCM(final int x, final int y, final int halfSize) {
double[][] glcm = new double[GLCM_SIZE][GLCM_SIZE];
//int count = 0;
for (int dy = -halfSize; dy <= halfSize; dy++) {
for (int dx = -halfSize; dx <= halfSize; dx++) {
int xPos = x + dx;
int yPos = y + dy;
// find the reference pixel
int referenceValue = quantize(getGreyValue(xPos, yPos));
// and its neighbour (to the immediate right)
int neighbourValue = quantize(getGreyValue(xPos + 1, yPos));
// increment the glcm
glcm[referenceValue][neighbourValue]++;
// increment the opposite side too (for symmetry)
glcm[neighbourValue][referenceValue]++;
//count+= 2;
}
}
// normalise values
/* for (int i = 0; i < GLCM_SIZE; i++) {
for (int j = 0; j < GLCM_SIZE; j++) {
glcm[i][j] /= count;
}
}*/
return glcm;
}
public FastStatistics get3x3Stats(int x, int y, int channel) {
final int size = 1;
FastStatistics solver = new FastStatistics();
if (y - size < 0) return solver;
if (y + size > (getHeight() - 1)) return solver;
if (x - size < 0) return solver;
if (x + size > (getWidth() - 1)) return solver;
for (int dy = -size; dy <= size; dy++) {
int newY = y + dy;
for (int dx = -size; dx <= size; dx++) {
int newX = x + dx;
switch (channel) {
case ColourChannels.RED:
solver.addData(getRed(newX, newY));
break;
case ColourChannels.GREEN:
solver.addData(getGreen(newX, newY));
break;
case ColourChannels.BLUE:
solver.addData(getBlue(newX, newY));
break;
case ColourChannels.HUE:
solver.addData(getHue(newX, newY));
break;
case ColourChannels.SATURATION:
solver.addData(getSaturation(newX, newY));
break;
case ColourChannels.LIGHTNESS:
solver.addData(getLightness(newX, newY));
break;
case ColourChannels.GREYSCALE:
solver.addData(getGreyValue(newX, newY));
break;
}
}
}
return solver;
}
/**
* Speed improvements, 19th Feb 2008
* Now runs 39% faster.
*/
public int getConvolved(int x, int y, ConvolutionMatrix matrix) {
double sum = 0;
int size = matrix.getWidth() / 2;
if (y - size < 0) return 0;
if (y + size > (getHeight() - 1)) return 0;
if (x - size < 0) return 0;
if (x + size > (getWidth() - 1)) return 0;
for (int dy = -size; dy <= size; dy++) {
int newY = y + dy;
for (int dx = -size; dx <= size; dx++) {
int newX = x + dx;
sum += getGreyValue(newX, newY) * matrix.getWeight(dx + size, dy + size);
}
}
return (int) (sum / matrix.getTotal());
}
public int[] getPrecomputedColours() {
// Precache the colours
int[] colours = new int[256];
for (int i = 0; i <= 255; i++) {
colours[i] = new Color(i, i, i).getRGB();
}
return colours;
}
/**
* Speed improvements, 19th Feb 2008
* Now runs 39% faster.
*/
public BufferedImage getConvolved(ConvolutionMatrix matrix) {
BufferedImage image = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
int[] colours = getPrecomputedColours();
for (int yPos = 0; yPos < img.getHeight(); yPos++) {
for (int xPos = 0; xPos < img.getWidth(); xPos++) {
int c = getConvolved(xPos, yPos, matrix);
if (c > 255) c = 255;
if (c < 0) c = 0;
if (c != 0) image.setRGB(xPos, yPos, colours[c]);
}
}
return image;
}
public void setRGB(int x, int y, int colour) {
img.setRGB(x, y, colour);
// todo - this is wrong!!
if (greyColourCache != null) {
greyColourCache[y][x] = colour;
}
}
public void saveAs(String filename) throws Exception {
saveAs(new File(filename));
}
public void saveAs(File imageFile) throws Exception {
try {
String type = "bmp";
if (imageFile.getName().endsWith(".png")) {
type = "png";
}
if (imageFile.getName().endsWith(".jpg")) {
type = "jpg";
}
// Use the Java ImageIO library to shave the file as a PNG image
javax.imageio.ImageIO.write(img, type, imageFile);
} catch (Exception e) {
e.printStackTrace();
}
}
public int getWidth() {
return img.getWidth();
}
public int getHeight() {
return img.getHeight();
}
public String getFilename() {
return getFile().getName();
}
}