package ij;
import java.awt.*;
import java.awt.image.*;
import ij.process.*;
public class ImageStack {
static final int INITIAL_SIZE = 25;
static final String outOfRange = "Stack argument out of range: ";
private int bitDepth = 0; private int nSlices = 0;
private Object[] stack;
private String[] label;
private int width, height;
private Rectangle roi;
private ColorModel cm;
private double min=Double.MAX_VALUE;
private double max;
protected float[] cTable;
private int viewers;
private boolean signedInt;
public ImageStack() { }
public ImageStack(int width, int height) {
this(width, height, null);
}
public ImageStack(int width, int height, int size) {
this.width = width;
this.height = height;
stack = new Object[size];
label = new String[size];
nSlices = size;
}
public ImageStack(int width, int height, ColorModel cm) {
this.width = width;
this.height = height;
this.cm = cm;
stack = new Object[INITIAL_SIZE];
label = new String[INITIAL_SIZE];
nSlices = 0;
}
public void addSlice(String sliceLabel, Object pixels) {
if (pixels==null)
throw new IllegalArgumentException("'pixels' is null!");
if (!pixels.getClass().isArray())
throw new IllegalArgumentException("'pixels' is not an array");
int size = stack.length;
nSlices++;
if (nSlices>=size) {
Object[] tmp1 = new Object[size*2];
System.arraycopy(stack, 0, tmp1, 0, size);
stack = tmp1;
String[] tmp2 = new String[size*2];
System.arraycopy(label, 0, tmp2, 0, size);
label = tmp2;
}
stack[nSlices-1] = pixels;
this.label[nSlices-1] = sliceLabel;
if (this.bitDepth==0)
setBitDepth(pixels);
}
private void setBitDepth(Object pixels) {
if (pixels==null)
return;
if (pixels instanceof byte[])
this.bitDepth = 8;
else if (pixels instanceof short[])
this.bitDepth = 16;
else if (pixels instanceof float[])
this.bitDepth = 32;
else if (pixels instanceof int[])
this.bitDepth = 24;
}
public void addUnsignedShortSlice(String sliceLabel, Object pixels) {
addSlice(sliceLabel, pixels);
}
public void addSlice(ImageProcessor ip) {
addSlice(null, ip);
}
public void addSlice(String sliceLabel, ImageProcessor ip) {
ip = convertType(ip);
if (ip.getWidth()!=this.width || ip.getHeight()!=this.height) {
if (this.width==0 && this.height==0)
init(ip.getWidth(), ip.getHeight());
else {
ImageProcessor ip2 = ip.createProcessor(this.width,this.height);
ip2.insert(ip, 0, 0);
ip = ip2;
}
}
if (nSlices==0) {
cm = ip.getColorModel();
min = ip.getMin();
max = ip.getMax();
}
addSlice(sliceLabel, ip.getPixels());
}
private void init(int width, int height) {
this.width = width;
this.height = height;
stack = new Object[INITIAL_SIZE];
label = new String[INITIAL_SIZE];
}
private ImageProcessor convertType(ImageProcessor ip) {
int newBitDepth = ip.getBitDepth();
if (this.bitDepth==0)
this.bitDepth = newBitDepth;
if (this.bitDepth!=newBitDepth) {
switch (this.bitDepth) {
case 8: ip=ip.convertToByte(true); break;
case 16: ip=ip.convertToShort(true); break;
case 24: ip=ip.convertToRGB(); break;
case 32: ip=ip.convertToFloat(); break;
}
}
return ip;
}
public void addSlice(String sliceLabel, ImageProcessor ip, int n) {
if (n<0 || n>nSlices)
throw new IllegalArgumentException(outOfRange+n);
addSlice(sliceLabel, ip);
Object tempSlice = stack[nSlices-1];
String tempLabel = label[nSlices-1];
int first = n>0?n:1;
for (int i=nSlices-1; i>=first; i--) {
stack[i] = stack[i-1];
label[i] = label[i-1];
}
stack[n] = tempSlice;
label[n] = tempLabel;
}
public void deleteSlice(int n) {
if (n<1 || n>nSlices)
throw new IllegalArgumentException(outOfRange+n);
if (nSlices<1)
return;
for (int i=n; i<nSlices; i++) {
stack[i-1] = stack[i];
label[i-1] = label[i];
}
stack[nSlices-1] = null;
label[nSlices-1] = null;
nSlices--;
}
public void deleteLastSlice() {
if (nSlices>0)
deleteSlice(nSlices);
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public void setRoi(Rectangle roi) {
this.roi = roi;
}
public Rectangle getRoi() {
if (roi==null)
return new Rectangle(0, 0, width, height);
else
return(roi);
}
public void update(ImageProcessor ip) {
if (ip!=null) {
min = ip.getMin();
max = ip.getMax();
cTable = ip.getCalibrationTable();
cm = ip.getColorModel();
}
}
public Object getPixels(int n) {
if (n<1 || n>nSlices)
throw new IllegalArgumentException(outOfRange+n);
return stack[n-1];
}
public void setPixels(Object pixels, int n) {
if (n<1 || n>nSlices)
throw new IllegalArgumentException(outOfRange+n);
stack[n-1] = pixels;
if (this.bitDepth==0)
setBitDepth(pixels);
}
public Object[] getImageArray() {
return stack;
}
public int size() {
return getSize();
}
public int getSize() {
return nSlices;
}
public String[] getSliceLabels() {
if (nSlices==0)
return null;
else
return label;
}
public String getSliceLabel(int n) {
if (n<1 || n>nSlices)
return null;
else
return label[n-1];
}
public String getShortSliceLabel(int n) {
return getShortSliceLabel(n, 60);
}
public String getShortSliceLabel(int n, int max) {
String shortLabel = getSliceLabel(n);
if (shortLabel==null) return null;
int newline = shortLabel.indexOf('\n');
if (newline==0) return null;
if (newline>0)
shortLabel = shortLabel.substring(0, newline);
int len = shortLabel.length();
if (len>4 && shortLabel.charAt(len-4)=='.' && !Character.isDigit(shortLabel.charAt(len-1)))
shortLabel = shortLabel.substring(0,len-4);
if (shortLabel.length()>max)
shortLabel = shortLabel.substring(0, max);
return shortLabel;
}
public void setSliceLabel(String label, int n) {
if (n<1 || n>nSlices)
throw new IllegalArgumentException(outOfRange+n);
this.label[n-1] = label;
}
public ImageProcessor getProcessor(int n) {
ImageProcessor ip;
if (n<1 || n>nSlices)
throw new IllegalArgumentException(outOfRange+n);
if (nSlices==0)
return null;
if (stack[n-1]==null)
throw new IllegalArgumentException("Pixel array is null");
if (stack[n-1] instanceof byte[])
ip = new ByteProcessor(width, height, null, cm);
else if (stack[n-1] instanceof short[])
ip = new ShortProcessor(width, height, null, cm);
else if (stack[n-1] instanceof int[]) {
if (signedInt)
ip = new IntProcessor(width, height);
else
ip = new ColorProcessor(width, height, null);
} else if (stack[n-1] instanceof float[])
ip = new FloatProcessor(width, height, null, cm);
else
throw new IllegalArgumentException("Unknown stack type");
ip.setPixels(stack[n-1]);
if (min!=Double.MAX_VALUE && ip!=null && !(ip instanceof ColorProcessor))
ip.setMinAndMax(min, max);
if (cTable!=null)
ip.setCalibrationTable(cTable);
return ip;
}
public void setProcessor(ImageProcessor ip, int n) {
if (n<1 || n>nSlices)
throw new IllegalArgumentException(outOfRange+n);
ip = convertType(ip);
if (ip.getWidth()!=width || ip.getHeight()!=height)
throw new IllegalArgumentException("Wrong dimensions for this stack");
stack[n-1] = ip.getPixels();
}
public void setColorModel(ColorModel cm) {
this.cm = cm;
}
public ColorModel getColorModel() {
return cm;
}
public boolean isRGB() {
return nSlices==3 && (stack[0] instanceof byte[]) && getSliceLabel(1)!=null && getSliceLabel(1).equals("Red");
}
public boolean isHSB() {
return nSlices==3 && bitDepth==8 && getSliceLabel(1)!=null && getSliceLabel(1).equals("Hue");
}
public boolean isHSB32() {
return nSlices==3 && bitDepth==32 && getSliceLabel(1)!=null && getSliceLabel(1).equals("Hue");
}
public boolean isLab() {
return nSlices==3 && getSliceLabel(1)!=null && getSliceLabel(1).equals("L*");
}
public boolean isVirtual() {
return false;
}
public void trim() {
int n = (int)Math.round(Math.log(nSlices)+1.0);
for (int i=0; i<n; i++) {
deleteLastSlice();
System.gc();
}
}
public String toString() {
String v = isVirtual()?"(V)":"";
return ("stack["+getWidth()+"x"+getHeight()+"x"+size()+v+"]");
}
public final double getVoxel(int x, int y, int z) {
if (x>=0 && x<width && y>=0 && y<height && z>=0 && z<nSlices) {
switch (bitDepth) {
case 8:
byte[] bytes = (byte[])stack[z];
return bytes[y*width+x]&0xff;
case 16:
short[] shorts = (short[])stack[z];
return shorts[y*width+x]&0xffff;
case 32:
float[] floats = (float[])stack[z];
return floats[y*width+x];
case 24:
int[] ints = (int[])stack[z];
return ints[y*width+x]&0xffffffff;
default: return Double.NaN;
}
} else
throw new IndexOutOfBoundsException();
}
public final void setVoxel(int x, int y, int z, double value) {
if (x>=0 && x<width && y>=0 && y<height && z>=0 && z<nSlices) {
switch (bitDepth) {
case 8:
byte[] bytes = (byte[])stack[z];
if (value>255.0)
value = 255.0;
else if (value<0.0)
value = 0.0;
bytes[y*width+x] = (byte)(value+0.5);
break;
case 16:
short[] shorts = (short[])stack[z];
if (value>65535.0)
value = 65535.0;
else if (value<0.0)
value = 0.0;
shorts[y*width+x] = (short)(value+0.5);
break;
case 32:
float[] floats = (float[])stack[z];
floats[y*width+x] = (float)value;
break;
case 24:
int[] ints = (int[])stack[z];
ints[y*width+x] = (int)value;
break;
}
}
}
public float[] getVoxels(int x0, int y0, int z0, int w, int h, int d, float[] voxels) {
boolean inBounds = x0>=0 && x0+w<=width && y0>=0 && y0+h<=height && z0>=0 && z0+d<=nSlices;
if (voxels==null || voxels.length!=w*h*d)
voxels = new float[w*h*d];
int i = 0;
int offset;
for (int z=z0; z<z0+d; z++) {
for (int y=y0; y<y0+h; y++) {
if (inBounds) {
switch (bitDepth) {
case 8:
byte[] bytes = (byte[])stack[z];
for (int x=x0; x<x0+w; x++)
voxels[i++] = bytes[y*width+x]&0xff;
break;
case 16:
short[] shorts = (short[])stack[z];
for (int x=x0; x<x0+w; x++)
voxels[i++] = shorts[y*width+x]&0xffff;
break;
case 32:
float[] floats = (float[])stack[z];
for (int x=x0; x<x0+w; x++)
voxels[i++] = floats[y*width+x];
break;
case 24:
int[] ints = (int[])stack[z];
for (int x=x0; x<x0+w; x++)
voxels[i++] = ints[y*width+x]&0xffffffff;
break;
default:
for (int x=x0; x<x0+w; x++)
voxels[i++] = 0f;
}
} else {
for (int x=x0; x<x0+w; x++)
voxels[i++] = (float)getVoxel(x, y, z);
}
}
}
return voxels;
}
public float[] getVoxels(int x0, int y0, int z0, int w, int h, int d, float[] voxels, int channel) {
if (bitDepth!=24)
return getVoxels(x0, y0, z0, w, h, d, voxels);
boolean inBounds = x0>=0 && x0+w<=width && y0>=0 && y0+h<=height && z0>=0 && z0+d<=nSlices;
if (voxels==null || voxels.length!=w*h*d)
voxels = new float[w*h*d];
int i = 0;
for (int z=z0; z<z0+d; z++) {
int[] ints = (int[])stack[z];
for (int y=y0; y<y0+h; y++) {
for (int x=x0; x<x0+w; x++) {
int value = inBounds?ints[y*width+x]&0xffffffff:(int)getVoxel(x, y, z);
switch (channel) {
case 0: value=(value&0xff0000)>>16; break;
case 1: value=(value&0xff00)>>8; break;
case 2: value=value&0xff;; break;
}
voxels[i++] = (float)value;
}
}
}
return voxels;
}
public void setVoxels(int x0, int y0, int z0, int w, int h, int d, float[] voxels) {
boolean inBounds = x0>=0 && x0+w<=width && y0>=0 && y0+h<=height && z0>=0 && z0+d<=nSlices;
if (voxels==null || voxels.length!=w*h*d)
;
int i = 0;
float value;
for (int z=z0; z<z0+d; z++) {
for (int y=y0; y<y0+h; y++) {
if (inBounds) {
switch (bitDepth) {
case 8:
byte[] bytes = (byte[])stack[z];
for (int x=x0; x<x0+w; x++) {
value = voxels[i++];
if (value>255f)
value = 255f;
else if (value<0f)
value = 0f;
bytes[y*width+x] = (byte)(value+0.5f);
}
break;
case 16:
short[] shorts = (short[])stack[z];
for (int x=x0; x<x0+w; x++) {
value = voxels[i++];
if (value>65535f)
value = 65535f;
else if (value<0f)
value = 0f;
shorts[y*width+x] = (short)(value+0.5f);
}
break;
case 32:
float[] floats = (float[])stack[z];
for (int x=x0; x<x0+w; x++) {
value = voxels[i++];
floats[y*width+x] = value;
}
break;
case 24:
int[] ints = (int[])stack[z];
for (int x=x0; x<x0+w; x++) {
value = voxels[i++];
ints[y*width+x] = (int)value;
}
break;
}
} else {
for (int x=x0; x<x0+w; x++)
setVoxel(x, y, z, voxels[i++]);
}
}
}
}
public void setVoxels(int x0, int y0, int z0, int w, int h, int d, float[] voxels, int channel) {
if (bitDepth!=24) {
setVoxels(x0, y0, z0, w, h, d, voxels);
return;
}
boolean inBounds = x0>=0 && x0+w<=width && y0>=0 && y0+h<=height && z0>=0 && z0+d<=nSlices;
if (voxels==null || voxels.length!=w*h*d)
;
int i = 0;
for (int z=z0; z<z0+d; z++) {
int[] ints = (int[])stack[z];
for (int y=y0; y<y0+h; y++) {
for (int x=x0; x<x0+w; x++) {
int value = inBounds?ints[y*width+x]&0xffffffff:(int)getVoxel(x, y, z);
int color = (int)voxels[i++];
switch (channel) {
case 0: value=(value&0xff00ffff) | ((color&0xff)<<16); break;
case 1: value=(value&0xffff00ff) | ((color&0xff)<<8); break;
case 2: value=(value&0xffffff00) | (color&0xff); break;
}
if (inBounds)
ints[y*width+x] = value;
else
setVoxel(x, y, z, value);
}
}
}
}
public void drawSphere(double radius, int xc, int yc, int zc) {
int diameter = (int)Math.round(radius*2);
double r = radius;
int xmin=(int)(xc-r+0.5), ymin=(int)(yc-r+0.5), zmin=(int)(zc-r+0.5);
int xmax=xmin+diameter, ymax=ymin+diameter, zmax=zmin+diameter;
double r2 = r*r;
r -= 0.5;
double xoffset=xmin+r, yoffset=ymin+r, zoffset=zmin+r;
double xx, yy, zz;
for (int x=xmin; x<=xmax; x++) {
for (int y=ymin; y<=ymax; y++) {
for (int z=zmin; z<=zmax; z++) {
xx = x-xoffset; yy = y-yoffset; zz = z-zoffset;
if (xx*xx+yy*yy+zz*zz<=r2)
setVoxel(x, y, z, 255);
}
}
}
}
public int getBitDepth() {
if (this.bitDepth==0 && stack!=null && stack.length>0)
setBitDepth(stack[0]);
return this.bitDepth;
}
public void setBitDepth(int depth) {
if (size()==0 && (depth==8||depth==16||depth==24||depth==32))
this.bitDepth = depth;
}
public static ImageStack create(int width, int height, int depth, int bitdepth) {
ImageStack stack = IJ.createImage("", width, height, depth, bitdepth).getStack();
if (bitdepth==16 || bitdepth==32) {
stack.min = Double.MAX_VALUE;
stack.max = 0.0;
}
return stack;
}
public static ImageStack create(ImagePlus[] images) {
int w = 0;
int h = 0;
for (int i=0; i<images.length; i++) {
if (images[i].getWidth()>w) w=images[i].getWidth();
if (images[i].getHeight()>h) h=images[i].getHeight();
}
ImageStack stack = new ImageStack(w, h);
stack.init(w, h);
for (int i=0; i<images.length; i++) {
stack.addSlice(images[i].getProcessor());
}
int depth = images[0].getBitDepth();
if (depth==16 || depth==32) {
stack.min = Double.MAX_VALUE;
stack.max = 0.0;
}
return stack;
}
public ImageStack duplicate() {
return crop(0, 0, 0, width, height, size());
}
public ImageStack crop(int x, int y, int z, int width, int height, int depth) {
if (x<0||y<0||z<0||x+width>this.width||y+height>this.height||z+depth>size())
throw new IllegalArgumentException("Argument out of range");
ImageStack stack2 = new ImageStack(width, height, getColorModel());
for (int i=z; i<z+depth; i++) {
ImageProcessor ip2 = this.getProcessor(i+1);
ip2.setRoi(x, y, width, height);
ip2 = ip2.crop();
stack2.addSlice(this.getSliceLabel(i+1), ip2);
}
return stack2;
}
public ImageStack convertToFloat() {
ImageStack stack2 = new ImageStack(width, height, getColorModel());
for (int i=1; i<=size(); i++) {
ImageProcessor ip2 = this.getProcessor(i);
ip2 = ip2.convertToFloat();
stack2.addSlice(this.getSliceLabel(i), ip2);
}
return stack2;
}
int viewers(int inc) {
viewers += inc;
return viewers;
}
public void setOptions(String options) {
if (options==null) return;
signedInt = options.contains("32-bit int");
}
}