File: DownsizeTable.java

package info (click to toggle)
imagej 1.46a-1
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 4,248 kB
  • sloc: java: 89,778; sh: 311; xml: 51; makefile: 6
file content (130 lines) | stat: -rw-r--r-- 6,271 bytes parent folder | download | duplicates (7)
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;
	}

}