|
|
@ -0,0 +1,378 @@ |
|
|
|
|
|
|
|
package filtro_chido; |
|
|
|
|
|
|
|
import ij.IJ; |
|
|
|
import ij.ImagePlus; |
|
|
|
import ij.ImageStack; |
|
|
|
import ij.gui.GenericDialog; |
|
|
|
import ij.plugin.filter.PlugInFilter; |
|
|
|
import static ij.plugin.filter.PlugInFilter.DOES_8G; |
|
|
|
import static ij.plugin.filter.PlugInFilter.NO_CHANGES; |
|
|
|
import ij.process.ByteProcessor; |
|
|
|
import ij.process.FloatProcessor; |
|
|
|
import ij.process.ImageProcessor; |
|
|
|
import ij.process.ShortProcessor; |
|
|
|
|
|
|
|
import java.util.Arrays; |
|
|
|
|
|
|
|
|
|
|
|
public class SRM implements PlugInFilter { |
|
|
|
ImagePlus image; |
|
|
|
|
|
|
|
public int setup(String arg, ImagePlus image) { |
|
|
|
this.image = image; |
|
|
|
return DOES_8G | NO_CHANGES; |
|
|
|
} |
|
|
|
|
|
|
|
public void run(ImageProcessor ip) { |
|
|
|
boolean isStack = image.getStackSize() > 1; |
|
|
|
|
|
|
|
GenericDialog gd = new GenericDialog("SRM"); |
|
|
|
gd.addNumericField("Q", Q, 2); |
|
|
|
gd.addCheckbox("showAverages", true); |
|
|
|
if (isStack) |
|
|
|
gd.addCheckbox("3d", true); |
|
|
|
gd.showDialog(); |
|
|
|
|
|
|
|
if (gd.wasCanceled()) |
|
|
|
return; |
|
|
|
|
|
|
|
Q = (float)gd.getNextNumber(); |
|
|
|
boolean showAverages = gd.getNextBoolean(); |
|
|
|
boolean do3D = isStack ? gd.getNextBoolean() : false; |
|
|
|
if (do3D) |
|
|
|
srm3D(showAverages).show(); |
|
|
|
else |
|
|
|
srm2D(ip, showAverages).show(); |
|
|
|
} |
|
|
|
|
|
|
|
final float g = 256; // number of different intensity values |
|
|
|
protected float Q = 25; //25; // complexity of the assumed distributions |
|
|
|
protected float delta; |
|
|
|
|
|
|
|
protected float factor, logDelta; |
|
|
|
|
|
|
|
float[] average; |
|
|
|
int[] count; |
|
|
|
int[] regionIndex; // if < 0, it is -1 - actual_regionIndex |
|
|
|
|
|
|
|
int[] nextNeighbor, neighborBucket; |
|
|
|
|
|
|
|
protected ImagePlus srm3D(boolean showAverages) { |
|
|
|
int w = image.getWidth(), h = image.getHeight(); |
|
|
|
int d = image.getStackSize(); |
|
|
|
|
|
|
|
delta = 1f / (6 * w * h * d); |
|
|
|
|
|
|
|
factor = g * g / 2 / Q; |
|
|
|
logDelta = 2f * (float)Math.log(6 * w * h * d); |
|
|
|
|
|
|
|
IJ.showStatus("Initializing regions"); |
|
|
|
initializeRegions3D(w, h, d); |
|
|
|
IJ.showStatus("Initializing neighbors"); |
|
|
|
initializeNeighbors3D(w, h, d); |
|
|
|
IJ.showStatus("Merging neighbors"); |
|
|
|
mergeAllNeighbors3D(w, h); |
|
|
|
IJ.showStatus("Making stack"); |
|
|
|
|
|
|
|
ImageStack stack = new ImageStack(w, h); |
|
|
|
if (showAverages) |
|
|
|
for (int k = 0; k < d; k++) { |
|
|
|
int off = k * w * h; |
|
|
|
float[] p = new float[w * h]; |
|
|
|
for (int i = 0; i < w * h; i++) |
|
|
|
p[i] = average[getRegionIndex(i + off)]; |
|
|
|
stack.addSlice(null, new FloatProcessor(w, h, |
|
|
|
p, null)); |
|
|
|
} |
|
|
|
else { |
|
|
|
int regionCount = consolidateRegions(); |
|
|
|
|
|
|
|
if (regionCount > 1<<16) |
|
|
|
IJ.showMessage("Found " + regionCount |
|
|
|
+ " regions, which does not fit" |
|
|
|
+ " in 16-bit."); |
|
|
|
|
|
|
|
for (int k = 0; k < d; k++) { |
|
|
|
ImageProcessor ip; |
|
|
|
|
|
|
|
int off = k * w * h; |
|
|
|
|
|
|
|
if (regionCount > 1<<8) { |
|
|
|
short[] p = new short[w * h]; |
|
|
|
for (int i = 0; i < p.length; i++) |
|
|
|
p[i] = (short)regionIndex[i |
|
|
|
+ off]; |
|
|
|
ip = new ShortProcessor(w, h, p, null); |
|
|
|
} |
|
|
|
else { |
|
|
|
byte[] p = new byte[w * h]; |
|
|
|
for (int i = 0; i < p.length; i++) |
|
|
|
p[i] = (byte)regionIndex[i |
|
|
|
+ off]; |
|
|
|
ip = new ByteProcessor(w, h, p, null); |
|
|
|
} |
|
|
|
stack.addSlice(null, ip); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
IJ.showStatus(""); |
|
|
|
String title = image.getTitle() + " (SRM3D Q=" + Q + ")"; |
|
|
|
return new ImagePlus(title, stack); |
|
|
|
} |
|
|
|
|
|
|
|
protected ImagePlus srm2D(ImageProcessor ip, boolean showAverages) { |
|
|
|
int w = ip.getWidth(), h = ip.getHeight(); |
|
|
|
|
|
|
|
delta = 1f / (6 * w * h); |
|
|
|
|
|
|
|
factor = g * g / 2 / Q; |
|
|
|
logDelta = 2f * (float)Math.log(6 * w * h); |
|
|
|
|
|
|
|
byte[] pixel = (byte[])ip.getPixels(); |
|
|
|
initializeRegions2D(pixel, ip.getWidth(), ip.getHeight()); |
|
|
|
initializeNeighbors2D(pixel, w, h); |
|
|
|
mergeAllNeighbors2D(w); |
|
|
|
|
|
|
|
if (showAverages) { |
|
|
|
for (int i = 0; i < average.length; i++) |
|
|
|
average[i] = average[getRegionIndex(i)]; |
|
|
|
ip = new FloatProcessor(w, h, average, null); |
|
|
|
} |
|
|
|
else { |
|
|
|
int regionCount = consolidateRegions(); |
|
|
|
|
|
|
|
if (regionCount > 1<<8) { |
|
|
|
if (regionCount > 1<<16) |
|
|
|
IJ.showMessage("Found " + regionCount |
|
|
|
+ " regions, which does not fit" |
|
|
|
+ " in 16-bit."); |
|
|
|
short[] pixel16 = new short[w * h]; |
|
|
|
for (int i = 0; i < pixel16.length; i++) |
|
|
|
pixel16[i] = (short)regionIndex[i]; |
|
|
|
ip = new ShortProcessor(w, h, pixel16, null); |
|
|
|
} |
|
|
|
else { |
|
|
|
pixel = new byte[w * h]; |
|
|
|
for (int i = 0; i < pixel.length; i++) |
|
|
|
pixel[i] = (byte)regionIndex[i]; |
|
|
|
ip = new ByteProcessor(w, h, pixel, null); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
String title = image.getTitle() + " (SRM Q=" + Q + ")"; |
|
|
|
return new ImagePlus(title, ip); |
|
|
|
} |
|
|
|
|
|
|
|
void initializeRegions2D(byte[] pixel, int w, int h) { |
|
|
|
average = new float[w * h]; |
|
|
|
count = new int[w * h]; |
|
|
|
regionIndex = new int[w * h]; |
|
|
|
|
|
|
|
for (int i = 0; i < average.length; i++) { |
|
|
|
average[i] = pixel[i] & 0xff; |
|
|
|
count[i] = 1; |
|
|
|
regionIndex[i] = i; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void initializeRegions3D(int w, int h, int d) { |
|
|
|
average = new float[w * h * d]; |
|
|
|
count = new int[w * h * d]; |
|
|
|
regionIndex = new int[w * h * d]; |
|
|
|
|
|
|
|
for (int j = 0; j < d; j++) { |
|
|
|
byte[] pixel = |
|
|
|
(byte[])image.getStack().getProcessor(j |
|
|
|
+ 1).getPixels(); |
|
|
|
int offset = j * w * h; |
|
|
|
for (int i = 0; i < w * h; i++) { |
|
|
|
average[offset + i] = pixel[i] & 0xff; |
|
|
|
count[offset + i] = 1; |
|
|
|
regionIndex[offset + i] = offset + i; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
protected void addNeighborPair(int neighborIndex, |
|
|
|
byte[] pixel, int i1, int i2) { |
|
|
|
int difference = Math.abs((pixel[i1] & 0xff) |
|
|
|
- (pixel[i2] & 0xff)); |
|
|
|
nextNeighbor[neighborIndex] = neighborBucket[difference]; |
|
|
|
neighborBucket[difference] = neighborIndex; |
|
|
|
} |
|
|
|
|
|
|
|
void initializeNeighbors2D(byte[] pixel, int w, int h) { |
|
|
|
nextNeighbor = new int[2 * w * h]; |
|
|
|
|
|
|
|
// bucket sort |
|
|
|
neighborBucket = new int[256]; |
|
|
|
Arrays.fill(neighborBucket, -1); |
|
|
|
|
|
|
|
for (int j = h - 1; j >= 0; j--) |
|
|
|
for (int i = w - 1; i >= 0; i--) { |
|
|
|
int index = i + w * j; |
|
|
|
int neighborIndex = 2 * index; |
|
|
|
|
|
|
|
// vertical |
|
|
|
if (j < h - 1) |
|
|
|
addNeighborPair(neighborIndex + 1, |
|
|
|
pixel, index, index + w); |
|
|
|
|
|
|
|
// horizontal |
|
|
|
if (i < w - 1) |
|
|
|
addNeighborPair(neighborIndex, |
|
|
|
pixel, index, index + 1); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
protected void addNeighborPair(int neighborIndex, |
|
|
|
byte[] pixel, byte[] nextPixel, int i) { |
|
|
|
int difference = Math.abs((pixel[i] & 0xff) |
|
|
|
- (nextPixel[i] & 0xff)); |
|
|
|
nextNeighbor[neighborIndex] = neighborBucket[difference]; |
|
|
|
neighborBucket[difference] = neighborIndex; |
|
|
|
} |
|
|
|
|
|
|
|
void initializeNeighbors3D(int w, int h, int d) { |
|
|
|
nextNeighbor = new int[3 * w * h * d]; |
|
|
|
|
|
|
|
// bucket sort |
|
|
|
neighborBucket = new int[256]; |
|
|
|
Arrays.fill(neighborBucket, -1); |
|
|
|
|
|
|
|
byte[] nextPixel = null; |
|
|
|
for (int k = d - 1; k >= 0; k--) { |
|
|
|
byte[] pixel = |
|
|
|
(byte[])image.getStack().getProcessor(k |
|
|
|
+ 1).getPixels(); |
|
|
|
for (int j = h - 1; j >= 0; j--) |
|
|
|
for (int i = w - 1; i >= 0; i--) { |
|
|
|
int index = i + w * j; |
|
|
|
int neighborIndex = |
|
|
|
3 * (index + k * w * h); |
|
|
|
|
|
|
|
// depth |
|
|
|
if (nextPixel != null) |
|
|
|
addNeighborPair(neighborIndex |
|
|
|
+ 2, pixel, |
|
|
|
nextPixel, index); |
|
|
|
|
|
|
|
// vertical |
|
|
|
if (j < h - 1) |
|
|
|
addNeighborPair(neighborIndex |
|
|
|
+ 1, pixel, |
|
|
|
index, index + w); |
|
|
|
|
|
|
|
// horizontal |
|
|
|
if (i < w - 1) |
|
|
|
addNeighborPair(neighborIndex, |
|
|
|
pixel, |
|
|
|
index, index + 1); |
|
|
|
} |
|
|
|
nextPixel = pixel; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// recursively find out the region index for this pixel |
|
|
|
int getRegionIndex(int i) { |
|
|
|
i = regionIndex[i]; |
|
|
|
while (i < 0) |
|
|
|
i = regionIndex[-1 - i]; |
|
|
|
return i; |
|
|
|
} |
|
|
|
|
|
|
|
// should regions i1 and i2 be merged? |
|
|
|
boolean predicate(int i1, int i2) { |
|
|
|
float difference = average[i1] - average[i2]; |
|
|
|
/* |
|
|
|
* This would be the non-relaxed predicate mentioned in the |
|
|
|
* paper. |
|
|
|
* |
|
|
|
* return difference * difference < |
|
|
|
factor * (1f / count[i1] + 1f / count[i2]); |
|
|
|
* |
|
|
|
*/ |
|
|
|
float log1 = (float)Math.log(1 + count[i1]) |
|
|
|
* (g < count[i1] ? g : count[i1]); |
|
|
|
float log2 = (float)Math.log(1 + count[i2]) |
|
|
|
* (g < count[i2] ? g : count[i2]); |
|
|
|
return difference * difference < |
|
|
|
.1f * factor * ((log1 + logDelta) / count[i1] |
|
|
|
+ ((log2 + logDelta) / count[i2])); |
|
|
|
} |
|
|
|
|
|
|
|
void mergeAllNeighbors2D(int w) { |
|
|
|
for (int i = 0; i < neighborBucket.length; i++) { |
|
|
|
int neighborIndex = neighborBucket[i]; |
|
|
|
while (neighborIndex >= 0) { |
|
|
|
int i1 = neighborIndex / 2; |
|
|
|
int i2 = i1 |
|
|
|
+ (0 == (neighborIndex & 1) ? 1 : w); |
|
|
|
|
|
|
|
i1 = getRegionIndex(i1); |
|
|
|
i2 = getRegionIndex(i2); |
|
|
|
|
|
|
|
if (predicate(i1, i2)) |
|
|
|
mergeRegions(i1, i2); |
|
|
|
|
|
|
|
neighborIndex = nextNeighbor[neighborIndex]; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void mergeAllNeighbors3D(int w, int h) { |
|
|
|
for (int i = 0; i < neighborBucket.length; i++) { |
|
|
|
int neighborIndex = neighborBucket[i]; |
|
|
|
IJ.showProgress(i, neighborBucket.length); |
|
|
|
while (neighborIndex >= 0) { |
|
|
|
int i1 = neighborIndex / 3; |
|
|
|
int i2 = i1 |
|
|
|
+ (0 == (neighborIndex % 3) ? 1 : |
|
|
|
(1 == (neighborIndex % 3) ? w : |
|
|
|
w * h)); |
|
|
|
|
|
|
|
i1 = getRegionIndex(i1); |
|
|
|
i2 = getRegionIndex(i2); |
|
|
|
|
|
|
|
if (i1 != i2 && predicate(i1, i2)) |
|
|
|
mergeRegions(i1, i2); |
|
|
|
|
|
|
|
neighborIndex = nextNeighbor[neighborIndex]; |
|
|
|
} |
|
|
|
} |
|
|
|
IJ.showProgress(neighborBucket.length, neighborBucket.length); |
|
|
|
} |
|
|
|
|
|
|
|
void mergeRegions(int i1, int i2) { |
|
|
|
if (i1 == i2) |
|
|
|
return; |
|
|
|
int mergedCount = count[i1] + count[i2]; |
|
|
|
float mergedAverage = (average[i1] * count[i1] |
|
|
|
+ average[i2] * count[i2]) / mergedCount; |
|
|
|
|
|
|
|
// merge larger index into smaller index |
|
|
|
if (i1 > i2) { |
|
|
|
average[i2] = mergedAverage; |
|
|
|
count[i2] = mergedCount; |
|
|
|
regionIndex[i1] = -1 - i2; |
|
|
|
} |
|
|
|
else { |
|
|
|
average[i1] = mergedAverage; |
|
|
|
count[i1] = mergedCount; |
|
|
|
regionIndex[i2] = -1 - i1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
int consolidateRegions() { |
|
|
|
|
|
|
|
int count = 0; |
|
|
|
for (int i = 0; i < regionIndex.length; i++) |
|
|
|
if (regionIndex[i] < 0) |
|
|
|
regionIndex[i] = |
|
|
|
regionIndex[-1 - regionIndex[i]]; |
|
|
|
else |
|
|
|
regionIndex[i] = count++; |
|
|
|
return count; |
|
|
|
} |
|
|
|
} |