content: 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;
  	}	
  }
  
  
  