ImageProcessorPlus.java
package com.github.celldynamics.quimp.plugin.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ij.process.ImageProcessor;
/**
* Class implementing extra functionalities for ij.ImageProcessor
*
* <p>Check {@link #extendImageBeforeRotation(ImageProcessor, double)} for possible problems
*
* @author p.baniukiewicz
*/
public class ImageProcessorPlus {
/**
* The Constant LOGGER.
*/
static final Logger LOGGER = LoggerFactory.getLogger(ImageProcessorPlus.class.getName());
/**
* Main constructor.
*/
public ImageProcessorPlus() {
}
/**
* Add borders around image to prevent cropping during rotating.
*
* <p><b>Warning</b>
*
* <p>Replaces original image and may not preserve all its attributes
*
* @param ip ImageProcessor to be extended
* @param angle Angle to be image rotated
* @return copy of ip extended to size that allows to rotate it by angle without clipping
*/
public ImageProcessor extendImageBeforeRotation(ImageProcessor ip, double angle) {
ImageProcessor ret;
int width = ip.getWidth();
int height = ip.getHeight();
// get bounding box after rotation
RectangleBox rb = new RectangleBox(width, height);
rb.rotateBoundingBox(angle);
int newWidth = (int) Math.round(rb.getWidth());
int newHeight = (int) Math.round(rb.getHeight());
if (newWidth < width) {
newWidth = width;
}
if (newHeight < height) {
newHeight = height;
}
// create new array resized
ret = ip.createProcessor(newWidth, newHeight);
// get current background - borders will have the same value
ret.setValue(ip.getBackgroundValue()); // set current fill value for extended image
ret.setBackgroundValue(ip.getBackgroundValue()); // set the same background as in original image
ret.fill(); // change color of extended image
ret.setInterpolationMethod(ip.getInterpolationMethod());
// insert original image into extended
ret.insert(ip, (newWidth - ip.getWidth()) / 2, (newHeight - ip.getHeight()) / 2);
ret.resetRoi();
return ret; // assign extended into current
}
/**
* Rotate image by specified angle keeping correct rotation direction.
*
* @param ip ImageProcessor to be rotated
* @param angle Angle of rotation in anti-clockwise direction
* @param addBorders if true rotates with extension, false use standard rotation with clipping
* @return rotated ip that is a copy of ip whenaddBorders is true or reference when addBorders
* is false
*/
public ImageProcessor rotate(ImageProcessor ip, double angle, boolean addBorders) {
ImageProcessor ret;
if (addBorders) {
ret = extendImageBeforeRotation(ip, angle);
} else {
ret = ip;
}
ret.rotate(angle);
return ret;
}
/**
* Crop image.
*
* <p><b>Warning</b>
*
* <p>Modifies current object
*
* @param ip ImageProcessor to be cropped
* @param luX Left upper corner x coordinate
* @param luY Left upper corner y coordinate
* @param width Width of clipped area
* @param height Height of clipped area
* @return Clipped image
*/
public ImageProcessor crop(ImageProcessor ip, int luX, int luY, int width, int height) {
ip.setRoi(luX, luY, width, height);
ip = ip.crop();
ip.resetRoi();
return ip;
}
/**
* Crop image.
*
* <p><b>Warning</b>
*
* <p>Modifies current object Designed to use with cooperation with
* extendImageBeforeRotation(ImageProcessor,double).
*
* <p>Assumes that cropping area is centered in source image
*
* @param ip ImageProcessor to be cropped
* @param width Width of clipped area
* @param height Height of clipped area
* @return Clipped image
*/
public ImageProcessor cropImageAfterRotation(ImageProcessor ip, int width, int height) {
int sw = (ip.getWidth() - width) / 2;
int sh = (ip.getHeight() - height) / 2;
if (sw < 0) {
sw = 0;
}
if (sh < 0) {
sh = 0;
}
ip.setRoi(sw, sh, width, height);
ip = ip.crop();
ip.resetRoi();
return ip;
}
/**
* Perform running mean on image using convolution.
*
* @param ip ImageProcessor
* @param prefilterangle angle as k*45
* @param masksize odd size, 0 will skip processing
* @see ImageProcessorPlus.GenerateKernel
*/
public void runningMean(ImageProcessor ip, String prefilterangle, int masksize) {
LOGGER.debug("Convolving: " + prefilterangle + " " + masksize);
if (masksize == 0) {
return;
}
float[] kernel = new GenerateKernel(masksize).generateKernel(prefilterangle);
ip.convolve(kernel, masksize, masksize);
}
/**
* Support generating kernels for running mean.
*
* @author p.baniukiewicz
*
*/
class GenerateKernel {
private int size;
/**
*
* @param size Size of the kernel assuming its rectangularity.
*/
public GenerateKernel(int size) {
this.size = size;
}
/**
* Generate convolution kernel.
*
* <p>Returned kernel is compatible with ij.process.ImageProcessor.convolve(float[], int, int)
*
* @param option Option can be 0, 45, 90, 135 as string.
* @return 1D array as row ordered matrix. The kernel contains 1 on diagonal and it is
* normalised.
*/
public float[] generateKernel(String option) {
float[] ret = new float[size * size];
int mid = size / 2; // middle element (0 indexed)
switch (option) {
case "0": // row in middle
for (int i = 0; i < size; i++) {
ret[sub2lin(mid, i)] = 1.0f;
}
break;
case "135":
for (int i = 0; i < size; i++) {
ret[sub2lin(i, i)] = 1.0f;
}
break;
case "90":
for (int i = 0; i < size; i++) {
ret[sub2lin(i, mid)] = 1.0f;
}
break;
case "45":
for (int i = 0; i < size; i++) {
ret[sub2lin(i, size - 1 - i)] = 1.0f;
}
break;
default:
throw new UnsupportedOperationException("Unsupported mask angle.");
}
return normalise(ret);
}
/**
* Convert subscript indexes to linear.
*
* @param row row counted from 0
* @param col column counted from 0
* @return Linear index based on row and col position
*/
private int sub2lin(int row, int col) {
return row * size + col;
}
/**
* Normalise the kernel.
*
* <p>Divide every element by sum of elements.
*
* @param kernel kernel to normalise
* @return normalised kernel (copy)
*/
private float[] normalise(float[] kernel) {
float s = 0;
for (int i = 0; i < kernel.length; i++) {
s += kernel[i];
}
float[] ret = new float[kernel.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = kernel[i] / s;
}
return ret;
}
}
}