import java.io.*; import java.util.*; // for the Vector and Hashtable classes import java.awt.*; import ij.*; import ij.plugin.*; import ij.process.*; import ij.io.*; import ij.measure.*; //import ij.IJ.*; // ------------------------------------------ // DM3_Reader.java // ------------------------------------------ // This plugin will read DM3 files produced by Gatan Digital Micrograph // Decoding is based on info gleaned from the EMAN project based at Baylor // Made a start using the Analyze_Reader plugin by Guy Williams and // the Biorad_Reader as a base, but not that much remains. // The guts were significantly inspired by the GatanDM3.C and // GatanDM3.h files of the EMAN project source code: // http://ncmi.bcm.tmc.edu/~stevel/EMAN/doc/ // ------------------------------------ // Greg Jefferis, // Dept Biological Sciences, // Stanford University // jefferis@stanford.edu // ------------------------------------------- // as of v1.0.1 030615 // ------------------------------------------- // - reads 16 bit images only, // - correctly parses all simple tags // - Places tags in File/Show Info // including acquisition time / sample date etc. // - allows spatial calibration of images // - sets minimum and maximum intensity // ------------------------------------ // as of v1.1 030615 // - went back to making it an ImagePlus extension // ------------------------------------ // as of v1.2.6 030618 // - Corrected a bad bug - The offset for the image data was off // by +1 // - AND I never told the image opener whether the image data was // in little endian format or not. // - So what I was doing was reading one byte from pixel n and one from pixel n+1! // (which looked more or less like the correct number in big-endian) // - Added my best guess for what ImageJ image type should be set for // the standard data types // - Changed load(), parseDM3() and getDM3FileInfo functions to receive // directory and FileName as parameters - that way load() can be called // directly bypassing the run() function. // ------------------------------------ // v 1.2.7 030621 // - Most functions were previously defined as public, now restricted // to those that might actually get an external call // - read through code once making a few small tidies and improving comments // ------------------------------------ // v 1.2.8 030624 // - Improved ability to set min/max brightness of image according to information // in the Gatan file after bug report from Charles Daghlian at Dartmouth. // This takes place at the end of the load()function. // ------------------------------------ // v 1.2.9 030625 // - Fixed handling of signed 16 bit and 32 bit images by using the // FileInfo.GRAY16_SIGNED and FileInfo.GRAY32_INT constants // & keeping the Calibration object generated by ImageJ when the image // is loaded rather than creating my own from scratch - I had been // throwing away the brightness calibration which ImageJ does on // 16 bit signed images as a result. // & supplying _raw_ values of loVal and hiVal to setMinAndMax() // ------------------------------------ // v 1.3.0 030625 // - Removed calls to functions introduced since Java 1.1 to allow // plugin to run on OS9 Macs with Java 1.1.7 // - However as far as I can see there is a bug in MRJ that prevents // UTF-16 conversion from occurring so unicode strings in the // DM3 tags don't import. Instead there will be a string saying // "couldn't read string blah". This is annoying because the // calibration units are given as a unicode string. // ------------------------------------ // v 1.3.1 030625 // - Fixed UTF-16 conversion bug in MRJ by writing a quick and dirty unicode // reader to be used if the UTF-16 conversion fails. Now correctly // sets the spatial calibration on OS9 // - Another thing that I noticed was that it seemed to be important to // compile with v1.3.1 if one wanted to use the class with v1.1.7 // ------------------------------------ // v 1.3.2 030808 // - Fixed a bug in getDM3FileInfo which meant that the last rather than // the largest image in the DM3 file was chosen // - Fixed a bug in getDM3CalibrationInfo which caused a failure to // recognise "nm" as a valid calibration unit - the problem was caused // by doing (unit == "nm") instead of (unit.equals("nm")) // ------------------------------------ // v 1.3.3 030821 // - Fixed a remaining bug in the image selection routine which now // works for all files that I have available to test. // ------------------------------------ // v 1.3.4 040506 // - Fixed a bug reading in USHORT image data. The problem was that // USHORT data and strings are hard to tell apart. Although Image Data // can be identified categorically, other lumps of data (e.g. LUTs) // scattered throughout the file are harder to spot and will cause problems // if read as a string. Have compromised by reading as string if: // + it isn't image data // + but is an unsigned short array // + of less than 256 bytes // ------------------------------------ // v 1.3.4 051213 (Yes the 2nd v1.3.4 - hadn't noticed a branch!) // - Fixed a bug which prevented units other than µm or nm being passed to // calibration object. Occasioned by a file with units 1/nm // ------------------------------------ // v 1.3.5 051213 // - Adding handling of DIFFRACTION mode images (ie reciprocal space) by // setting FHT property and copying // calibration object. Occasioned by a file with units 1/nm // ------------------------------------ // v 1.3.6 060904 // - Added storing of struct fields to tag list // They are displayed as tag= {1,2,3,4} // This means that Digital Micrograph selection rectangles // can now be identified from Show Info // ------------------------------------ // v 1.3.7 070831 // - debugLevel is now set according to IJ.debugMode // (accesible from Edit ... Options ... Misc) // - Can now open data type 23 (RGBA_UINT8_3_DATA) images // ------------------------------------ // v 1.3.8 080326 // - Fixed a bug in which tag hashes were not cleared when reading a new file // - Small speed improvement by converting tags to String via StringBuffer // - Both thanks to report by Eric Olson at UIUC public class DM3_Reader extends ImagePlus implements PlugIn { // Decide whether to use Gatan's information for determining the min // and maximum brightness thresholds for display or leave to ImageJ // I find Gatan more reliable public boolean useGatanMinMax = true; private boolean littleEndian = true; // default for .dm3 files // nb all tags are written big-endian, it is only the actual data // attached to each tag that may be little-endian (and will be for PC files) //private String directory; //private String fileName; private RandomAccessFile f; // This stream will be used for reading by parseDM3() private FileInfo fi; private String notes = ""; // I will store interesting file info in here // 0=none, 1-3=basic, 4-5=simple, 6-10 verbose private static final int debugLevel = IJ.debugMode?10:0; // the number of the chosen image in the DM3 file // since there apparently may be several - usually at least a thumbnail // I will select the largest image. If there are multiple images // of the same size, the first will be chosen. private int chosenImage = 1; private int curGroupLevel=-1; // Track how deep is the group we are currently reading private static final int MAXDEPTH = 64; // Maximum number of levels of tags private int[] curGroupAtLevelX=new int[MAXDEPTH]; // To track group at current level private String[] curGroupNameAtLevelX=new String[MAXDEPTH]; // To track group name at current level private int[] curTagAtLevelX=new int[MAXDEPTH]; // To track tag number at current level private String curTagName = ""; // the name of the current tag data item // Will use these to store tags private Vector storedTags = new Vector(); private Hashtable tagHash = new Hashtable(); // Set up constants for the different encoded data types used in DM3 files private static final int SHORT = 2; private static final int LONG = 3; private static final int USHORT = 4; private static final int ULONG = 5; private static final int FLOAT = 6; private static final int DOUBLE = 7; private static final int BOOLEAN = 8; private static final int CHAR = 9; private static final int OCTET = 10; private static final int STRUCT = 15; private static final int STRING = 18; private static final int ARRAY = 20; // This the lhs of Image list tags private static final String IMGLIST = "root.ImageList."; // This is the lhs for Document Object List Tags // root.DocumentObjectList.0.AnnotationGroupList.0.AnnotationType = 31 private static final String OBJLIST = "root.DocumentObjectList."; public void run(String arg) { String directory = ""; String fileName = arg; //if (debugLevel>5); if (debugLevel>5) IJ.write("IN:dir = "+directory+", file="+fileName); if ((arg==null) || (arg=="")) { // Choose a file since none specified OpenDialog od = new OpenDialog("Load DM3 File...", arg); fileName = od.getFileName(); if (fileName==null) return; directory = od.getDirectory(); if (debugLevel>5) IJ.write("IF:dir = "+directory+", file="+fileName); } else { // we were sent a filename to open File dest = new File(arg); directory = dest.getParent(); fileName = dest.getName(); if (debugLevel>5) IJ.write("ELSE:dir = "+directory+", file="+fileName); } // Load in the image ImagePlus imp = load(directory, fileName); if (imp==null) return; // Attach the Image Processor setProcessor(fileName, imp.getProcessor()); // Copy the scale info over copyScale(imp); // Copy the Show Info field over setProperty("Info",imp.getProperty("Info")); // and the FHT property (to handle diffraction mode images) if(imp.getProperty("FHT")!=null) setProperty("FHT", imp.getProperty("FHT")); // Show the image if it was selected by the file // chooser, don't if an argument was passed ie // some other ImageJ process called the plugin if (arg.equals("")) show(); } public ImagePlus load(String directory, String fileName) /*throws IOException*/ { if ((fileName == null) || (fileName == "")) return null; if (!directory.endsWith(File.separator)) directory += File.separator; IJ.showStatus("Loading DM3 File: " + directory + fileName); // Clear the lists of tags in which additional info will be stored tagHash.clear(); storedTags.clear(); // Try calling the parse routine try{ parseDM3(directory, fileName);} catch (Exception e) { IJ.showStatus("parseDM3() error"); IJ.showMessage("DM3_Reader", ""+e); return null; } // Make a blank file information object fi = new FileInfo(); // Go and fetch the DM3 specific file Information try {fi=getDM3FileInfo(directory, fileName);} // This is in case of trouble parsing the tag table catch (Exception e) { IJ.showStatus(""); IJ.showMessage("DM3_Reader", "gDM3:"+e); return null; } // Write out Calculated Offset if reqd if(debugLevel>1) IJ.write("Calculated offset = "+fi.offset); if(debugLevel>1) IJ.write("Chosen image = "+chosenImage); // Open the image! FileOpener fo = new FileOpener(fi); ImagePlus imp = fo.open(false); //if(debugLevel>5) if(imp==null) IJ.write("Image load failed!"); // Write out the contents of the storedTags list // and set the value of notes StringBuffer notesBuffer=new StringBuffer(); for (int i = 0; i double hiVal=0.0, loVal=0.0; // Iterate over components of the taglist // looking for the image brightness tag // all this because I don't know how to partially match a hash key for (Enumeration e = tagHash.keys() ; e.hasMoreElements() ;) { String thisElementString = (String) e.nextElement(); if( (thisElementString).endsWith("ImageDisplayInfo.HighLimit")) hiVal = ((Float) tagHash.get(thisElementString)).doubleValue(); if( (thisElementString).endsWith("ImageDisplayInfo.LowLimit")) loVal = ((Float) tagHash.get(thisElementString)).doubleValue(); } // If we found at least one, then set the min max brightness if (hiVal!=0.0 || loVal!=0.0) { // min,max are set through the image processor, so get it ImageProcessor ip = imp.getProcessor(); // set them - nb setMinMax expects raw pixel values // if a brightness calibration is in force then getRawValue() // does the appropriate conversion ip.setMinAndMax(imp.getCalibration().getRawValue(loVal),imp.getCalibration().getRawValue(hiVal)); } } return imp; } void parseDM3(String directory, String fileName) throws IOException { // This reads through the DM3 file, extracting useful tags // which allow one to determine the data offset etc. // alternative way to read from file - allows seeks! // and therefore keeps track of position (use long getFilePointer()) // also has DataInput Interface allowing // reading of specific types f = new RandomAccessFile(directory+fileName,"r"); if(debugLevel>0) IJ.write("Directory = "+directory); if(debugLevel>0) IJ.write("File = "+fileName); // Get the first 3 4byte ints from Header to find out // FileVersion (which must be 3) int fileVersion = f.readInt(); if (fileVersion!=3) throw new IOException("This does not seem to be a DM3 file"); if(debugLevel>5) IJ.write("File Version"+fileVersion); // ... file size int FileSize=f.readInt(); int lE=f.readInt(); if(debugLevel>5) IJ.write("lE "+lE); // ... and whether it was written in little endian (PC) format or not // (Mac and Java output are big endian) if(lE==1) { littleEndian=true; } else { if(lE==0) littleEndian=false; else { throw new IOException("This does not seem to be a DM3 file"); } } // The DM3 file has an unnamed root group which contains everything in the file curGroupNameAtLevelX[0] = "root"; // Set the name of the root group // Now go read it (and all of its sub groups. readTagGroup(); // Close the input stream f.close(); } FileInfo getDM3FileInfo(String directory, String fileName) throws IOException { // this gets the basic file information using the contents of the tag // tables created by parseDM3() // Set the basic file information FileInfo fi = new FileInfo(); fi.fileFormat = fi.RAW; fi.fileName = fileName; fi.directory = directory; // Originally forgot to do this - tells ImageJ what endian form the actual // image data is in fi.intelByteOrder=littleEndian; chosenImage = 0; // Look for largest Image and assume that is the one we want int i=0; // nb the first image is image = 0 long largestDataSizeSoFar=0; // Iterate over images keeping a note of the largest image so far while (true) { // The specific part of the key we are looking for String rString=".ImageData.Data.Size"; if(debugLevel>1) IJ.write("Looking for:"+IMGLIST+i+rString); // Can we find information for image i if(tagHash.containsKey(IMGLIST+i+rString)) { if(debugLevel>1) IJ.write("Found:"+IMGLIST+i+rString); // how big is this image? long dataSize = ((Long) tagHash.get(IMGLIST+i+rString)).longValue(); if(debugLevel>1) IJ.write("Current Data Size"+dataSize); // Is it the largest so far? if(dataSize>largestDataSizeSoFar) { // Choose this image largestDataSizeSoFar=dataSize; if(debugLevel>1) IJ.write("New Largest Data Size:"+largestDataSizeSoFar); chosenImage=i; if(debugLevel>1) IJ.write("New Chosen Image:"+chosenImage); } i++; // move on to the next image } else { break; // we ran out of images } } /* Here are the ImageData.DataType definitions from GatanDM3.h class DataType { public: enum Type { 0= NULL_DATA, 1= SIGNED_INT16_DATA, ... REAL4_DATA, COMPLEX8_DATA, OBSELETE_DATA, PACKED_DATA, UNSIGNED_INT8_DATA, SIGNED_INT32_DATA, RGB_DATA, SIGNED_INT8_DATA, UNSIGNED_INT16_DATA, UNSIGNED_INT32_DATA, REAL8_DATA, COMPLEX16_DATA, BINARY_DATA, RGB_UINT8_0_DATA, RGB_UINT8_1_DATA, RGB_UINT16_DATA, RGB_FLOAT32_DATA, RGB_FLOAT64_DATA, RGBA_UINT8_0_DATA, RGBA_UINT8_1_DATA, RGBA_UINT8_2_DATA, RGBA_UINT8_3_DATA, RGBA_UINT16_DATA, RGBA_FLOAT32_DATA, RGBA_FLOAT64_DATA, POINT2_SINT16_0_DATA, POINT2_SINT16_1_DATA, POINT2_SINT32_0_DATA, POINT2_FLOAT32_0_DATA, RECT_SINT16_1_DATA, RECT_SINT32_1_DATA, RECT_FLOAT32_1_DATA, RECT_FLOAT32_0_DATA, SIGNED_INT64_DATA, UNSIGNED_INT64_DATA, LAST_DATA }; }; */ // OK pick the DataType int dataType = ((Integer) tagHash.get(IMGLIST+chosenImage+".ImageData.DataType")).intValue(); // I have made my best guess for types 1-14 // ie SIGNED_INT16_DATA to BINARY_DATA // but I don't know how to implement the remainder switch(dataType){ case 1: // SIGNED_INT16_DATA fi.fileType=FileInfo.GRAY16_SIGNED; break; case 10: // UNSIGNED_INT16_DATA fi.fileType=FileInfo.GRAY16_UNSIGNED; break; case 2: // REAL4_DATA fi.fileType=FileInfo.GRAY32_FLOAT; break; //case 9: // or SIGNED_INT8_DATA // NB ImageJ only handles unsigned ints - initially was treating // these as unsigned, but in the end decided to remove for safety case 6: // UNSIGNED_INT8_DATA fi.fileType=FileInfo.GRAY8; break; case 7: // SIGNED_INT32_DATA fi.fileType=FileInfo.GRAY32_INT; break; case 11: // UNSIGNED_INT32_DATA fi.fileType=FileInfo.GRAY32_UNSIGNED; break; case 8: // RGB_DATA fi.fileType=FileInfo.RGB; break; case 14: // BINARY_DATA fi.fileType=FileInfo.BITMAP; break; case 23: // RGBA_UINT8_3_DATA // NB it is uncertain if this data type corresponds exactly to ImageJ's ARGB // A definitely comes first but the RGB values could be scrambled // (since they were all equal on my test image) fi.fileType=FileInfo.ARGB; break; default: throw new IOException("Unimplemented ImageData dataType="+dataType+" in DM3 file. See getDM3FileInfo() for details"); } // Get the dimensions of the image for the chosen image // I'm assuming they are ordered width then height fi.width = ((Integer) tagHash.get(IMGLIST+chosenImage+".ImageData.Dimensions.0")).intValue(); fi.height = ((Integer) tagHash.get(IMGLIST+chosenImage+".ImageData.Dimensions.1")).intValue(); // Get the offset of the Image Data for chosen image fi.offset = ((Long) tagHash.get(IMGLIST+chosenImage+".ImageData.Data.Offset")).intValue(); return fi; } Calibration getDM3CalibrationInfo(Calibration cal){ // get the spatial calibration information // could also do brightness // (actually a calibration fn is applied by ImageJ according // to the image Type - GRAY16_SIGNED has 32768 removed in calibration // Figure out what the units are - need to check if nm is correct and // if other units are likely // also will µm get corrupted? may be necessary to do a unicode comparison String unit = (String) tagHash.get(IMGLIST+chosenImage+".ImageData.Calibrations.Dimension.0.Units"); // Reciprocal space images - return the original unit - reciprocal // space will be handled by setting the FHT image property if (unit.startsWith("1/")) unit=unit.substring(2); if (unit.equals("µm")){ cal.setUnit("micron"); } else { cal.setUnit(unit); } if(debugLevel>0) IJ.write("Calibration unit: "+unit); cal.pixelWidth = ((Float) tagHash.get(IMGLIST+chosenImage+".ImageData.Calibrations.Dimension.0.Scale")).doubleValue(); cal.pixelHeight = ((Float) tagHash.get(IMGLIST+chosenImage+".ImageData.Calibrations.Dimension.1.Scale")).doubleValue(); // not yet implemented stacks - if they exist //cal.pixelDepth = return cal; } int readTagGroup() throws IOException { curGroupLevel+=1; // Go down a level since this is a new group curGroupAtLevelX[curGroupLevel]++; // Increment the group counter at this level // Set the number of current tag at this level to -1 // since the readTagEntry routine pre-increments // ie the first tag will be labelled tag 0 curTagAtLevelX[curGroupLevel]=-1; if(debugLevel>5) IJ.write("rTG: Current Group Level: "+curGroupLevel); int isSorted=f.readByte(); int isOpen=f.readByte(); int nTags=f.readInt(); if(debugLevel>5) IJ.write("rTG: Iterating over the "+nTags+" tag entries in this group"); // Iterate over the number of Tag Entries in this group for( int i = 0; i5) { IJ.write(curGroupLevel+"|"+makeGroupString()+": Tag label = "+tagLabel); } else if (debugLevel>0){ IJ.write(curGroupLevel+": Tag label = "+tagLabel); } // Figure out if the tag was data or a new group if (isData==21) { // this tag entry is data // OK settle what this piece of data will be called curTagName = new String(makeGroupNameString()+"."+tagLabel); // now get it readTagType(); } else { //this tag entry is a tag group // Slightly ugly that this can't be done in readTagGroup curGroupNameAtLevelX[curGroupLevel+1]=tagLabel; // Store the name of the group at the new level readTagGroup(); // which will actually increment curGroupLevel } return 1; }; String makeGroupNameString() { // A utility function: // Produces a string which is the concatenation of the current group names String tString = new String(curGroupNameAtLevelX[0]); for (int i=1; i<=curGroupLevel;i++){ tString += "."+curGroupNameAtLevelX[i]; } return tString; } int readTagType() throws IOException { int Delim=f.readInt(); // Should always start with %%%% if (Delim!=0x25252525) throw new IOException("Tag Type delimiter not %%%%"); // This is redundant info, so just ignore it. int nInTag=f.readInt(); readAnyData(); return 1; }; int readAnyData() throws IOException { // Higher level function which dispatches to functions // handling specific data types // This specifies what kind of type we are dealing with // eg short, long, struct, array etc. int encodedType = f.readInt(); // Figure out the size of the encodedType int etSize = encodedTypeSize(encodedType); if(debugLevel>5) IJ.write("rAnD, "+hexPosition()+": Tag Type = "+encodedType+", Tag Size = "+etSize); if(etSize>0){ // must be a regular data type, so read it and store a tag for ir storeTag( curTagName,readNativeData(encodedType,etSize) ); } // OK then, perhaps it's an array, struct or string. else if (encodedType==STRING) // String { // nb readStringData will also store tags internally int stringSize = f.readInt(); readStringData(stringSize); } else if (encodedType==STRUCT) // Struct { // This now stores fields (in curly braces) but does not store // field names. In fact the code will be break for non-zero // field names. Vector structTypes = readStructTypes(); readStructData(structTypes); } else if (encodedType==ARRAY) // Array { // This only stores a tag which I defined myself // to indicate the size of data chunks that are skipped Vector arrayTypes=readArrayTypes(); readArrayData(arrayTypes); } else { throw new IOException("rAnD, 0x"+hexPosition()+": Can't understand encoded type"); } return 1; } Object readNativeData(int encodedType,int etSize) throws IOException { // Does the actual reading of ordinary data types // since it starts as an object, it is not tied to a particular // data type Object val=null; if(encodedType==SHORT){ //short val = new Short(blreadShort()); } else if(encodedType==LONG){ //long val = new Integer(blreadInt()); } else if(encodedType==USHORT){ //u short val = new Short(blreadUShort()); } else if(encodedType==ULONG){ //u long val = new Integer(blreadInt()); } else if(encodedType==FLOAT){ //float val = new Float(blreadFloat()); } else if(encodedType==DOUBLE){ //double val = new Double(blreadDouble()); } else if(encodedType==BOOLEAN){ //boolean if (f.readByte()==0){ val = new Boolean(false); } else { val = new Boolean(true); } } else if(encodedType==CHAR){ //char val = new Character( (char) f.readByte() ); } else if(encodedType==OCTET){ //octet // what's the difference? val = new Byte( f.readByte() ); } else { // Not a known data type throw new IOException("rND, 0x"+hexPosition()+": Unknown data type "+encodedType); } // Print out the value if necessary if(debugLevel>3){ IJ.write("rND, 0x"+hexPosition()+": "+val); } else if(debugLevel>0) { IJ.write(""+val); } return val; } String readStringData(int stringSize) throws IOException { // Does the actual reading of string data types // These should be written as Unicode which can be directly // converted by the String constructor if(stringSize<=0) return new String(""); // Read the string data into a temporary byte buffer. byte[] temp = new byte[stringSize]; f.read(temp,0,stringSize); // Now convert these unicode bytes into a real string String rString; // Note that I can't get UTF encoding to work on MRJ 2.2.5 = // JDK 1.1.7 even though I think it should // so I have put together a kludge if(littleEndian){ // Use UTF-16LE encoding (this seems to fail with MacOS 9 Java 1.1.7) try { rString=new String(temp,"UTF-16LE"); } catch (Exception e) { // Manual conversion of the string if the above fails rString=""; for(int i=0;i0) IJ.write("StringVal: "+rString); // Store the value of this tag storeTag(curTagName,rString); return rString; } Vector readArrayTypes() throws IOException { // Figures out the data types in an array data type // complicated by the fact that the array type could be a struct! // Don't know if this will behave for arrays of strings or arrays int arrayType=f.readInt(); Vector itemTypes = new Vector(); if (arrayType==STRUCT) { // ie a Struct itemTypes = readStructTypes(); } else if (arrayType==ARRAY) { // ie a sub array itemTypes = readArrayTypes(); // not sure if this will work! } else { // assume its an array of simple types // add changed to addElement for Java 1.1.7 compatibility itemTypes.addElement(new Integer(arrayType)); } return itemTypes; } int readArrayData(Vector arrayTypes) throws IOException { // Reads in array data // First thing to do is get number of array elements int arraySize=f.readInt(); if(debugLevel>3) IJ.write("rArD, 0x"+hexPosition()+": Reading array of size = "+arraySize); // Now figure out the total width of each element in the array // nb these elements can have subelements if the array is an array // of STRUCTS etc. int itemSize=0; // Iterate over every type in arrayTypes - there may be more than // one type if this is an array of structs or arrays. int encodedType=0; for (int i = 0; i < arrayTypes.size(); i++) { // cast the ith Vector element to Integer and then get a regular int encodedType = ((Integer) arrayTypes.elementAt(i)).intValue(); int etSize=encodedTypeSize(encodedType); // Now add that size to our running total itemSize+=etSize; // For debugging if(debugLevel>5) IJ.write("rArD: Tag Type = "+encodedType+", Tag Size = "+etSize); //readNativeData(encodedType,etSize); } if(debugLevel>5) IJ.write("rArD: Array Item Size = "+itemSize); // OK now figure out what to do with this array // this would be the buffer size needed to accommodate ot long bufSize = (long) arraySize * (long) itemSize; // If this isn't image data but is an unsigned short array // of less than 256 bytes then it is probably a string if(!curTagName.endsWith("ImageData.Data") && arrayTypes.size() == 1 && encodedType == USHORT && arraySize <256) { // read in as string String val = readStringData((int) bufSize); } else { // treat as binary data // Make up my own tags to indicate data size storeTag(curTagName+".Size",new Long(bufSize)); // and current offset // nb for a while I had offset + 1but this was wrong! // and gave a peculiar staircase histogram because what I had // ended up doing was reading one byte each from a pair of pixels // rather than 2 bytes from a single pixel. Ugh! storeTag(curTagName+".Offset",new Long(f.getFilePointer())); // then go ahead and skip bufSize bytes from current position // without trying to read this data f.seek( f.getFilePointer()+bufSize); } return 1; } Vector readStructTypes() throws IOException { // Figures out the data types in a struct if(debugLevel>3) IJ.write("Reading Struct Types at Pos = "+f.getFilePointer()+", 0x"+hexPosition()); // nb GatanDM3 has longs - I think C++ long = 4 bytes, so use Java int int structNameLength=f.readInt(); int nFields=f.readInt(); if(debugLevel>5) IJ.write("nFields = "+nFields); if (nFields>100) throw new IOException("Too many fields"); Vector fieldTypes = new Vector(); int nameLength = 0; for (int i = 0; i10) IJ.write(i+"th namelength = "+nameLength); int fieldType=f.readInt(); // add changed to addElement for Java 1.1.7 compatibility fieldTypes.addElement(new Integer(fieldType)); } return fieldTypes; } int readStructData(Vector structTypes) throws IOException { // Reads in struct data based on the type info in structTypes String structAsString=""; for (int i = 0; i < structTypes.size(); i++) { Integer iTagType = (Integer) structTypes.elementAt(i); int encodedType = iTagType.intValue(); int etSize=encodedTypeSize(encodedType); // For debugging if(debugLevel>5) IJ.write("Tag Type = "+encodedType+", Tag Size = "+etSize); // OK now get the data structAsString+=readNativeData(encodedType,etSize); // Add a comma to separate values unless this is the last entry if(i+1!=structTypes.size()) structAsString+=","; } storeTag(curTagName,"{"+structAsString+"}"); return 1; } int encodedTypeSize(int encodedType){ // returns the size in bytes of the data type // 030614 Replaced type numbers based on GatanDM3.h // so -1 will be the value returned for an unrecognised type // (which could include ARRAYs, STRUCTs, STRINGs) int width=-1; switch(encodedType){ case 0: // blank field? Do I need this? width = 0; break; case BOOLEAN: // boolean: data size = 1 case CHAR: // char: data size = 1 case OCTET: // octet: data size = 1 width=1; break; case SHORT: // 1. short: data size = 2 case USHORT: // 3. unsigned short: data size = 2 width=2; break; case LONG: // 2. long: data size = 4 case ULONG: // 4. unsigned long: data size = 4 case FLOAT: // 5. float: data size = 4 width=4; break; case DOUBLE: // double: data size = 8 width=8; break; } return(width); } // Store the value and key of the tag that we have just // read in a table void storeTag( String tagName, Object tagValue){ // add changed to addElement for Java 1.1.7 compatibility storedTags.addElement(new String(tagName+" = "+tagValue)); tagHash.put(tagName,tagValue); } // ******************************************************** // the bl methods will check value of littleEndian and read // from the RandomAccessFile f accordingly. // (bl for big/little - ie can cope with either endian format) // ******************************************************** short blreadShort() throws IOException { if (!littleEndian) return f.readShort(); byte b1 = f.readByte(); byte b2 = f.readByte(); return ( (short) (((b2 & 0xff) << 8) | (b1 & 0xff)) ); } short blreadUShort() throws IOException // Identical to blreadShort - is this correct? // not really - java uses signed shorts, but I think it's // too complicated to try and implement unsigned // In principle, one should add 65536 to negative values // to convert, but then they would have to be stored as 4 byte ints // or something. { if (!littleEndian) return (short) f.readUnsignedShort(); byte b1 = f.readByte(); byte b2 = f.readByte(); return ( (short) (((b2 & 0xff) << 8) | (b1 & 0xff)) ); } int blreadInt() throws IOException { if (!littleEndian) return f.readInt(); byte b1 = f.readByte(); byte b2 = f.readByte(); byte b3 = f.readByte(); byte b4 = f.readByte(); return ( (((b4 & 0xff) << 24) | ((b3 & 0xff) << 16) | ((b2 & 0xff) << 8) | (b1 & 0xff)) ); } long blreadLong() throws IOException // New fn, not quite sure if the little endian version is correct // OK Corrected now as far as I can tell { if (!littleEndian) return f.readLong(); int i1=blreadInt(); int i2=blreadInt(); // need to convert intermediates to long explicitly since the standard // is presumably just to work with them as int return ( ((long) (i2 & 0xffffffff) << 32) | (long) (i1 & 0xffffffff) ); } double blreadDouble() throws IOException // new fn to read 8 byte doubles using blreadLong as a base { if (!littleEndian) return f.readDouble(); long orig = blreadLong(); return (Double.longBitsToDouble(orig)); } float blreadFloat() throws IOException { if (!littleEndian) return f.readFloat(); int orig = blreadInt(); return (Float.intBitsToFloat(orig)); } // used to read in field labels String readString(int n) throws IOException{ // not sure if this readString limit is sensible or necessary if(n>2000) throw new IOException("Can't handle strings longer than 2000 chars, n = "+n+" at pos = "+f.getFilePointer()); byte[] temp = new byte[n]; f.read(temp,0,n); return new String(temp); } String hexPosition() throws IOException { // Utility fn to return current file position in hex return ( Long.toHexString( f.getFilePointer() ) ); } }