/* Author: George H. Silva Version History: v1.00 - 2005/8/23 - initial release - InstantImager_Reader opens a Packard InstantImager image (*.img, 16-bit unsigned image). - Format information for images was hacked from various data files, so this may not work 100% - feedback welcome. - Incorporated into HandleExtraFileTypes.java to work seamlessly with ImageJ. - See EOF for hacked file format (as Pascal types ;-) v1.01 - 2005/8/26 - fixed code so that "File|Revert" function works - added code for extracting/displaying additional relevant header information v1.02 - 2005/8/29 - removed "public" modifiers from class variables (no need to be global; the original idea was that this class would be useful to just parse image headers) */ import java.io.*; import java.text.*; import ij.*; import ij.io.*; import ij.plugin.*; import ij.process.*; import ij.text.*; public class InstantImager_Reader extends ImagePlus implements PlugIn { // Image information variables - call readHeader to initialize short OffsetParams = 0; short OffsetImage = 0; short ImageWidth = 0; short ImageHeight = 0; short MaxCountsPixel = 0; int ElapsedTime = 0; // Seconds String DetectorSerial = ""; String UserName = ""; String Project = ""; String AcquisitionDate = ""; // public void run(String arg) { OpenDialog od = new OpenDialog("Select an InstantImager File", arg); String directory = od.getDirectory(); String filename = od.getFileName(); if ((filename == null) || (directory == null)) return; IJ.showStatus("Opening: "+directory+filename); open(directory, filename, arg); } void open(String directory, String filename, String arg) { if (!readHeader(directory+filename)) { IJ.log("Failed to open "+directory+filename+" as an InstantImager data file."); return; } // Report details from image header TextWindow tw = new TextWindow("Parameters for "+filename,"InstantImager Parameters",500,300); tw.append("------------------------"); tw.append("Image File: "+directory+filename); tw.append("Detector Serial#: "+DetectorSerial); tw.append("User: "+UserName); tw.append("Project: "+Project); tw.append("Image Dimensions: "+ImageWidth+" x "+ImageHeight); tw.append("Maximum Counts/Pixel: "+MaxCountsPixel); tw.append("Acquisition Date/Time: "+AcquisitionDate); tw.append("Elapsed Time: "+ElapsedTime+" seconds"); // Now open the image file FileInfo fi = new FileInfo(); fi.fileFormat = fi.RAW; fi.directory = directory; fi.fileName = filename; fi.width = ImageWidth; fi.height = ImageHeight; fi.nImages = 1; fi.fileType = FileInfo.GRAY16_UNSIGNED; fi.offset = OffsetImage+1; // File read is zero-based fi.whiteIsZero = true; // // For compatibility with both HandleExtraFileTypes.java and the Plugins|Input-Output menu, // use the following method to open the image... // setFileInfo(fi); // Set properties for "this" FileOpener fo = new FileOpener(fi); // Do this so that File|Revert works - is there a better way? fo.revertToSaved(this); if (arg.equals("")) fo.open(); // Selected from Plugins menu } public boolean readHeader(String filepath) { FileInputStream fis; byte[] numBuf = new byte[4]; byte[] strBuf = new byte[33]; // Max text item size + null byte SimpleDateFormat sdf; // See notes at EOF for file format details try { fis = new FileInputStream(filepath); // Read/verify image header fis.read(numBuf,0,4); if (numBuf[0] != 75 || numBuf[1] != 65 || numBuf[2] != 74 || numBuf[3] != 0) { IJ.showMessage("InstantImager Reader","Missing image data signature."); return false; } fis.skip(5); fis.read(numBuf,0,2); OffsetParams = shortBuf(numBuf); fis.skip(15); fis.read(numBuf,0,2); OffsetImage = shortBuf(numBuf); fis.close(); // Get image parameters; see Pascal structures below fis = new FileInputStream(filepath); fis.skip(OffsetParams); fis.read(strBuf,0,17); UserName = stringBuf(strBuf); fis.read(strBuf,0,33); Project = stringBuf(strBuf); fis.read(numBuf,0,2); ImageWidth = shortBuf(numBuf); fis.read(numBuf,0,2); ImageHeight = shortBuf(numBuf); fis.skip(10); fis.read(numBuf,0,2); MaxCountsPixel = shortBuf(numBuf); fis.skip(195); fis.read(strBuf,0,11); DetectorSerial = stringBuf(strBuf); // Date/Time stored as single bytes in order: sec,min,hour,day,month,year fis.read(strBuf,0,6); sdf = new SimpleDateFormat("EEEE MMMM d, yyyy 'at' H:mm:ss"); sdf.getCalendar().set(strBuf[5]+1900,strBuf[4]-1,strBuf[3],strBuf[2],strBuf[1],strBuf[0]); AcquisitionDate = sdf.format(sdf.getCalendar().getTime()); fis.skip(1); fis.read(numBuf,0,4); ElapsedTime = intBuf(numBuf); fis.close(); } catch (IOException e) { IJ.showMessage("InstantImager Reader","Problem parsing input file for image data parameters."); return false; } return true; } // Helper functions for little-endian conversion // If using JDK 1.5+, could replace this with "reverseBytes" methods short shortBuf(byte[] buf) { // Assumes buf is 2 bytes return (short)((int)(buf[0] & 0xFF) | ((int)(buf[1] & 0xFF) << 8)); } int intBuf(byte[] buf) { // Assumes buf is 4 bytes int result = 0; for (int i=0; i<4; i++) { result |= ((int)(buf[i] & 0xFF) << (i*8)); } return result; } // Convert buffer array to string String stringBuf(byte[] buf) { String result = ""; int i = 0; while (buf[i] != 0) { result += (char)buf[i]; i++; } return result; } } /* // Hacked-out InstantImager file format // Much of this is guess work and/or unknown!!! CONST IMG_SIG = $004A414B; // JAK#0 TYPE TInstantImageHeader = packed record // 72 bytes (minimum?) is fixed dwSignature: DWORD; // Signature for image file byUnknown1: Array[1..5] of Byte; // Unknown wOffsetParams: Word; // Offset from start of file to image parameters byUnknown2: Array[1..15] of Byte; // Unknown wOffsetImage: Word; // Offset from start of file to image data byUnknown3: Array[1..44] of Byte; // Unknown end; // Preset region values are saved as TWICE the actual value, meaning you can // specify values as whole or half numbers only! TImagePresetRegion = packed record wLeft: Word; wTop: Word; wWidth: Word; wHeight: Word; end; // Date/Time stored as simple byte values TImageAcquireDate = packed record bySec: Byte; byMin: Byte; byHour: Byte; byDay: Byte; byMonth: Byte; byYear: Byte; // TWO-DIGIT YEAR! Add 1900 to get actual four-digit year ;-) end; // This starts at wOffsetParams from start of file TInstantImagerParams = packed record // 283 bytes (minimum?) fixed szUser: Array[0..16] of Char; // User name, null terminated szProject: Array[0..32] of Char; // Project comment, null terminated wImageX: Word; // Image width in pixels wImageY: Word; // Image height in pixels byUnknown1: Array[1..10] of Byte; // Unknown wMaxCountsPixel: Word; // Actual maximum counts per pixel collected byUnknown2: Array[1..42] of Byte; // Unknown wPresetMaxCounts: Word; // Preset maximum counts per pixel wUnknown1: Word; // Unknown iprRegion: TImagePresetRegion; // Preset region for data collection; see notes above byUnknown: Array[1..134] of Byte; // Unknown dwPresetTime: DWORD; // Preset data collection time in seconds byUnknown3: Array[1..3] of Byte; // Unknown szSerial: Array[0..10] of Char; // Detector serial number, null terminated iadDateTime: TImageAcquireDate; // Date/Time image was acquired; see above byUnknown4: Byte; // Unknown dwElapsedTime: DWORD; // Actual data collection (elapsed) time in seconds end; */