// // ZVI_Reader.java // import ij.*; import ij.io.*; import ij.plugin.*; import java.io.*; import java.util.*; import ij.process.*; /** * Imports a Z series (image stack) from a Zeiss ZVI file. This plugin was * created through trial and error (i.e., reverse engineering several example * ZVI files), and most likely does not work with some types of ZVI. * * @author Curtis Rueden ctrueden at wisc.edu * * Modified by Michel Boudinot (mb) 24 Aug 2006. * michel.boudinot at inaf.cnrs-gif.fr * - improved decoding strategy, more robust against zvi file structure diversity. * - new approach to compute the number of channels. * - new image stack construction from file image slices. */ public class ZVI_Reader implements PlugIn { boolean littleEndian = true; private int theC, theZ, numC; //+ (mb) //+ new code private Set C_Set = new HashSet(); // to hold C channel index collection //- (mb) private ImageProcessor ip1; String fileName ; // -- Constants -- /** First few bytes of every ZVI file. */ private static final byte[] ZVI_SIG = { -48, -49, 17, -32, -95, -79, 26, -31 }; /** Block identifying start of useful header information. */ private static final byte[] ZVI_MAGIC_BLOCK_1 = { // 41 00 10 65, 0, 16 }; /** Block identifying second part of useful header information. */ private static final byte[] ZVI_MAGIC_BLOCK_2 = { // 41 00 80 65, 0, -128 }; /** Block identifying third part of useful header information. */ private static final byte[] ZVI_MAGIC_BLOCK_3 = { // 20 00 10 32, 0, 16 }; /** Memory buffer size in bytes, for reading from disk. */ private static final int BUFFER_SIZE = 8192; /** Debugging flag. */ private static final boolean DEBUG = false; // -- PlugIn API methods -- /** Executes the plugin. */ public void run(String arg) { OpenDialog od = new OpenDialog("Open ZVI...", arg); String directory = od.getDirectory(); String fileName = od.getFileName(); if (fileName == null) return; IJ.showStatus("Opening: " + directory + fileName); FileInfo[] fi = null; try { fi = getHeaderInfo(directory, fileName); } catch (Exception e) { IJ.showStatus(""); IJ.showMessage("ZVI Reader", "" + e); return; } if (fi == null) { IJ.showStatus(""); IJ.showMessage("ZVI Reader", "Could not find header information."); return; } Opener opener = new Opener(); ImagePlus imp = opener.openTiffStack(fi); // is there a cleaner way? if (imp == null) { IJ.showStatus(""); IJ.showMessage("ZVI Reader", "Could not extract pixel data."); return; } //setStack(fileName, imp.getStack()); // addition by TjC ImageStack istk = imp.getStack(); int nSlice = istk .getSize(); int sWidth = istk.getWidth(); int sHeight = istk.getHeight(); String stackName=""; //+ (mb) new image stack construction. //- original code removed //- int sliceOffset = 1; //- int sliceNumberPerChannel = nSlice/numC; //- //- //IJ.showMessage("nSlice = " +nSlice ); //- //IJ.showMessage("nSlice/ch = " +sliceNumberPerChannel); //- //- for (int i=1; i<=numC; i++) //- {stackName= fileName + " Ch"+i; //- ImageStack img = new ImageStack (sWidth,sHeight); //- for(int j=0; j theZ = 0 // - channel (4 bytes) -> theC = 0 // - timestep (4 bytes) -> theT = 0 // ZVI_MAGIC_BLOCK_2 <== Start of header information // - Z-slice (4 bytes) -> theZ actual value // - channel (4 bytes) -> theC actual value // - timestep (4 bytes) -> theT actual value // ZVI_MAGIC_BLOCK_3 <== End of header information // ... // // Two consecutive Start of header information ZVI_MAGIC_BLOCK_2 // make test 3) of original decoding strategy fail. The first // null values are taken as theZ, theC and theT values, the // following actual values are ignored. // Parsing the rest of the file appears to be ok. // // New decoding strategy looks for the last header information // ZVI_MAGIC_BLOCK_2 / ZVI_MAGIC_BLOCK_3 to get proper image // slice theZ, theC and theT values. //- original code removed //- long magic3 = findBlock(in, ZVI_MAGIC_BLOCK_3, pos); //- if (magic3 < 0) return null; //- pos = magic3 + ZVI_MAGIC_BLOCK_3.length; //- //- //+ new code // these bytes don't matter in.skipBytes(89); pos += 89; byte[] magic3 = new byte[ZVI_MAGIC_BLOCK_3.length]; in.readFully(magic3); for (int i=0; i= numZ) numZ = theZ + 1; //- if (theC >= numC) numC = theC + 1; //- if (theT >= numT) numT = theT + 1; //+ new code // populate Z, C and T index collections Z_Set.add(new Integer(theZ)); C_Set.add(new Integer(theC)); T_Set.add(new Integer(theT)); //- (mb) // save this image block's position blockList.add(zviBlock); pos += w * h * bytesPerPixel; } if (blockList.isEmpty()) return null; //+ (mb) //+ new code // number of Z, C and T index numZ = Z_Set.size(); numC = C_Set.size(); numT = T_Set.size(); //- (mb) if (numZ * numC * numT != blockList.size()) { IJ.showMessage("ZVI Reader", "Warning: image counts do not match."); } // convert ZVI blocks into single FileInfo object FileInfo[] fi = new FileInfo[blockList.size()]; for (int i=0; i buf.length) len = buf.length; in.readFully(buf, 0, len); for (int i=0; i= 0) in.seek(spot + block.length); return spot; } /** Translates up to the first 4 bytes of a byte array to an integer. */ private static int batoi(byte[] b) { int len = b.length > 4 ? 4 : b.length; int total = 0; for (int i = 0; i < len; i++) { int q = b[i] < 0 ? b[i] + 256 : b[i]; // convert to unsigned int shift = 8 * i; // little endian total += q << shift; } return total; } /** Reads a little-endian integer from the given file. */ private static int readInt(RandomAccessFile fin) throws IOException { byte[] b = new byte[4]; fin.readFully(b); return batoi(b); } //+ (mb) //+ new code /** get channel index theC from slice fileInfo(). */ private static int getChannel(FileInfo theFi) { StringTokenizer st = new StringTokenizer(theFi.info," ,"); int theZ = Integer.parseInt(st.nextToken()); // skip Z index int theC = Integer.parseInt(st.nextToken()); // int theT = Integer.parseInt(st.nextToken()); return theC; } //- (mb) // -- Helper classes -- /** Contains information collected from a ZVI image header. */ private class ZVIBlock { private int theZ, theC, theT; private int width, height; private int alwaysOne; private int bytesPerPixel; private int pixelType; private int bitDepth; private long imagePos; private int numPixels; private int imageSize; private int numChannels; private int bytesPerChannel; public ZVIBlock(int theZ, int theC, int theT, int width, int height, int alwaysOne, int bytesPerPixel, int pixelType, int bitDepth, long imagePos) { this.theZ = theZ; this.theC = theC; this.theT = theT; this.width = width; this.height = height; this.alwaysOne = alwaysOne; this.bytesPerPixel = bytesPerPixel; this.pixelType = pixelType; this.bitDepth = bitDepth; this.imagePos = imagePos; numPixels = width * height; imageSize = numPixels * bytesPerPixel; numChannels = 1; // the second decision is redundant, but left there for further(?) pixel types if ((pixelType == 1) | (pixelType == 8)) { numChannels = 3;} // 1 and 8 are RGB 8-bit and 16-bit else if ((pixelType == 3) | (pixelType == 4)) { numChannels = 1;} // 3 and 4 are GRAY 8-bit and 16-bit if (bytesPerPixel % numChannels != 0) { IJ.showMessage("ZVI Reader", "Warning: incompatible bytesPerPixel (" + bytesPerPixel + ") and numChannels (" + numChannels + "). Assuming grayscale data."); numChannels = 1; } bytesPerChannel = bytesPerPixel / numChannels; // IJ.showMessage("ZVI Reader", "numChannels: " + numChannels + ", bytesPerPixel: " + bytesPerPixel + ", pixelType: " + pixelType + ", bitDepth: " + bitDepth + ", imagePos: " + imagePos); } public String toString() { return "Image header block:\n" + " theZ = " + theZ + "\n" + " theC = " + theC + "\n" + " theT = " + theT + "\n" + " width = " + width + "\n" + " height = " + height + "\n" + " alwaysOne = " + alwaysOne + "\n" + " bytesPerPixel = " + bytesPerPixel + "\n" + " pixelType = " + pixelType + "\n" + " bitDepth = " + bitDepth; } } }