// // Exporter.java // /* LOCI Plugins for ImageJ: a collection of ImageJ plugins including the Bio-Formats Importer, Bio-Formats Exporter, Bio-Formats Macro Extensions, Data Browser and Stack Slicer. Copyright (C) 2005-@year@ Melissa Linkert, Curtis Rueden and Christopher Peterson. This program is free software; you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details. You should have received a copy of the GNU 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.plugins.out; import ij.IJ; import ij.ImagePlus; import ij.ImageStack; import ij.Macro; import ij.gui.GenericDialog; import ij.io.FileInfo; import ij.io.OpenDialog; import ij.measure.Calibration; import ij.plugin.frame.Recorder; import ij.process.ByteProcessor; import ij.process.ColorProcessor; import ij.process.FloatProcessor; import ij.process.ImageProcessor; import ij.process.ShortProcessor; import java.awt.Image; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.filechooser.FileFilter; import loci.common.DataTools; import loci.common.services.DependencyException; import loci.common.services.ServiceException; import loci.common.services.ServiceFactory; import loci.formats.FormatException; import loci.formats.FormatTools; import loci.formats.IFormatWriter; import loci.formats.ImageWriter; import loci.formats.MetadataTools; import loci.formats.gui.AWTImageTools; import loci.formats.gui.ExtensionFileFilter; import loci.formats.gui.GUITools; import loci.formats.meta.IMetadata; import loci.formats.services.OMEXMLService; import loci.plugins.BF; import loci.plugins.LociExporter; import loci.plugins.util.RecordedImageProcessor; import loci.plugins.util.WindowTools; import ome.xml.model.OME; import ome.xml.model.enums.DimensionOrder; import ome.xml.model.enums.EnumerationException; import ome.xml.model.enums.PixelType; import ome.xml.model.primitives.PositiveFloat; import ome.xml.model.primitives.PositiveInteger; /** * Core logic for the Bio-Formats Exporter ImageJ plugin. * * <dl><dt><b>Source code:</b></dt> * <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/loci-plugins/src/loci/plugins/out/Exporter.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/loci-plugins/src/loci/plugins/out/Exporter.java;hb=HEAD">Gitweb</a></dd></dl> * * @author Melissa Linkert melissa at glencoesoftware.com */ public class Exporter { // -- Fields -- /** Current stack. */ private ImagePlus imp; private LociExporter plugin; // -- Constructor -- public Exporter(LociExporter plugin, ImagePlus imp) { this.plugin = plugin; this.imp = imp; } // -- Exporter API methods -- /** Executes the plugin. */ public void run() { String outfile = null; Boolean splitZ = null; Boolean splitC = null; Boolean splitT = null; if (plugin.arg != null) { outfile = Macro.getValue(plugin.arg, "outfile", null); String z = Macro.getValue(plugin.arg, "splitZ", null); String c = Macro.getValue(plugin.arg, "splitC", null); String t = Macro.getValue(plugin.arg, "splitT", null); splitZ = z == null ? null : Boolean.valueOf(z); splitC = c == null ? null : Boolean.valueOf(c); splitT = t == null ? null : Boolean.valueOf(t); plugin.arg = null; } if (outfile == null) { String options = Macro.getOptions(); if (options != null) { String save = Macro.getValue(options, "save", null); if (save != null) outfile = save; } } if (outfile == null || outfile.length() == 0) { // open a dialog prompting for the filename to save //SaveDialog sd = new SaveDialog("Bio-Formats Exporter", "", ""); //String dir = sd.getDirectory(); //String name = sd.getFileName(); // NB: Copied and adapted from ij.io.SaveDIalog.jSaveDispatchThread, // so that the save dialog has a file filter for choosing output format. String dir = null, name = null; JFileChooser fc = GUITools.buildFileChooser(new ImageWriter(), false); fc.setDialogTitle("Bio-Formats Exporter"); String defaultDir = OpenDialog.getDefaultDirectory(); if (defaultDir != null) fc.setCurrentDirectory(new File(defaultDir)); // set OME-TIFF as the default output format FileFilter[] ff = fc.getChoosableFileFilters(); FileFilter defaultFilter = null; for (int i=0; i<ff.length; i++) { if (ff[i] instanceof ExtensionFileFilter) { ExtensionFileFilter eff = (ExtensionFileFilter) ff[i]; if (i == 0 || eff.getExtension().equals("ome.tif")) { defaultFilter = eff; break; } } } if (defaultFilter != null) fc.setFileFilter(defaultFilter); int returnVal = fc.showSaveDialog(IJ.getInstance()); if (returnVal != JFileChooser.APPROVE_OPTION) { Macro.abort(); return; } File f = fc.getSelectedFile(); if (f.exists()) { int ret = JOptionPane.showConfirmDialog(fc, "The file " + f.getName() + " already exists. \n" + "Would you like to replace it?", "Replace?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (ret != JOptionPane.OK_OPTION) f = null; } if (f == null) Macro.abort(); else { dir = fc.getCurrentDirectory().getPath() + File.separator; name = fc.getName(f); // ensure filename matches selected filter FileFilter filter = fc.getFileFilter(); if (filter instanceof ExtensionFileFilter) { ExtensionFileFilter eff = (ExtensionFileFilter) filter; String[] ext = eff.getExtensions(); String lName = name.toLowerCase(); boolean hasExtension = false; for (int i=0; i<ext.length; i++) { if (lName.endsWith("." + ext[i])) { hasExtension = true; break; } } if (!hasExtension && ext.length > 0) { // append chosen extension name = name + "." + ext[0]; } } // do some ImageJ bookkeeping OpenDialog.setDefaultDirectory(dir); if (Recorder.record) Recorder.recordPath("save", dir+name); } if (dir == null || name == null) return; outfile = new File(dir, name).getAbsolutePath(); if (outfile == null) return; } if (splitZ == null || splitC == null || splitT == null) { // ask if we want to export multiple files GenericDialog multiFile = new GenericDialog("Bio-Formats Exporter - Multiple Files"); multiFile.addCheckbox("Write each Z section to a separate file", false); multiFile.addCheckbox("Write each timepoint to a separate file", false); multiFile.addCheckbox("Write each channel to a separate file", false); multiFile.showDialog(); splitZ = multiFile.getNextBoolean(); splitT = multiFile.getNextBoolean(); splitC = multiFile.getNextBoolean(); } try { int ptype = 0; int channels = 1; switch (imp.getType()) { case ImagePlus.GRAY8: case ImagePlus.COLOR_256: ptype = FormatTools.UINT8; break; case ImagePlus.COLOR_RGB: channels = 3; ptype = FormatTools.UINT8; break; case ImagePlus.GRAY16: ptype = FormatTools.UINT16; break; case ImagePlus.GRAY32: ptype = FormatTools.FLOAT; break; } String title = imp.getTitle(); IFormatWriter w = new ImageWriter().getWriter(outfile); w.setWriteSequentially(true); FileInfo fi = imp.getOriginalFileInfo(); String xml = fi == null ? null : fi.description == null ? null : fi.description.indexOf("xml") == -1 ? null : fi.description; OMEXMLService service = null; IMetadata store = null; try { ServiceFactory factory = new ServiceFactory(); service = factory.getInstance(OMEXMLService.class); store = service.createOMEXMLMetadata(xml); } catch (DependencyException de) { } catch (ServiceException se) { } if (store == null) IJ.error("OME-XML Java library not found."); if (xml == null) { store.createRoot(); } else if (store.getImageCount() > 1) { // the original dataset had multiple series // we need to modify the IMetadata to represent the correct series ArrayList<Integer> matchingSeries = new ArrayList<Integer>(); for (int series=0; series<store.getImageCount(); series++) { String type = store.getPixelsType(series).toString(); int pixelType = FormatTools.pixelTypeFromString(type); if (pixelType == ptype) { String imageName = store.getImageName(series); if (title.indexOf(imageName) >= 0) { matchingSeries.add(series); } } } int series = 0; if (matchingSeries.size() > 1) { for (int i=0; i<matchingSeries.size(); i++) { int index = matchingSeries.get(i); String name = store.getImageName(index); boolean valid = true; for (int j=0; j<matchingSeries.size(); j++) { if (i != j) { String compName = store.getImageName(matchingSeries.get(j)); if (compName.indexOf(name) >= 0) { valid = false; break; } } } if (valid) { series = index; break; } } } else if (matchingSeries.size() == 1) series = matchingSeries.get(0); OME root = (OME) store.getRoot(); ome.xml.model.Image exportImage = root.getImage(series); List<ome.xml.model.Image> allImages = root.copyImageList(); for (ome.xml.model.Image img : allImages) { if (!img.equals(exportImage)) { root.removeImage(img); } } store.setRoot(root); } store.setPixelsSizeX(new PositiveInteger(imp.getWidth()), 0); store.setPixelsSizeY(new PositiveInteger(imp.getHeight()), 0); store.setPixelsSizeZ(new PositiveInteger(imp.getNSlices()), 0); store.setPixelsSizeC(new PositiveInteger(channels*imp.getNChannels()), 0); store.setPixelsSizeT(new PositiveInteger(imp.getNFrames()), 0); if (store.getImageID(0) == null) { store.setImageID(MetadataTools.createLSID("Image", 0), 0); } if (store.getPixelsID(0) == null) { store.setPixelsID(MetadataTools.createLSID("Pixels", 0), 0); } // always reset the pixel type // this prevents problems if the user changed the bit depth of the image try { store.setPixelsType(PixelType.fromString( FormatTools.getPixelTypeString(ptype)), 0); } catch (EnumerationException e) { } if (store.getPixelsBinDataCount(0) == 0 || store.getPixelsBinDataBigEndian(0, 0) == null) { store.setPixelsBinDataBigEndian(Boolean.FALSE, 0, 0); } if (store.getPixelsDimensionOrder(0) == null) { try { store.setPixelsDimensionOrder(DimensionOrder.fromString("XYCZT"), 0); } catch (EnumerationException e) { } } for (int c=0; c<imp.getNChannels(); c++) { if (c >= store.getChannelCount(0) || store.getChannelID(0, c) == null) { String lsid = MetadataTools.createLSID("Channel", 0, c); store.setChannelID(lsid, 0, c); } store.setChannelSamplesPerPixel(new PositiveInteger(channels), 0, 0); } Calibration cal = imp.getCalibration(); store.setPixelsPhysicalSizeX(new PositiveFloat(cal.pixelWidth), 0); store.setPixelsPhysicalSizeY(new PositiveFloat(cal.pixelHeight), 0); store.setPixelsPhysicalSizeZ(new PositiveFloat(cal.pixelDepth), 0); store.setPixelsTimeIncrement(new Double(cal.frameInterval), 0); if (imp.getImageStackSize() != imp.getNChannels() * imp.getNSlices() * imp.getNFrames()) { IJ.showMessageWithCancel("Bio-Formats Exporter Warning", "The number of planes in the stack (" + imp.getImageStackSize() + ") does not match the number of expected planes (" + (imp.getNChannels() * imp.getNSlices() * imp.getNFrames()) + ")." + "\nIf you select 'OK', only " + imp.getImageStackSize() + " planes will be exported. If you wish to export all of the " + "planes,\nselect 'Cancel' and convert the Image5D window " + "to a stack."); store.setPixelsSizeZ(new PositiveInteger(imp.getImageStackSize()), 0); store.setPixelsSizeC(new PositiveInteger(1), 0); store.setPixelsSizeT(new PositiveInteger(1), 0); } w.setMetadataRetrieve(store); Object info = imp.getProperty("Info"); if (info != null) { String imageInfo = info.toString(); if (imageInfo != null) { String[] lines = imageInfo.split("\n"); for (String line : lines) { int eq = line.lastIndexOf("="); if (eq > 0) { String key = line.substring(0, eq).trim(); String value = line.substring(eq + 1).trim(); if (key.endsWith("BitsPerPixel")) { w.setValidBitsPerPixel(Integer.parseInt(value)); break; } } } } } String[] outputFiles = new String[] {outfile}; if (splitZ || splitC || splitT) { int sizeZ = store.getPixelsSizeZ(0).getValue(); int sizeC = store.getPixelsSizeC(0).getValue(); int sizeT = store.getPixelsSizeT(0).getValue(); int nFiles = 1; if (splitZ) { nFiles *= sizeZ; } if (splitC) { nFiles *= sizeC; } if (splitT) { nFiles *= sizeT; } outputFiles = new String[nFiles]; int dot = outfile.indexOf(".", outfile.lastIndexOf(File.separator)); String base = outfile.substring(0, dot); String ext = outfile.substring(dot); int nextFile = 0; for (int z=0; z<(splitZ ? sizeZ : 1); z++) { for (int c=0; c<(splitC ? sizeC : 1); c++) { for (int t=0; t<(splitT ? sizeT : 1); t++) { outputFiles[nextFile++] = base + (splitZ ? "_Z" + z : "") + (splitC ? "_C" + c : "") + (splitT ? "_T" + t : "") + ext; } } } } // prompt for options String[] codecs = w.getCompressionTypes(); ImageProcessor proc = imp.getImageStack().getProcessor(1); Image firstImage = proc.createImage(); firstImage = AWTImageTools.makeBuffered(firstImage, proc.getColorModel()); int thisType = AWTImageTools.getPixelType((BufferedImage) firstImage); if (proc instanceof ColorProcessor) { thisType = FormatTools.UINT8; } if (!proc.isDefaultLut()) { w.setColorModel(proc.getColorModel()); } boolean notSupportedType = !w.isSupportedType(thisType); if (notSupportedType) { IJ.error("Pixel type (" + FormatTools.getPixelTypeString(thisType) + ") not supported by this format."); } if (codecs != null && codecs.length > 1) { GenericDialog gd = new GenericDialog("Bio-Formats Exporter Options"); gd.addChoice("Compression type: ", codecs, codecs[0]); gd.showDialog(); if (gd.wasCanceled()) return; w.setCompression(gd.getNextChoice()); } // convert and save slices int size = imp.getImageStackSize(); ImageStack is = imp.getImageStack(); boolean doStack = w.canDoStacks() && size > 1; int start = doStack ? 0 : imp.getCurrentSlice() - 1; int end = doStack ? size : start + 1; boolean littleEndian = !w.getMetadataRetrieve().getPixelsBinDataBigEndian(0, 0).booleanValue(); byte[] plane = null; w.setInterleaved(false); int no = 0; for (int i=start; i<end; i++) { if (doStack) { BF.status(false, "Saving plane " + (i + 1) + "/" + size); BF.progress(false, i, size); } else BF.status(false, "Saving image"); proc = is.getProcessor(i + 1); if (proc instanceof RecordedImageProcessor) { proc = ((RecordedImageProcessor) proc).getChild(); } int x = proc.getWidth(); int y = proc.getHeight(); if (proc instanceof ByteProcessor) { plane = (byte[]) proc.getPixels(); } else if (proc instanceof ShortProcessor) { plane = DataTools.shortsToBytes( (short[]) proc.getPixels(), littleEndian); } else if (proc instanceof FloatProcessor) { plane = DataTools.floatsToBytes( (float[]) proc.getPixels(), littleEndian); } else if (proc instanceof ColorProcessor) { byte[][] pix = new byte[3][x*y]; ((ColorProcessor) proc).getRGB(pix[0], pix[1], pix[2]); plane = new byte[3 * x * y]; System.arraycopy(pix[0], 0, plane, 0, x * y); System.arraycopy(pix[1], 0, plane, x * y, x * y); System.arraycopy(pix[2], 0, plane, 2 * x * y, x * y); } if (notSupportedType) { IJ.error("Pixel type not supported by this format."); } else { w.setId(outputFiles[no]); w.saveBytes(no++, plane); } } w.close(); } catch (FormatException e) { WindowTools.reportException(e); } catch (IOException e) { WindowTools.reportException(e); } } }