1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
|
package ij.process;
import java.util.Arrays;
/** A table for easier downsizing by convolution with a kernel.
* Supports the interpolation methods of ImageProcessor: none, bilinear, bicubic
* Convention used: The left edges of the first pixel are the same for source and destination.
* E.g. when downsizing by a factor of 2, pixel 0 of the destination
* takes the space of pixels 0 and 1 of the source.
*
* Example for use: Downsizing row 0 of 'pixels' from 'roi.width' to 'destinationWidth'.
* The input range is given by the roi rectangle.
* Output is written to row 0 of 'pixels2' (width: 'destinationWidth')
<code>
DownSizeTable dt = new DownSizeTable(width, roi.x, roi.width, destinationWidth, ImageProcessor.BICUBIC);
int tablePointer = 0;
for (int srcPoint=dt.srcStart, srcPoint<=dt.srcEnd; srcPoint++) {
float v = pixels[srcPoint];
for (int i=0; i<dt.kernelSize; i++, tablePointer++)
pixels2[dt.indices[tablePointer]] += v * dt.weights[tablePointer];
</code>
*/
public class DownsizeTable {
/** Number of kernel points per source data point */
public final int kernelSize;
/** index of the first point of the source data that should be accessed */
public final int srcStart;
/** index of the last point of the source data that should be accessed */
public final int srcEnd;
/** For each source point between srcStart and srcEnd, indices of destination
* points where the data should be added.
* Arranged in blocks of 'kernelSize' points. E.g. for kernelSize=2, array
* elements 0,1 are for point srcStart, 2,3 for point srcStart+1, etc. */
public final int[] indices;
/** For each source point, weights for adding it to the destination point
* given in the corresponding element of 'indices' */
public final float[] weights;
/** Kernel sizes corresponding to the interpolation methods NONE, BILINEAR, BICUBIC */
private final static int[] kernelSizes = new int[] {1, 2, 4};
private final int srcOrigin, srcLength;
private final double scale; //source/destination pixel numbers
private final int interpolationMethod;
private final static int UNUSED=-1; //marks unused entries in 'indices' array
/** Create a table for 1-dimensional downscaling interpolation.
* Interpolation is done by
* @param srcSize Size of source data, i.e., width or height of input image
* @param srcOrigin Index of first pixel of source data that corresponds to an ouput pixel,
* 0 or origin of source rectangle if only a roi is scaled
* @param srcLength Number of pixels of source data that should correspond to output, i.e.,
* width or height of source roi
* @param dstSize Number of destination pixels.
* @param interpolationMethod One of the methods defined in ImageProcessor: NONE, BILINEAR, BICUBIC
*/
DownsizeTable(int srcSize, int srcOrigin, int srcLength, int dstSize, int interpolationMethod) {
this.srcOrigin = srcOrigin;
this.srcLength = srcLength;
this.interpolationMethod = interpolationMethod;
this.scale = srcLength / (double)dstSize;
this.kernelSize = kernelSizes[interpolationMethod];
int srcStartUncorr = (int)(Math.ceil(1e-8+srcIndex(-0.5*kernelSize))); //may be <0
srcStart = srcStartUncorr < 0 ? 0 : srcStartUncorr; //corrected value, avoids pointing out of array
int srcEndUncorr = (int)(Math.floor(1e-8+srcIndex(dstSize-1 + 0.5*kernelSize)));
srcEnd = srcEndUncorr >=srcSize ? srcSize-1 : srcEndUncorr;
int arraySize = (srcEnd - srcStart + 1) * kernelSize;
indices = new int[arraySize];
weights = new float[arraySize];
Arrays.fill(indices, UNUSED);
//IJ.log("src size="+srcSize+" range="+srcStart+"-"+srcEnd+" array:"+arraySize+" scale="+(float)scale);
for (int dst=0; dst<dstSize; dst++) {
double sum = 0;
int lowestS = (int)(Math.ceil(1e-8+srcIndex(dst-0.5*kernelSize)));
int highestS = (int)(Math.floor(-1e-8+srcIndex(dst+0.5*kernelSize)));
for (int src=lowestS; src<=highestS; src++) {
//out of bounds policy is 'use value of edge pixel'.
//We therfore replace an out-of-bounds pixel by the edge pixel:
int s = src < 0 ? 0 : (src >= srcSize ? srcSize-1 : src);
int p = (s-srcStart)*kernelSize;// points to first value in 'indices' and 'weights'
// arrays reserved for this source pixel
while(indices[p]!=UNUSED && indices[p]!=dst)
p++; //position used for other destination pixel, try the next one
//if(p-(s-srcStart)*kernelSize>=kernelSize)IJ.log(srcSize+">"+dstSize+": too long: src="+src+" dst="+dst);
indices[p] = dst;
float weight = kernel(dst - dstIndex(src));
sum += weight;
weights[p] += weight;
//IJ.log("src="+src+"("+s+") to "+dst+" w="+weight+" p="+p);
}
//normalize: sum of weights contributing to this destination pixel should be 1
int iStart = (lowestS-srcStart)*kernelSize;
if (iStart < 0) iStart = 0;
int iStop = (highestS-srcStart)*kernelSize+(kernelSize-1);
if (iStop>=indices.length) iStop = indices.length-1;
//IJ.log("normalize "+iStart+"-"+iStop+" sum="+sum);
for (int i=iStart; i<=iStop; i++)
if (indices[i] == dst)
weights[i] = (float)(weights[i]/sum);
}
for (int i=0; i<indices.length; i++)
if (indices[i]==UNUSED)
indices[i] = 0; //set unused entries to pixel 0 (weight is 0 anyhow), then they do no harm
}
// Converts a destination pixel coordinate (index) to the corresponding
// source coordinate
// All coordinates refer to the centers of the pixel
// Also for fractional indices, e.g. dstIndex = i-0.5 for left edge of pixel.
// The output is also fractional (not converted to int)
// No check for source array bounds, may result in a value <0 or >= array size.
private double srcIndex(double dstIndex) {
return srcOrigin-0.5 + (dstIndex+0.5)*scale;
}
// Converts the coordinate (index) of a source pixel to the destination pixel
private double dstIndex(int srcIndex) {
return (srcIndex-srcOrigin+0.5)/scale - 0.5;
}
// Calculates the kernel value. Only valid within +/- 0.5*kernelSize
protected float kernel(double x) {
switch (interpolationMethod) {
case ImageProcessor.NONE:
return 1f;
case ImageProcessor.BILINEAR:
return 1f - (float)Math.abs(x);
case ImageProcessor.BICUBIC:
return (float)ImageProcessor.cubic(x);
}
return Float.NaN;
}
}
|