//
// NikonReader.java
//
/*
LOCI Bio-Formats package for reading and converting biological file formats.
Copyright (C) 2005-@year@ Melissa Linkert, Curtis Rueden, Chris Allan,
Eric Kjellman and Brian Loranger.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Library 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 Library General Public License for more details.
You should have received a copy of the GNU Library 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.*;
import java.util.*;
import loci.formats.*;
/**
* NikonReader is the file format reader for
* Nikon NEF (TIFF) files.
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/NikonReader.java">Trac</a>,
* <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/NikonReader.java">SVN</a></dd></dl>
*
* @author Melissa Linkert linkert at wisc.edu
*/
public class NikonReader extends BaseTiffReader {
// -- Constants --
/** Maximum number of bytes to check for Nikon header information. */
private static final int BLOCK_CHECK_LEN = 16384;
// Tags that give a good indication of whether this is an NEF file.
private static final int EXIF_IFD_POINTER = 34665;
private static final int TIFF_EPS_STANDARD = 37398;
// EXIF IFD tags.
private static final int CFA_REPEAT_DIM = 33421;
private static final int EXPOSURE_TIME = 33434;
private static final int APERTURE = 33437;
private static final int EXPOSURE_PROGRAM = 34850;
private static final int DATE_TIME_DIGITIZED = 36867;
private static final int DATE_TIME_ORIGINAL = 36868;
private static final int EXPOSURE_BIAS_VALUE = 37380;
private static final int MAX_APERTURE_VALUE = 37381;
private static final int METERING_MODE = 37383;
private static final int LIGHT_SOURCE = 37384;
private static final int FLASH = 37385;
private static final int FOCAL_LENGTH = 37386;
private static final int SENSING_METHOD = 37399;
private static final int MAKER_NOTE = 37500;
private static final int USER_COMMENT = 37510;
private static final int SUBSEC_TIME = 37520;
private static final int SUBSEC_TIME_ORIGINAL = 37521;
private static final int SUBSEC_TIME_DIGITIZED = 37522;
private static final int COLOR_SPACE = 40961;
private static final int FILE_SOURCE = 41728;
private static final int SCENE_TYPE = 41729;
private static final int CFA_PATTERN = 41730;
private static final int CUSTOM_RENDERED = 41985;
private static final int EXPOSURE_MODE = 41986;
private static final int WHITE_BALANCE = 41987;
private static final int DIGITAL_ZOOM_RATIO = 41988;
private static final int FOCAL_LENGTH_35MM_FILM = 41989;
private static final int SCENE_CAPTURE_TYPE = 41990;
private static final int GAIN_CONTROL = 41991;
private static final int CONTRAST = 41992;
private static final int SATURATION = 41993;
private static final int SHARPNESS = 41994;
private static final int SUBJECT_DISTANCE_RANGE = 41996;
// Maker Note tags.
private static final int FIRMWARE_VERSION = 1;
private static final int ISO = 2;
private static final int QUALITY = 4;
private static final int MAKER_WHITE_BALANCE = 5;
private static final int SHARPENING = 6;
private static final int FOCUS_MODE = 7;
private static final int FLASH_SETTING = 8;
private static final int FLASH_MODE = 9;
private static final int WHITE_BALANCE_FINE = 11;
private static final int WHITE_BALANCE_RGB_COEFFS = 12;
private static final int FLASH_COMPENSATION = 18;
private static final int TONE_COMPENSATION = 129;
private static final int LENS_TYPE = 131;
private static final int LENS = 132;
private static final int FLASH_USED = 135;
private static final int CURVE = 140;
private static final int COLOR_MODE = 141;
private static final int LIGHT_TYPE = 144;
private static final int HUE = 146;
private static final int CAPTURE_EDITOR_DATA = 3585;
// -- Fields --
/** Offset to the Nikon Maker Note. */
protected int makerNoteOffset;
/** The original IFD. */
protected Hashtable original;
// -- Constructor --
/** Constructs a new Nikon reader. */
public NikonReader() {
super("Nikon NEF (TIFF)", new String[] {"nef", "tif", "tiff"});
}
// -- IFormatReader API methods --
/* @see loci.formats.IFormatReader#isThisType(byte[]) */
public boolean isThisType(byte[] block) {
// adapted from MetamorphReader.isThisType(byte[])
if (block.length < 3) {
return false;
}
if (block.length < 8) {
return true; // we have no way of verifying further
}
boolean little = (block[0] == 0x49 && block[1] == 0x49);
int ifdlocation = DataTools.bytesToInt(block, 4, little);
if (ifdlocation < 0 || ifdlocation + 1 > block.length) {
return false;
}
else {
int ifdnumber = DataTools.bytesToInt(block, ifdlocation, 2, little);
for (int i=0; i<ifdnumber; i++) {
if (ifdlocation + 3 + (i*12) > block.length) {
return false;
}
else {
int ifdtag = DataTools.bytesToInt(block,
ifdlocation + 2 + (i*12), 2, little);
if (ifdtag == TIFF_EPS_STANDARD) {
return true;
}
}
}
return false;
}
}
// -- IFormatHandler API methods --
/* @see loci.formats.IFormatHandler#isThisType(String, boolean) */
public boolean isThisType(String name, boolean open) {
String lname = name.toLowerCase();
if (lname.endsWith(".nef")) return true;
else if (!lname.endsWith(".tif") && !lname.endsWith(".tiff")) return false;
// just checking the filename isn't enough to differentiate between
// Nikon and regular TIFF; open the file and check more thoroughly
return open ? checkBytes(name, BLOCK_CHECK_LEN) : true;
}
// -- Internal BaseTiffReader API methods --
/* @see BaseTiffReader#initStandardMetadata() */
protected void initStandardMetadata() throws FormatException, IOException {
super.initStandardMetadata();
// look for the TIFF_EPS_STANDARD tag
// it should contain version information
try {
short[] version = (short[])
TiffTools.getIFDValue(original, TIFF_EPS_STANDARD);
String v = "";
for (int i=0; i<version.length; i++) v += version[i];
addMeta("Version", v);
}
catch (NullPointerException e) { }
core.littleEndian[0] = true;
try {
core.littleEndian[0] = TiffTools.isLittleEndian(ifds[0]);
}
catch (FormatException f) { }
// now look for the EXIF IFD pointer
try {
int exif = TiffTools.getIFDIntValue(original, EXIF_IFD_POINTER);
if (exif != -1) {
Hashtable exifIFD = TiffTools.getIFD(in, 0, exif);
// put all the EXIF data in the metadata hashtable
if (exifIFD != null) {
Enumeration e = exifIFD.keys();
Integer key;
while (e.hasMoreElements()) {
key = (Integer) e.nextElement();
int tag = key.intValue();
if (tag == CFA_PATTERN) {
byte[] cfa = (byte[]) exifIFD.get(key);
int[] colorMap = new int[cfa.length];
for (int i=0; i<cfa.length; i++) colorMap[i] = (int) cfa[i];
addMeta(getTagName(tag), colorMap);
}
else addMeta(getTagName(tag), exifIFD.get(key));
}
}
}
}
catch (IOException io) { }
catch (NullPointerException e) { }
// read the maker note
byte[] offsets = (byte[]) getMeta("Offset to maker note");
if (offsets != null) makerNoteOffset = offsets[0];
try {
if (makerNoteOffset >= in.length() || makerNoteOffset == 0) return;
Hashtable makerNote = TiffTools.getIFD(in, 0, makerNoteOffset);
if (makerNote != null) {
Enumeration e = makerNote.keys();
Integer key;
while (e.hasMoreElements()) {
key = (Integer) e.nextElement();
int tag = key.intValue();
if (makerNote.containsKey(key)) {
addMeta(getTagName(tag), makerNote.get(key));
}
}
}
}
catch (IOException exc) {
if (debug) trace(exc);
}
}
// -- Internal FormatReader API methods --
/* @see loci.formats.FormatReader#initFile(String) */
protected void initFile(String id) throws FormatException, IOException {
if (debug) debug("NikonReader.initFile(" + id + ")");
super.initFile(id);
in = new RandomAccessStream(id);
if (in.readShort() == 0x4949) in.order(true);
ifds = TiffTools.getIFDs(in);
if (ifds == null) throw new FormatException("No IFDs found");
// look for the SubIFD tag (330);
int offset = 0;
try {
offset = TiffTools.getIFDIntValue(ifds[0], 330, false, 0);
}
catch (FormatException exc) {
if (debug) trace(exc);
long[] array = TiffTools.getIFDLongArray(ifds[0], 330, false);
offset = (int) array[array.length - 1];
}
Hashtable realImage = TiffTools.getIFD(in, 1, offset);
original = ifds[0];
ifds[0] = realImage;
core.imageCount[0] = 1;
Object pattern = getMeta("CFA pattern");
if (pattern != null) {
realImage.put(new Integer(TiffTools.COLOR_MAP), getMeta("CFA pattern"));
}
}
// -- Helper methods --
/** Gets the name of the IFD tag encoded by the given number. */
private String getTagName(int tag) {
switch (tag) {
case CFA_REPEAT_DIM:
return "CFA Repeat Dimensions";
case EXPOSURE_TIME:
return "Exposure Time";
case APERTURE:
return "Aperture";
case EXPOSURE_PROGRAM:
return "Exposure Program";
case DATE_TIME_DIGITIZED:
return "Date/Time Digitized";
case DATE_TIME_ORIGINAL:
return "Date/Time Original";
case EXPOSURE_BIAS_VALUE:
return "Exposure Bias Value";
case MAX_APERTURE_VALUE:
return "Max Aperture Value";
case METERING_MODE:
return "Metering Mode";
case LIGHT_SOURCE:
return "Light Source";
case FLASH:
return "Flash Enabled?";
case FOCAL_LENGTH:
return "Focal length of lens";
case SENSING_METHOD:
return "Sensing Method";
case MAKER_NOTE:
return "Offset to maker note";
case USER_COMMENT:
return "User comment";
case SUBSEC_TIME:
return "Subsec. Sampling for Date/Time field";
case SUBSEC_TIME_ORIGINAL:
return "Subsec. Sampling for original date";
case SUBSEC_TIME_DIGITIZED:
return "Subsec. Sampling for digitized date";
case COLOR_SPACE:
return "Color space";
case FILE_SOURCE:
return "File source";
case SCENE_TYPE:
return "Scene type";
case CFA_PATTERN:
return "CFA pattern";
case CUSTOM_RENDERED:
return "Custom Rendered?";
case EXPOSURE_MODE:
return "Exposure mode";
case WHITE_BALANCE:
return "White Balance";
case DIGITAL_ZOOM_RATIO:
return "Digital Zoom Ratio";
case FOCAL_LENGTH_35MM_FILM:
return "Focal Length of 35mm lens";
case SCENE_CAPTURE_TYPE:
return "Scene Capture Type";
case GAIN_CONTROL:
return "Gain Control";
case CONTRAST:
return "Contrast";
case SATURATION:
return "Saturation";
case SHARPNESS:
return "Sharpness";
case SUBJECT_DISTANCE_RANGE:
return "Subject Distance Range";
case FIRMWARE_VERSION:
return "Firmware version";
case ISO:
return "ISO";
case QUALITY:
return "Quality";
case MAKER_WHITE_BALANCE:
return "White Balance (Maker)";
case SHARPENING:
return "Sharpening";
case FOCUS_MODE:
return "Focus Mode";
case FLASH_SETTING:
return "Flash Setting";
case FLASH_MODE:
return "Flash Mode";
case WHITE_BALANCE_FINE:
return "White Balance Fine";
case WHITE_BALANCE_RGB_COEFFS:
return "White Balance (RGB coefficients)";
case FLASH_COMPENSATION:
return "Flash compensation";
case TONE_COMPENSATION:
return "Tone compensation";
case LENS_TYPE:
return "Lens type";
case LENS:
return "Lens";
case FLASH_USED:
return "Flash used?";
case CURVE:
return "Curve";
case COLOR_MODE:
return "Color mode";
case LIGHT_TYPE:
return "Light type";
case HUE:
return "Hue";
case CAPTURE_EDITOR_DATA:
return "Capture Editor Data";
}
return "" + tag;
}
}