package edu.stanford.rsl.conrad.io; // Nrrd_Writer // ----------- // ImageJ plugin to save a file in Gordon Kindlmann's NRRD // or 'nearly raw raster data' format, a simple format which handles // coordinate systems and data types in a very general way // See http://teem.sourceforge.net/nrrd/ // and http://flybrain.stanford.edu/nrrd/ // (c) Gregory Jefferis 2007 // Department of Zoology, University of Cambridge // jefferis@gmail.com // All rights reserved // Source code released under Lesser Gnu Public License v2 // v0.1 2007-04-02 // - First functional version can write single channel image (stack) // to raw/gzip encoded monolithic nrrd file // - Writes key spatial calibration information including // spacings, centers, units, axis mins // TODO // - Support for multichannel images, time data // - option to write a detached header instead of detached nrrd file // NB this class can be used to create detached headers for other file types // See import ij.IJ; import ij.ImagePlus; import ij.WindowManager; import ij.io.FileInfo; import ij.io.ImageWriter; import ij.io.SaveDialog; import ij.measure.Calibration; import java.io.*; import java.util.Date; import java.util.zip.GZIPOutputStream; import edu.stanford.rsl.conrad.utils.CONRAD; public class NrrdFileWriter { private static final String plugInName = "Nrrd Writer"; private static final String noImages = plugInName+"...\n"+ "No images are open."; private static final String supportedTypes = plugInName+"..." + "Supported types:\n\n" + "32-bit Grayscale float : FLOAT\n" + "(32-bit Grayscale integer) : LONG\n" + "16-bit Grayscale integer: INT\n" + "(16-bit Grayscale unsigned integer) : UINT\n"+ "8-bit Grayscale : BYTE\n"+ "8-bit Colour LUT (converted to greyscale): BYTE\n"; public static final int NRRD_VERSION = 4; private String imgTypeString=null; String nrrdEncoding="raw"; // See http://teem.sourceforge.net/nrrd/format.html#centers static final String defaultNrrdCentering="node"; String setNrrdEncoding(String enc) throws IOException { enc=enc.toLowerCase(); if (enc.equals("raw")) nrrdEncoding="raw"; else if (enc.equals("gz") || enc.equals("gzip")) nrrdEncoding="gzip"; else if (enc.equals("bz2") || enc.equals("bzip2")) throw new IOException("bzip2 encoding not yet supported"); else if (enc.equals("txt") || enc.equals("text") || enc.equals("ascii")) throw new IOException("text encoding not yet supported"); else if (enc.equals("hex") ) throw new IOException("hex encoding not yet supported"); else throw new IOException("Unknown encoding "+enc); return nrrdEncoding; } public void run(String arg) { ImagePlus imp = WindowManager.getCurrentImage(); if (imp == null) { IJ.showMessage(noImages); return; } String name = arg; if (arg == null || arg.equals("")) { name = imp.getTitle(); } SaveDialog sd = new SaveDialog(plugInName+"...", name, ".nrrd"); String file = sd.getFileName(); if (file == null) return; String directory = sd.getDirectory(); save(imp, directory, file); } public void save(ImagePlus imp, String directory, String file) { if (imp == null) { IJ.showMessage(noImages); return; } FileInfo fi = imp.getFileInfo(); // Make sure that we can save this kind of image if(imgTypeString==null) { imgTypeString=imgType(fi.fileType); if (imgTypeString.equals("unsupported")) { IJ.showMessage(supportedTypes); return; } } // Set the fileName stored in the file info record to the // file name that was passed in or chosen in the dialog box fi.fileName=file; fi.directory=directory; // Actually write out the image try { writeImage(fi,imp.getCalibration()); } catch (IOException e) { IJ.error("An error occured writing the file.\n \n" + e); IJ.showStatus(""); } } void writeImage(FileInfo fi, Calibration cal) throws IOException { FileOutputStream out = new FileOutputStream(new File(fi.directory, fi.fileName)); // First write out the full header Writer bw = new BufferedWriter(new OutputStreamWriter(out)); // Blank line terminates header bw.write(makeHeader(fi,cal)+"\n"); // Flush rather than close bw.flush(); // Then the image data ImageWriter writer = new ImageWriter(fi); if(nrrdEncoding.equals("gzip")) { GZIPOutputStream zStream = new GZIPOutputStream(new BufferedOutputStream( out )); writer.write(zStream); zStream.close(); } else { writer.write(out); out.close(); } IJ.showStatus("Saved "+ fi.fileName); } public static String makeDetachedHeader(FileInfo fi,Calibration cal, boolean withDataFile) { // this static method can also be used externally to generate // a basic nrrd detached header // Right now it will only work for single channel images // NB You can add further fields to this basic header but // You MUST add your own blank line at the end StringWriter out=new StringWriter(); out.write(makeHeader(fi,cal)); out.write("byte skip: "+(fi.longOffset>0?fi.longOffset:fi.offset)+"\n"); if(withDataFile) out.write("data file: "+fi.fileName+"\n"); return out.toString(); } public static String makeHeader(FileInfo fi,Calibration cal) { // NB You can add further fields to this basic header but // You MUST add your own blank line at the end // See http://teem.sourceforge.net/nrrd/format.html /* * type: uchar * dimension: 3 * sizes: 3 640 480 * encoding: raw */ StringWriter out=new StringWriter(); out.write("NRRD000"+NRRD_VERSION+"\n"); out.write("# Created by Nrrd_Writer at "+(new Date())+"\n"); // Fetch and write the data type out.write("type: "+imgType(fi.fileType)+"\n"); // Fetch and write the encoding out.write("encoding: "+getEncoding(fi)+"\n"); if(fi.intelByteOrder) out.write("endian: little\n"); else out.write("endian: big\n"); int dimension=(fi.nImages==1)?2:3; out.write("dimension: "+dimension+"\n"); out.write(dimmedLine("sizes",dimension,fi.width+"",fi.height+"",fi.nImages+"")); if(cal!=null) out.write(dimmedLine("spacings",dimension,cal.pixelWidth+"",cal.pixelHeight+"",cal.pixelDepth+"")); // GJ: It's my understanding that ImageJ operates on a 'node' basis // See http://teem.sourceforge.net/nrrd/format.html#centers out.write(dimmedLine("centers",dimension,defaultNrrdCentering,defaultNrrdCentering,"node")); String units; if(cal!=null) units=cal.getUnit(); else units=fi.unit; if(units.equals("�m")) units="microns"; if(!units.equals("")) out.write(dimmedQuotedLine("units",dimension,units,units,units)); // Only write axis mins if origin info has at least one non-zero // element if(cal!=null && (cal.xOrigin!=0 || cal.yOrigin!=0 || cal.zOrigin!=0) ) { out.write(dimmedLine("axis mins",dimension,(-cal.xOrigin*cal.pixelWidth)+"", (-cal.yOrigin*cal.pixelHeight)+"",(-cal.zOrigin*cal.pixelDepth)+"")); } return out.toString(); } public static String imgType(int fiType) { switch (fiType) { case FileInfo.GRAY32_FLOAT: return "float"; case FileInfo.GRAY32_INT: return "int32"; case FileInfo.GRAY32_UNSIGNED: return "uint32"; case FileInfo.GRAY16_SIGNED: return "int16"; case FileInfo.GRAY16_UNSIGNED: return "uint16"; case FileInfo.COLOR8: case FileInfo.GRAY8: return "uint8"; default: return "unsupported"; } } public static String getEncoding(FileInfo fi) { NrrdFileInfo nfi; if (IJ.debugMode) CONRAD.log("fi :"+fi); try { nfi=(NrrdFileInfo) fi; if (IJ.debugMode) CONRAD.log("nfi :"+nfi); if(nfi.encoding!=null && !nfi.encoding.equals("")) return (nfi.encoding); } catch (Exception e) { } switch(fi.compression) { case NrrdFileInfo.GZIP: return("gzip"); case NrrdFileInfo.BZIP2: return null; default: break; } // These aren't yet supported switch(fi.fileFormat) { case NrrdFileInfo.NRRD_TEXT: case NrrdFileInfo.NRRD_HEX: return(null); default: break; } // The default! return "raw"; } private static String dimmedQuotedLine(String tag,int dimension,String x1,String x2,String x3) { x1="\""+x1+"\""; x2="\""+x2+"\""; x3="\""+x3+"\""; return dimmedLine(tag, dimension,x1, x2, x3); } private static String dimmedLine(String tag,int dimension,String x1,String x2,String x3) { String rval=null; if(dimension==2) rval=tag+": "+x1+" "+x2+"\n"; else if(dimension==3) rval=tag+": "+x1+" "+x2+" "+x3+"\n"; return rval; } }