Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
TiffDecoder.java
1 package ij.io;
2 import java.io.*;
3 import java.util.*;
4 import java.net.*;
5 
7 public class TiffDecoder {
8 
9  // tags
10  public static final int NEW_SUBFILE_TYPE = 254;
11  public static final int IMAGE_WIDTH = 256;
12  public static final int IMAGE_LENGTH = 257;
13  public static final int BITS_PER_SAMPLE = 258;
14  public static final int COMPRESSION = 259;
15  public static final int PHOTO_INTERP = 262;
16  public static final int IMAGE_DESCRIPTION = 270;
17  public static final int STRIP_OFFSETS = 273;
18  public static final int SAMPLES_PER_PIXEL = 277;
19  public static final int ROWS_PER_STRIP = 278;
20  public static final int STRIP_BYTE_COUNT = 279;
21  public static final int X_RESOLUTION = 282;
22  public static final int Y_RESOLUTION = 283;
23  public static final int PLANAR_CONFIGURATION = 284;
24  public static final int RESOLUTION_UNIT = 296;
25  public static final int SOFTWARE = 305;
26  public static final int DATE_TIME = 306;
27  public static final int COLOR_MAP = 320;
28  public static final int SAMPLE_FORMAT = 339;
29  public static final int METAMORPH1 = 33628;
30  public static final int METAMORPH2 = 33629;
31  public static final int IPLAB = 34122;
32  public static final int NIH_IMAGE_HDR = 43314;
33 
34  //constants
35  static final int UNSIGNED = 1;
36  static final int SIGNED = 2;
37  static final int FLOATING_POINT = 3;
38 
39  //field types
40  static final int SHORT = 3;
41  static final int LONG = 4;
42 
43  private String directory;
44  private String name;
45  private String url;
46  protected RandomAccessStream in;
47  protected boolean debugMode;
48  private boolean littleEndian;
49  private String dInfo;
50  private int ifdCount;
51 
52  public TiffDecoder(String directory, String name) {
53  this.directory = directory;
54  this.name = name;
55  }
56 
57  public TiffDecoder(InputStream in, String name) {
58  directory = "";
59  this.name = name;
60  url = "";
61  this.in = new RandomAccessStream(in);
62  }
63 
64  final int getInt() throws IOException {
65  int b1 = in.read();
66  int b2 = in.read();
67  int b3 = in.read();
68  int b4 = in.read();
69  if (littleEndian)
70  return ((b4 << 24) + (b3 << 16) + (b2 << 8) + (b1 << 0));
71  else
72  return ((b1 << 24) + (b2 << 16) + (b3 << 8) + b4);
73  }
74 
75  int getShort() throws IOException {
76  int b1 = in.read();
77  int b2 = in.read();
78  if (littleEndian)
79  return ((b2 << 8) + b1);
80  else
81  return ((b1 << 8) + b2);
82  }
83 
84  int OpenImageFileHeader() throws IOException {
85  // Open 8-byte Image File Header at start of file.
86  // Returns the offset in bytes to the first IFD or -1
87  // if this is not a valid tiff file.
88  int byteOrder = in.readShort();
89  if (byteOrder==0x4949) // "II"
90  littleEndian = true;
91  else if (byteOrder==0x4d4d) // "MM"
92  littleEndian = false;
93  else {
94  in.close();
95  return -1;
96  }
97  int magicNumber = getShort(); // 42
98  int offset = getInt();
99  return offset;
100  }
101 
102  int getValue(int fieldType, int count) throws IOException {
103  int value = 0;
104  int unused;
105  if (fieldType==SHORT && count==1) {
106  value = getShort();
107  unused = getShort();
108  }
109  else
110  value = getInt();
111  return value;
112  }
113 
114  void getColorMap(int offset, FileInfo fi) throws IOException {
115  byte[] colorTable16 = new byte[768*2];
116  int saveLoc = in.getFilePointer();
117  in.seek(offset);
118  in.readFully(colorTable16);
119  in.seek(saveLoc);
120  fi.lutSize = 256;
121  fi.reds = new byte[256];
122  fi.greens = new byte[256];
123  fi.blues = new byte[256];
124  int j = 0;
125  if (littleEndian)
126  j++;
127  for (int i=0; i<256; i++) {
128  fi.reds[i] = colorTable16[j];
129  fi.greens[i] = colorTable16[512+j];
130  fi.blues[i] = colorTable16[1024+j];
131  j += 2;
132  }
133  fi.fileType = FileInfo.COLOR8;
134  }
135 
136  byte[] getString(int count, int offset) throws IOException {
137  count--; // skip null byte at end of string
138  if (count==0)
139  return null;
140  byte[] bytes = new byte[count];
141  int saveLoc = in.getFilePointer();
142  in.seek(offset);
143  in.readFully(bytes);
144  in.seek(saveLoc);
145  return bytes;
146  }
147 
152  public void decodeImageDescription(byte[] description, FileInfo fi) {
153  if (description.length<7)
154  return;
155  if (ij.IJ.debugMode)
156  ij.IJ.log("Image Description: " + new String(description).replace('\n',' '));
157  if (!new String(description,0,6).equals("ImageJ"))
158  return;
159  fi.description = new String(description);
160  }
161 
162  void decodeNIHImageHeader(int offset, FileInfo fi) throws IOException {
163  int saveLoc = in.getFilePointer();
164 
165  in.seek(offset+12);
166  int version = in.readShort();
167 
168  in.seek(offset+160);
169  double scale = in.readDouble();
170  if (version>106 && scale!=0.0) {
171  fi.pixelWidth = 1.0/scale;
172  fi.pixelHeight = fi.pixelWidth;
173  }
174 
175  // spatial calibration
176  in.seek(offset+172);
177  int units = in.readShort();
178  if (version<=153) units += 5;
179  switch (units) {
180  case 5: fi.unit = "nanometer"; break;
181  case 6: fi.unit = "micrometer"; break;
182  case 7: fi.unit = "mm"; break;
183  case 8: fi.unit = "cm"; break;
184  case 9: fi.unit = "meter"; break;
185  case 10: fi.unit = "km"; break;
186  case 11: fi.unit = "inch"; break;
187  case 12: fi.unit = "ft"; break;
188  case 13: fi.unit = "mi"; break;
189  }
190 
191  // density calibration
192  in.seek(offset+182);
193  int fitType = in.read();
194  int unused = in.read();
195  int nCoefficients = in.readShort();
196  if (fitType==11) {
197  fi.calibrationFunction = 21; //Calibration.UNCALIBRATED_OD
198  fi.valueUnit = "U. OD";
199  } else if (fitType>=0 && fitType<=8 && nCoefficients>=1 && nCoefficients<=5) {
200  switch (fitType) {
201  case 0: fi.calibrationFunction = 0; break; //Calibration.STRAIGHT_LINE
202  case 1: fi.calibrationFunction = 1; break; //Calibration.POLY2
203  case 2: fi.calibrationFunction = 2; break; //Calibration.POLY3
204  case 3: fi.calibrationFunction = 3; break; //Calibration.POLY4
205  case 5: fi.calibrationFunction = 4; break; //Calibration.EXPONENTIAL
206  case 6: fi.calibrationFunction = 5; break; //Calibration.POWER
207  case 7: fi.calibrationFunction = 6; break; //Calibration.LOG
208  case 8: fi.calibrationFunction = 7; break; //Calibration.RODBARD
209  }
210  fi.coefficients = new double[nCoefficients];
211  for (int i=0; i<nCoefficients; i++) {
212  fi.coefficients[i] = in.readDouble();
213  }
214  in.seek(offset+234);
215  int size = in.read();
216  StringBuffer sb = new StringBuffer();
217  if (size>=1 && size<=16) {
218  for (int i=0; i<size; i++)
219  sb.append((char)(in.read()));
220  fi.valueUnit = new String(sb);
221  } else
222  fi.valueUnit = " ";
223  }
224 
225  in.seek(offset+260);
226  int nImages = in.readShort();
227  if(nImages>=2 && (fi.fileType==FileInfo.GRAY8||fi.fileType==FileInfo.COLOR8)) {
228  fi.nImages = nImages;
229  fi.pixelDepth = in.readFloat(); //SliceSpacing
230  int skip = in.readShort(); //CurrentSlice
231  fi.frameInterval = in.readFloat();
232  //ij.IJ.write("fi.pixelDepth: "+fi.pixelDepth);
233  }
234 
235  in.seek(offset+272);
236  float aspectRatio = in.readFloat();
237  if (version>140 && aspectRatio!=0.0)
238  fi.pixelHeight = fi.pixelWidth/aspectRatio;
239 
240  in.seek(saveLoc);
241  }
242 
243  void dumpTag(int tag, int count, int value, FileInfo fi) {
244  String name;
245  switch (tag) {
246  case NEW_SUBFILE_TYPE: name="NewSubfileType"; break;
247  case IMAGE_WIDTH: name="ImageWidth"; break;
248  case IMAGE_LENGTH: name="ImageLength"; break;
249  case STRIP_OFFSETS: name="StripOffsets"; break;
250  case PHOTO_INTERP: name="PhotoInterp"; break;
251  case IMAGE_DESCRIPTION: name="ImageDescription"; break;
252  case BITS_PER_SAMPLE: name="BitsPerSample"; break;
253  case SAMPLES_PER_PIXEL: name="SamplesPerPixel"; break;
254  case ROWS_PER_STRIP: name="RowsPerStrip"; break;
255  case STRIP_BYTE_COUNT: name="StripByteCount"; break;
256  case X_RESOLUTION: name="XResolution"; break;
257  case Y_RESOLUTION: name="YResolution"; break;
258  case RESOLUTION_UNIT: name="ResolutionUnit"; break;
259  case SOFTWARE: name="Software"; break;
260  case DATE_TIME: name="DateTime"; break;
261  case PLANAR_CONFIGURATION: name="PlanarConfiguration"; break;
262  case COMPRESSION: name="Compression"; break;
263  case COLOR_MAP: name="ColorMap"; break;
264  case SAMPLE_FORMAT: name="SampleFormat"; break;
265  case NIH_IMAGE_HDR: name="NIHImageHeader"; break;
266  default: name="???"; break;
267  }
268  String cs = (count==1)?"":", count=" + count;
269  dInfo += " " + tag + ", \"" + name + "\", value=" + value + cs + "\n";
270  //ij.IJ.write(tag + ", \"" + name + "\", value=" + value + cs);
271  }
272 
273  double getRational(int loc) throws IOException {
274  int saveLoc = in.getFilePointer();
275  in.seek(loc);
276  int numerator = getInt();
277  int denominator = getInt();
278  in.seek(saveLoc);
279  if (denominator!=0)
280  return (double)numerator/denominator;
281  else
282  return 0.0;
283  }
284 
285  FileInfo OpenIFD() throws IOException {
286  // Get Image File Directory data
287 
288  int tag, fieldType, count, value;
289  int nEntries = getShort();
290  if (nEntries<1)
291  return null;
292  ifdCount++;
293  FileInfo fi = new FileInfo();
294  for (int i=0; i<nEntries; i++) {
295  tag = getShort();
296  fieldType = getShort();
297  count = getInt();
298  value = getValue(fieldType, count);
299  if (debugMode) dumpTag(tag, count, value, fi);
300  //ij.IJ.write(i+"/"+nEntries+" "+tag + ", count=" + count + ", value=" + value);
301  //if (tag==0) return null;
302  switch (tag) {
303  case IMAGE_WIDTH:
304  fi.width = value;
305  break;
306  case IMAGE_LENGTH:
307  fi.height = value;
308  break;
309  case STRIP_OFFSETS:
310  if (count==1)
311  fi.offset = value;
312  else {
313  int saveLoc = in.getFilePointer();
314  in.seek(value);
315  fi.offset = getInt(); // Assumes contiguous strips
316  in.seek(saveLoc);
317  }
318  break;
319  case PHOTO_INTERP:
320  fi.whiteIsZero = value==0;
321  break;
322  case BITS_PER_SAMPLE:
323  if (count==1) {
324  if (value==8)
325  fi.fileType = FileInfo.GRAY8;
326  else if (value==16) {
327  fi.fileType = FileInfo.GRAY16_UNSIGNED;
328  fi.intelByteOrder = littleEndian;
329  }
330  else if (value==32) {
331  fi.fileType = FileInfo.GRAY32_INT;
332  fi.intelByteOrder = littleEndian;
333  } else if (value==1)
334  fi.fileType = FileInfo.BITMAP;
335  else
336  throw new IOException("Unsupported BitsPerSample: " + value);
337  } else if (count==3) {
338  int saveLoc = in.getFilePointer();
339  in.seek(value);
340  int bitDepth = getShort();
341  if (!(bitDepth==8||bitDepth==16))
342  throw new IOException("ImageJ can only open 8 and 16 bit/channel RGB images");
343  if (bitDepth==16) {
344  fi.intelByteOrder = littleEndian;
345  fi.fileType = FileInfo.RGB48;
346  }
347  in.seek(saveLoc);
348  }
349  break;
350  case SAMPLES_PER_PIXEL:
351  if (value==3 && fi.fileType!=FileInfo.RGB48)
352  fi.fileType = FileInfo.RGB;
353  else if (!(value==1||value==3)) {
354  String msg = "Unsupported SamplesPerPixel: " + value;
355  if (value==4)
356  msg += " \n \n" + "ImageJ cannot open CMYK and RGB+alpha TIFFs";
357  throw new IOException(msg);
358  }
359  break;
360  case X_RESOLUTION:
361  double xScale = getRational(value);
362  if (xScale!=0.0) fi.pixelWidth = 1.0/xScale;
363  break;
364  case Y_RESOLUTION:
365  double yScale = getRational(value);
366  if (yScale!=0.0) fi.pixelHeight = 1.0/yScale;
367  break;
368  case RESOLUTION_UNIT:
369  if (value==1&&fi.unit==null)
370  fi.unit = " ";
371  else if (value==2)
372  fi.unit = "inch";
373  else if (value==3)
374  fi.unit = "cm";
375  break;
376  case PLANAR_CONFIGURATION:
377  if (value==2 && fi.fileType==FileInfo.RGB48)
378  throw new IOException("ImageJ cannot open planar 48-bit RGB images");
379  if (value==2 && fi.fileType==FileInfo.RGB)
380  fi.fileType = FileInfo.RGB_PLANAR;
381  break;
382  case COMPRESSION:
383  if (value!=1 && value!=7) // don't abort with Spot camera compressed (7) thumbnails
384  throw new IOException("ImageJ cannot open compressed TIFF files ("+value+")");
385  break;
386  case COLOR_MAP:
387  if (count==768 && fi.fileType==fi.GRAY8)
388  getColorMap(value, fi);
389  break;
390  case SAMPLE_FORMAT:
391  if (fi.fileType==FileInfo.GRAY32_INT && value==FLOATING_POINT)
392  fi.fileType = FileInfo.GRAY32_FLOAT;
393  if (fi.fileType==FileInfo.GRAY16_UNSIGNED && value==SIGNED)
394  fi.fileType = FileInfo.GRAY16_SIGNED;
395  break;
396  case IMAGE_DESCRIPTION:
397  if (ifdCount==1) {
398  byte[] s = getString(count,value);
399  if (s!=null) decodeImageDescription(s,fi);
400  }
401  break;
402  case METAMORPH1: case METAMORPH2:
403  if (name.indexOf(".STK")>0 || name.indexOf(".stk")>0) {
404  if (tag==METAMORPH2)
405  fi.nImages=count;
406  else
407  fi.nImages=9999;
408  }
409  break;
410  case IPLAB:
411  fi.nImages=value;
412  break;
413  case NIH_IMAGE_HDR:
414  if (count==256)
415  decodeNIHImageHeader(value, fi);
416  break;
417  default:
418  }
419  }
420  fi.fileFormat = fi.TIFF;
421  fi.fileName = name;
422  fi.directory = directory;
423  if (url!=null)
424  fi.url = url;
425  if (debugMode) dInfo += " offset=" + fi.offset + "\n";
426  return fi;
427  }
428 
429 
430  public void enableDebugging() {
431  debugMode = true;
432  }
433 
434 
435  public FileInfo[] getTiffInfo() throws IOException {
436  int ifdOffset;
437  Vector info;
438 
439  if (in==null)
440  in = new RandomAccessStream(new RandomAccessFile(directory + name, "r"));
441  info = new Vector();
442  ifdOffset = OpenImageFileHeader();
443  if (ifdOffset<0) {
444  in.close();
445  return null;
446  }
447  if (debugMode) dInfo = "\n " + name + ": opening\n";
448  while (ifdOffset>0) {
449  in.seek(ifdOffset);
450  FileInfo fi = OpenIFD();
451  //ij.IJ.write(""+fi);
452  if (fi!=null)
453  info.addElement(fi);
454  ifdOffset = getInt();
455  if (debugMode) dInfo += " nextIFD=" + ifdOffset + "\n";
456  if (fi!=null && fi.nImages>1) // ignore extra IFDs in ImageJ and NIH Image stacks
457  ifdOffset = 0;
458  }
459  if (info.size()==0) {
460  in.close();
461  return null;
462  } else {
463  FileInfo[] fi = new FileInfo[info.size()];
464  info.copyInto((Object[])fi);
465  if (debugMode) fi[0].info = dInfo;
466  if (url!=null) {
467  in.seek(0);
468  fi[0].inputStream = in;
469  } else
470  in.close();
471  return fi;
472  }
473  }
474 
475 }