//
// BaseTiffReader.java
//
/*
OME Bio-Formats package for reading and converting biological file formats.
Copyright (C) 2005-@year@ UW-Madison LOCI and Glencoe Software, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package loci.formats.in;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import loci.common.DateTools;
import loci.formats.FormatException;
import loci.formats.MetadataTools;
import loci.formats.meta.MetadataStore;
import ome.xml.model.primitives.PositiveFloat;
import loci.formats.tiff.IFD;
import loci.formats.tiff.IFDList;
import loci.formats.tiff.PhotoInterp;
import loci.formats.tiff.TiffCompression;
import loci.formats.tiff.TiffRational;
/**
* BaseTiffReader is the superclass for file format readers compatible with
* or derived from the TIFF 6.0 file format.
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/bio-formats/src/loci/formats/in/BaseTiffReader.java">Trac</a>,
* <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/in/BaseTiffReader.java;hb=HEAD">Gitweb</a></dd></dl>
*
* @author Curtis Rueden ctrueden at wisc.edu
* @author Melissa Linkert melissa at glencoesoftware.com
*/
public abstract class BaseTiffReader extends MinimalTiffReader {
// -- Constants --
/** Logger for this class. */
private static final Logger LOGGER =
LoggerFactory.getLogger(BaseTiffReader.class);
public static final String[] DATE_FORMATS = {
"yyyy:MM:dd HH:mm:ss",
"dd/MM/yyyy HH:mm:ss.SS",
"MM/dd/yyyy hh:mm:ss.SSS aa",
"yyyyMMdd HH:mm:ss.SSS",
"yyyy/MM/dd HH:mm:ss"
};
// -- Constructors --
/** Constructs a new BaseTiffReader. */
public BaseTiffReader(String name, String suffix) { super(name, suffix); }
/** Constructs a new BaseTiffReader. */
public BaseTiffReader(String name, String[] suffixes) {
super(name, suffixes);
}
// -- Internal BaseTiffReader API methods --
/** Populates the metadata hashtable and metadata store. */
protected void initMetadata() throws FormatException, IOException {
initStandardMetadata();
initMetadataStore();
}
/**
* Parses standard metadata.
*
* NOTE: Absolutely <b>no</b> calls to the metadata store should be made in
* this method or methods that override this method. Data <b>will</b> be
* overwritten if you do so.
*/
protected void initStandardMetadata() throws FormatException, IOException {
if (getMetadataOptions().getMetadataLevel() == MetadataLevel.MINIMUM) {
return;
}
for (int i=0; i<ifds.size(); i++) {
put("PageName #" + i, ifds.get(i), IFD.PAGE_NAME);
}
IFD firstIFD = ifds.get(0);
put("ImageWidth", firstIFD, IFD.IMAGE_WIDTH);
put("ImageLength", firstIFD, IFD.IMAGE_LENGTH);
put("BitsPerSample", firstIFD, IFD.BITS_PER_SAMPLE);
// retrieve EXIF values, if available
if (ifds.get(0).containsKey(IFD.EXIF)) {
IFDList exifIFDs = tiffParser.getExifIFDs();
if (exifIFDs.size() > 0) {
IFD exif = exifIFDs.get(0);
for (Integer key : exif.keySet()) {
int k = key.intValue();
addGlobalMeta(getExifTagName(k), exif.get(key));
}
}
}
TiffCompression comp = firstIFD.getCompression();
put("Compression", comp.getCodecName());
PhotoInterp photo = firstIFD.getPhotometricInterpretation();
String photoInterp = photo.getName();
String metaDataPhotoInterp = photo.getMetadataType();
put("PhotometricInterpretation", photoInterp);
put("MetaDataPhotometricInterpretation", metaDataPhotoInterp);
putInt("CellWidth", firstIFD, IFD.CELL_WIDTH);
putInt("CellLength", firstIFD, IFD.CELL_LENGTH);
int or = firstIFD.getIFDIntValue(IFD.ORIENTATION);
// adjust the width and height if necessary
if (or == 8) {
put("ImageWidth", firstIFD, IFD.IMAGE_LENGTH);
put("ImageLength", firstIFD, IFD.IMAGE_WIDTH);
}
String orientation = null;
// there is no case 0
switch (or) {
case 1:
orientation = "1st row -> top; 1st column -> left";
break;
case 2:
orientation = "1st row -> top; 1st column -> right";
break;
case 3:
orientation = "1st row -> bottom; 1st column -> right";
break;
case 4:
orientation = "1st row -> bottom; 1st column -> left";
break;
case 5:
orientation = "1st row -> left; 1st column -> top";
break;
case 6:
orientation = "1st row -> right; 1st column -> top";
break;
case 7:
orientation = "1st row -> right; 1st column -> bottom";
break;
case 8:
orientation = "1st row -> left; 1st column -> bottom";
break;
}
put("Orientation", orientation);
putInt("SamplesPerPixel", firstIFD, IFD.SAMPLES_PER_PIXEL);
put("Software", firstIFD, IFD.SOFTWARE);
put("Instrument Make", firstIFD, IFD.MAKE);
put("Instrument Model", firstIFD, IFD.MODEL);
put("Document Name", firstIFD, IFD.DOCUMENT_NAME);
put("DateTime", firstIFD, IFD.DATE_TIME);
put("Artist", firstIFD, IFD.ARTIST);
put("HostComputer", firstIFD, IFD.HOST_COMPUTER);
put("Copyright", firstIFD, IFD.COPYRIGHT);
put("NewSubfileType", firstIFD, IFD.NEW_SUBFILE_TYPE);
int thresh = firstIFD.getIFDIntValue(IFD.THRESHHOLDING);
String threshholding = null;
switch (thresh) {
case 1:
threshholding = "No dithering or halftoning";
break;
case 2:
threshholding = "Ordered dithering or halftoning";
break;
case 3:
threshholding = "Randomized error diffusion";
break;
}
put("Threshholding", threshholding);
int fill = firstIFD.getIFDIntValue(IFD.FILL_ORDER);
String fillOrder = null;
switch (fill) {
case 1:
fillOrder = "Pixels with lower column values are stored " +
"in the higher order bits of a byte";
break;
case 2:
fillOrder = "Pixels with lower column values are stored " +
"in the lower order bits of a byte";
break;
}
put("FillOrder", fillOrder);
putInt("Make", firstIFD, IFD.MAKE);
putInt("Model", firstIFD, IFD.MODEL);
putInt("MinSampleValue", firstIFD, IFD.MIN_SAMPLE_VALUE);
putInt("MaxSampleValue", firstIFD, IFD.MAX_SAMPLE_VALUE);
putInt("XResolution", firstIFD, IFD.X_RESOLUTION);
putInt("YResolution", firstIFD, IFD.Y_RESOLUTION);
int planar = firstIFD.getIFDIntValue(IFD.PLANAR_CONFIGURATION);
String planarConfig = null;
switch (planar) {
case 1:
planarConfig = "Chunky";
break;
case 2:
planarConfig = "Planar";
break;
}
put("PlanarConfiguration", planarConfig);
putInt("XPosition", firstIFD, IFD.X_POSITION);
putInt("YPosition", firstIFD, IFD.Y_POSITION);
putInt("FreeOffsets", firstIFD, IFD.FREE_OFFSETS);
putInt("FreeByteCounts", firstIFD, IFD.FREE_BYTE_COUNTS);
putInt("GrayResponseUnit", firstIFD, IFD.GRAY_RESPONSE_UNIT);
putInt("GrayResponseCurve", firstIFD, IFD.GRAY_RESPONSE_CURVE);
putInt("T4Options", firstIFD, IFD.T4_OPTIONS);
putInt("T6Options", firstIFD, IFD.T6_OPTIONS);
int res = firstIFD.getIFDIntValue(IFD.RESOLUTION_UNIT);
String resUnit = null;
switch (res) {
case 1:
resUnit = "None";
break;
case 2:
resUnit = "Inch";
break;
case 3:
resUnit = "Centimeter";
break;
}
put("ResolutionUnit", resUnit);
putInt("PageNumber", firstIFD, IFD.PAGE_NUMBER);
putInt("TransferFunction", firstIFD, IFD.TRANSFER_FUNCTION);
int predict = firstIFD.getIFDIntValue(IFD.PREDICTOR);
String predictor = null;
switch (predict) {
case 1:
predictor = "No prediction scheme";
break;
case 2:
predictor = "Horizontal differencing";
break;
}
put("Predictor", predictor);
putInt("WhitePoint", firstIFD, IFD.WHITE_POINT);
putInt("PrimaryChromacities", firstIFD, IFD.PRIMARY_CHROMATICITIES);
putInt("HalftoneHints", firstIFD, IFD.HALFTONE_HINTS);
putInt("TileWidth", firstIFD, IFD.TILE_WIDTH);
putInt("TileLength", firstIFD, IFD.TILE_LENGTH);
putInt("TileOffsets", firstIFD, IFD.TILE_OFFSETS);
putInt("TileByteCounts", firstIFD, IFD.TILE_BYTE_COUNTS);
int ink = firstIFD.getIFDIntValue(IFD.INK_SET);
String inkSet = null;
switch (ink) {
case 1:
inkSet = "CMYK";
break;
case 2:
inkSet = "Other";
break;
}
put("InkSet", inkSet);
putInt("InkNames", firstIFD, IFD.INK_NAMES);
putInt("NumberOfInks", firstIFD, IFD.NUMBER_OF_INKS);
putInt("DotRange", firstIFD, IFD.DOT_RANGE);
put("TargetPrinter", firstIFD, IFD.TARGET_PRINTER);
putInt("ExtraSamples", firstIFD, IFD.EXTRA_SAMPLES);
int fmt = firstIFD.getIFDIntValue(IFD.SAMPLE_FORMAT);
String sampleFormat = null;
switch (fmt) {
case 1:
sampleFormat = "unsigned integer";
break;
case 2:
sampleFormat = "two's complement signed integer";
break;
case 3:
sampleFormat = "IEEE floating point";
break;
case 4:
sampleFormat = "undefined";
break;
}
put("SampleFormat", sampleFormat);
putInt("SMinSampleValue", firstIFD, IFD.S_MIN_SAMPLE_VALUE);
putInt("SMaxSampleValue", firstIFD, IFD.S_MAX_SAMPLE_VALUE);
putInt("TransferRange", firstIFD, IFD.TRANSFER_RANGE);
int jpeg = firstIFD.getIFDIntValue(IFD.JPEG_PROC);
String jpegProc = null;
switch (jpeg) {
case 1:
jpegProc = "baseline sequential process";
break;
case 14:
jpegProc = "lossless process with Huffman coding";
break;
}
put("JPEGProc", jpegProc);
putInt("JPEGInterchangeFormat", firstIFD, IFD.JPEG_INTERCHANGE_FORMAT);
putInt("JPEGRestartInterval", firstIFD, IFD.JPEG_RESTART_INTERVAL);
putInt("JPEGLosslessPredictors", firstIFD, IFD.JPEG_LOSSLESS_PREDICTORS);
putInt("JPEGPointTransforms", firstIFD, IFD.JPEG_POINT_TRANSFORMS);
putInt("JPEGQTables", firstIFD, IFD.JPEG_Q_TABLES);
putInt("JPEGDCTables", firstIFD, IFD.JPEG_DC_TABLES);
putInt("JPEGACTables", firstIFD, IFD.JPEG_AC_TABLES);
putInt("YCbCrCoefficients", firstIFD, IFD.Y_CB_CR_COEFFICIENTS);
int ycbcr = firstIFD.getIFDIntValue(IFD.Y_CB_CR_SUB_SAMPLING);
String subSampling = null;
switch (ycbcr) {
case 1:
subSampling = "chroma image dimensions = luma image dimensions";
break;
case 2:
subSampling = "chroma image dimensions are " +
"half the luma image dimensions";
break;
case 4:
subSampling = "chroma image dimensions are " +
"1/4 the luma image dimensions";
break;
}
put("YCbCrSubSampling", subSampling);
putInt("YCbCrPositioning", firstIFD, IFD.Y_CB_CR_POSITIONING);
putInt("ReferenceBlackWhite", firstIFD, IFD.REFERENCE_BLACK_WHITE);
// bits per sample and number of channels
int[] q = firstIFD.getBitsPerSample();
int bps = q[0];
int numC = q.length;
// numC isn't set properly if we have an indexed color image, so we need
// to reset it here
if (photo == PhotoInterp.RGB_PALETTE || photo == PhotoInterp.CFA_ARRAY) {
numC = 3;
}
put("BitsPerSample", bps);
put("NumberOfChannels", numC);
}
/**
* Populates the metadata store using the data parsed in
* {@link #initStandardMetadata()} along with some further parsing done in
* the method itself.
*
* All calls to the active <code>MetadataStore</code> should be made in this
* method and <b>only</b> in this method. This is especially important for
* sub-classes that override the getters for pixel set array size, etc.
*/
protected void initMetadataStore() throws FormatException {
LOGGER.info("Populating OME metadata");
// the metadata store we're working with
MetadataStore store = makeFilterMetadata();
MetadataTools.populatePixels(store, this);
IFD firstIFD = ifds.get(0);
// format the creation date to ISO 8601
String creationDate = getImageCreationDate();
String date = DateTools.formatDate(creationDate, DATE_FORMATS);
if (creationDate != null && date == null) {
LOGGER.warn("unknown creation date format: {}", creationDate);
}
creationDate = date;
// populate Image
if (creationDate != null) {
store.setImageAcquiredDate(creationDate, 0);
}
else {
MetadataTools.setDefaultCreationDate(store, getCurrentFile(), 0);
}
if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) {
// populate Experimenter
String artist = firstIFD.getIFDTextValue(IFD.ARTIST);
if (artist != null) {
String firstName = null, lastName = null;
int ndx = artist.indexOf(" ");
if (ndx < 0) lastName = artist;
else {
firstName = artist.substring(0, ndx);
lastName = artist.substring(ndx + 1);
}
String email = firstIFD.getIFDStringValue(IFD.HOST_COMPUTER);
store.setExperimenterFirstName(firstName, 0);
store.setExperimenterLastName(lastName, 0);
store.setExperimenterEmail(email, 0);
store.setExperimenterDisplayName(artist, 0);
store.setExperimenterID(MetadataTools.createLSID("Experimenter", 0), 0);
}
store.setImageDescription(firstIFD.getComment(), 0);
// set the X and Y pixel dimensions
double pixX = firstIFD.getXResolution();
double pixY = firstIFD.getYResolution();
if (pixX > 0) {
store.setPixelsPhysicalSizeX(new PositiveFloat(pixX), 0);
}
if (pixY > 0) {
store.setPixelsPhysicalSizeY(new PositiveFloat(pixY), 0);
}
store.setPixelsPhysicalSizeZ(null, 0);
}
}
/**
* Retrieves the image creation date.
* @return the image creation date.
*/
protected String getImageCreationDate() {
Object o = ifds.get(0).getIFDValue(IFD.DATE_TIME);
if (o instanceof String) return (String) o;
if (o instanceof String[]) return ((String[]) o)[0];
return null;
}
// -- Internal FormatReader API methods - metadata convenience --
// TODO : the 'put' methods that accept primitive types could probably be
// removed, as there are now 'addGlobalMeta' methods that accept
// primitive types
protected void put(String key, Object value) {
if (value == null) return;
if (value instanceof String) value = ((String) value).trim();
addGlobalMeta(key, value);
}
protected void put(String key, int value) {
if (value == -1) return; // indicates missing value
addGlobalMeta(key, value);
}
protected void put(String key, boolean value) {
put(key, new Boolean(value));
}
protected void put(String key, byte value) { put(key, new Byte(value)); }
protected void put(String key, char value) { put(key, new Character(value)); }
protected void put(String key, double value) { put(key, new Double(value)); }
protected void put(String key, float value) { put(key, new Float(value)); }
protected void put(String key, long value) { put(key, new Long(value)); }
protected void put(String key, short value) { put(key, new Short(value)); }
protected void put(String key, IFD ifd, int tag) {
put(key, ifd.getIFDValue(tag));
}
protected void putInt(String key, IFD ifd, int tag) {
put(key, ifd.getIFDIntValue(tag));
}
// -- Internal FormatReader API methods --
/* @see loci.formats.FormatReader#initFile(String) */
protected void initFile(String id) throws FormatException, IOException {
super.initFile(id);
initMetadata();
}
// -- Helper methods --
public static String getExifTagName(int tag) {
return IFD.getIFDTagName(tag);
}
}