We're Hiring!

Writing Large Stitched Image

Historical discussions about the Bio-Formats library. Please look for and ask new questions at https://forum.image.sc/tags/bio-formats
Please note:
Historical discussions about the Bio-Formats library. Please look for and ask new questions at https://forum.image.sc/tags/bio-formats

If you are having trouble with image files, there is information about reporting bugs in the Bio-Formats documentation. Please send us the data and let us know what version of Bio-Formats you are using. For issues with your code, please provide a link to a public repository, ideally GitHub.

Writing Large Stitched Image

Postby Jameshobbs » Thu Sep 26, 2013 7:58 pm

Hello,

I am attempting to use the bio-format library to write large (potentially very large) images to disk.

The format I am attempting to write to is .ome.tiff. I am using the FileExport example as a reference, but I am unable to do things correctly, so I must be missing something.

Here is the basic layout for composing/writing the image
1) With list of images, I use a blend class for doing an average blend for each image. Each image has been wrapped into its own Array2DView, which represents a 2d representation of the one-dimensional pixel array.
Code: Select all
public class AverageBlendScifio implements JavaBlender {

   private float[][]sums;
   private int[][]counts;
   private int height;
   private int width;
   
   private FileExport exporter;

   @Override
   public void init(int width, int height, int type, String fileName) {
      exporter = new FileExport(fileName, width, height, type);   
      sums = new float[height][width];
      counts = new int[height][width];
      this.height = height;
      this.width = width;
   }

   @Override
   public void blend(int x, int y, Array2DView pixels) {
      for(int row = 0; row < pixels.getViewHeight(); row++ ){
         for(int col = 0; col < pixels.getViewWidth(); col++){
            sums[row+y][col+x] += pixels.get(row, col);
            counts[row+y][col+x]++;
         }
      }         
   }   

   @Override
   public BufferedImage getResult() {
      return null;
   }

   @Override
   public void postProcess() {
      byte[] vals = new byte[2*width*height];

      System.out.println("Writing pixels");
      for (int row = 0; row < height; row++)
         {
            for (int col = 0; col < width; col++)
            {         
               short value = (short)((float)sums[row][col] / (float)counts[row][col]);      
               vals[row*width+col] = (byte)(value & 0xFF00);
               vals[row*width+col+1] = (byte)(value & 0x00FF);
            }
         }      
         exporter.export(vals, width, height);
                 exporter.cleanup();      
   }
}



Here is my FileExporter class, which I adapted from the omexml file export example:
Code: Select all
package visualize.export;

/*
* #%L
* OME Bio-Formats package for reading and converting biological file formats.
* %%
* Copyright (C) 2005 - 2013 Open Microscopy Environment:
*   - Board of Regents of the University of Wisconsin-Madison
*   - Glencoe Software, Inc.
*   - University of Dundee
* %%
* 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, see
* <http://www.gnu.org/licenses/gpl-2.0.html>.
* #L%
*/

import java.io.IOException;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

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.ImageWriter;
import loci.formats.meta.IMetadata;
import loci.formats.services.OMEXMLService;

import ome.xml.model.enums.DimensionOrder;
import ome.xml.model.enums.EnumerationException;
import ome.xml.model.enums.PixelType;
import ome.xml.model.primitives.PositiveInteger;

/**
* Example class that shows how to export raw pixel data to OME-TIFF using
* Bio-Formats version 4.2 or later.
*/
public class FileExport {

  /** The file writer. */
  private ImageWriter writer;
  private int width;
  private int height;

  /** The name of the output file. */
  private String outputFile;

  /**
   * Construct a new FileExport that will save to the specified file.
   *
   * @param outputFile the file to which we will export
   */
  public FileExport(String outputFile, int width, int height, int pixelType) {
     BasicConfigurator.configure();
     Logger.getLogger("loci.common.services.ServiceFactory").setLevel(Level.ALL);
    this.outputFile = outputFile;
   
    IMetadata omexml = initializeMetadata(width, height, FormatTools.UINT16);   
    initializeWriter(omexml);   
   
    this.width = width;
    this.height = height;
  }

  public void export(byte[] vals)
  {
     try {
      writer.saveBytes(0, vals, 0, 0, width, height);
   } catch (FormatException e) {
      e.printStackTrace();
   } catch (IOException e) {
      e.printStackTrace();
   }
  }
 
  public void exportShort(short value, int x, int y)
  {         
     byte b0 = (byte)(value & 0xFF00);
     byte b1 = (byte)(value & 0x00FF);    
     try {
        System.out.println("Writing x: " + x + " y: " + y + " val: " + value);
      writer.saveBytes(0, new byte[] {b0, b1}, x, y, 1, 1);
   } catch (FormatException e) {
      e.printStackTrace();
   } catch (IOException e) {
      e.printStackTrace();
   }
  }
 
  /**
   * Set up the file writer.
   *
   * @param omexml the IMetadata object that is to be associated with the writer
   * @return true if the file writer was successfully initialized; false if an
   *   error occurred
   */
  private boolean initializeWriter(IMetadata omexml) {
    // create the file writer and associate the OME-XML metadata with it
    writer = new ImageWriter();
    writer.setMetadataRetrieve(omexml);

    Exception exception = null;
    try {
      writer.setId(outputFile);
    }
    catch (FormatException e) {
      exception = e;
    }
    catch (IOException e) {
      exception = e;
    }
    if (exception != null) {
      System.err.println("Failed to initialize file writer.");
      exception.printStackTrace();
    }
    return exception == null;
  }

  /**
   * Populate the minimum amount of metadata required to export an image.
   *
   * @param width the width (in pixels) of the image
   * @param height the height (in pixels) of the image
   * @param pixelType the pixel type of the image; @see loci.formats.FormatTools
   */
  private IMetadata initializeMetadata(int width, int height, int pixelType) {
    Exception exception = null;
    try {
      // create the OME-XML metadata storage object
      ServiceFactory factory = new ServiceFactory();
      OMEXMLService service = factory.getInstance(OMEXMLService.class);
      IMetadata meta = service.createOMEXMLMetadata();
      meta.createRoot();

      // define each stack of images - this defines a single stack of images
      meta.setImageID("Image:0", 0);
      meta.setPixelsID("Pixels:0", 0);

      // specify that the pixel data is stored in big-endian format
      // change 'TRUE' to 'FALSE' to specify little-endian format
      meta.setPixelsBinDataBigEndian(Boolean.TRUE, 0, 0);

      // specify that the images are stored in ZCT order
      meta.setPixelsDimensionOrder(DimensionOrder.XYZCT, 0);

      // specify that the pixel type of the images
      meta.setPixelsType(
        PixelType.fromString(FormatTools.getPixelTypeString(pixelType)), 0);

      // specify the dimensions of the images
      meta.setPixelsSizeX(new PositiveInteger(width), 0);
      meta.setPixelsSizeY(new PositiveInteger(height), 0);
      meta.setPixelsSizeZ(new PositiveInteger(1), 0);
      meta.setPixelsSizeC(new PositiveInteger(1), 0);
      meta.setPixelsSizeT(new PositiveInteger(1), 0);

      // define each channel and specify the number of samples in the channel
      // the number of samples is 3 for RGB images and 1 otherwise
      meta.setChannelID("Channel:0:0", 0, 0);
      meta.setChannelSamplesPerPixel(new PositiveInteger(1), 0, 0);
     
      System.out.println("Completed initializing meta data");

      return meta;
    }
    catch (DependencyException e) {
      exception = e;
    }
    catch (ServiceException e) {
      exception = e;
    }
    catch (EnumerationException e) {
      exception = e;
    }

    System.err.println("Failed to populate OME-XML metadata object.");
    exception.printStackTrace();
    return null;
  }

  /**
   * Generate a random plane of pixel data and save it to the output file.
   *
   * @param width the width of the image in pixels
   * @param height the height of the image in pixels
   * @param pixelType the pixel type of the image; @see loci.formats.FormatTools
   */
  public void savePlane(int width, int height, int pixelType) {
    byte[] plane = createImage(width, height, pixelType);
    Exception exception = null;
    try {
      writer.saveBytes(0, plane);
    }
    catch (FormatException e) {
      exception = e;
    }
    catch (IOException e) {
      exception = e;
    }
    if (exception != null) {
      System.err.println("Failed to save plane.");
      exception.printStackTrace();
    }
  }

  /**
   * Generate a random plane of pixel data.
   *
   * @param width the width of the image in pixels
   * @param height the height of the image in pixels
   * @param pixelType the pixel type of the image; @see loci.formats.FormatTools
   */
  private byte[] createImage(int width, int height, int pixelType) {
    // create a blank image of the specified size
    byte[] img =
      new byte[width * height * FormatTools.getBytesPerPixel(pixelType)];

    // fill it with random data
    for (int i=0; i<img.length; i++) {
      img[i] = (byte) (256 * Math.random());
    }
    return img;
  }

  /** Close the file writer. */
  public void cleanup() {
    try {
      writer.close();
    }
    catch (IOException e) {
      System.err.println("Failed to close file writer.");
      e.printStackTrace();
    }
  }

}


I get the following error when trying to export the bytes to the file:
Code: Select all
1320 [SwingWorker-pool-1-thread-1] DEBUG loci.formats.tiff.TiffSaver  - Reading IFD from 8 in non-sequential write.
1322 [SwingWorker-pool-1-thread-1] ERROR loci.formats.tiff.TiffParser  - Error reading IFD type at: 14
1323 [SwingWorker-pool-1-thread-1] DEBUG loci.formats.tiff.TiffParser  -
loci.common.enumeration.EnumException: Unable to find IFDType with code: 1024
   at loci.formats.tiff.IFDType.get(IFDType.java:98)
   at loci.formats.tiff.TiffParser.readTiffIFDEntry(TiffParser.java:1166)
   at loci.formats.tiff.TiffParser.getIFD(TiffParser.java:404)
   at loci.formats.tiff.TiffSaver.writeImageIFD(TiffSaver.java:434)
   at loci.formats.tiff.TiffSaver.writeImage(TiffSaver.java:388)
   at loci.formats.tiff.TiffSaver.writeImage(TiffSaver.java:270)
   at loci.formats.out.TiffWriter.saveBytes(TiffWriter.java:191)
   at loci.formats.out.OMETiffWriter.saveBytes(OMETiffWriter.java:201)
   at loci.formats.out.OMETiffWriter.saveBytes(OMETiffWriter.java:187)
   at loci.formats.ImageWriter.saveBytes(ImageWriter.java:212)
   at visualize.export.FileExport.export(FileExport.java:82)
   at visualize.java.blend.AverageBlendScifio.postProcess(AverageBlendScifio.java:57)
   at visualize.export.LargeImageExporter.exportImage(LargeImageExporter.java:152)
   at visualize.export.LargeImageExporter.exportImage(LargeImageExporter.java:182)
   at app.gui.StitchingExecutor.outputGrid(StitchingExecutor.java:587)
   at app.gui.StitchingExecutor.launchFFTWStitching(StitchingExecutor.java:413)
   at app.gui.StitchingExecutor.doInBackground(StitchingExecutor.java:189)
   at app.gui.StitchingExecutor.doInBackground(StitchingExecutor.java:1)
   at javax.swing.SwingWorker$1.call(Unknown Source)
   at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source)
   at java.util.concurrent.FutureTask.run(Unknown Source)
   at javax.swing.SwingWorker.run(Unknown Source)
   at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
   at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
   at java.lang.Thread.run(Unknown Source)


I was also wondering if it would be possible to use the exportShort function that I wrote in the FileExporter, but first I need to fix this IFD error, because I get it when trying to use exportShort.

Here is a basic example of how to begin executing:
Code: Select all
AverageBlendScifio blend = new AverageBlendScifio();
File outputFile = new File(outputDir, imageName);
blend.init(width, height, BufferedImage.TYPE_USHORT_GRAY, outputFile.getAbsolutePath());
LargeImageExporter.exportImage(grid, 0, 0, width, height, blend, outputFile, progress);


The LargeImageExporter basically loops through each image and sends the pixel data to the blend function and then calls postProcess on the blender.

Thank you for any comments/suggestions regarding this issue.
Jameshobbs
 
Posts: 3
Joined: Thu Sep 26, 2013 7:36 pm

Re: Writing Large Stitched Image

Postby Jameshobbs » Thu Sep 26, 2013 8:04 pm

Quick update. Got the error to go away by deleting the original file. For some reason it was not able to overwrite the file that already existed.

The file saves okay, but the image does not look right. The dimensions of the image is correct (2935x2547) except it appears to have shrunk the final stitched image into the first two quadrants.

For some reason it created 4 quadrants such that the first two quadrants are identical and look like the full stitched image and the bottom two quadrants are black.

I will create a basic paint representation of what I am seeing as I do not believe I can upload the actual image.

Image

Image did not look great on forum so here is the direct link:
http://i.imgur.com/tYOegKQ.png

It appears to be downsampling by 1/4.
Jameshobbs
 
Posts: 3
Joined: Thu Sep 26, 2013 7:36 pm

Re: Writing Large Stitched Image

Postby mlinkert » Fri Sep 27, 2013 7:55 pm

Could you please first verify that the short-to-byte unpacking logic in AverageBlendScifio is correct? This doesn't seem quite right to me:

Code: Select all
byte[] vals = new byte[2*width*height];

      System.out.println("Writing pixels");
      for (int row = 0; row < height; row++)
         {
            for (int col = 0; col < width; col++)
            {         
               short value = (short)((float)sums[row][col] / (float)counts[row][col]);     
               vals[row*width+col] = (byte)(value & 0xFF00);
               vals[row*width+col+1] = (byte)(value & 0x00FF);
            }
         }     


I would expect the indices into 'vals' to be '2 * (row * width + col)' and '2 * (row * width + col) + 1' respectively; otherwise, every high byte of the value is overwriting the low byte for the previous value. This means that only half of the image array is filled, with the latter half being empty (i.e. all black). You may also want to double check that the high byte values are indeed correct; I would think that 'value & 0xFF00' would need to be right-shifted by 8 bits before being cast to a byte.

There are helper methods to do short-to-byte unpacking and repacking in the loci.common.DataTools class, which you may find of use.

If the image still looks incorrect after updating that code and retrying the export, please let us know.
User avatar
mlinkert
Team Member
 
Posts: 353
Joined: Fri May 29, 2009 2:12 pm
Location: Southwest Wisconsin

Re: Writing Large Stitched Image

Postby Jameshobbs » Tue Oct 01, 2013 12:48 pm

mlinkert wrote:Could you please first verify that the short-to-byte unpacking logic in AverageBlendScifio is correct? This doesn't seem quite right to me:

Code: Select all
byte[] vals = new byte[2*width*height];

      System.out.println("Writing pixels");
      for (int row = 0; row < height; row++)
         {
            for (int col = 0; col < width; col++)
            {         
               short value = (short)((float)sums[row][col] / (float)counts[row][col]);     
               vals[row*width+col] = (byte)(value & 0xFF00);
               vals[row*width+col+1] = (byte)(value & 0x00FF);
            }
         }     


I would expect the indices into 'vals' to be '2 * (row * width + col)' and '2 * (row * width + col) + 1' respectively; otherwise, every high byte of the value is overwriting the low byte for the previous value. This means that only half of the image array is filled, with the latter half being empty (i.e. all black). You may also want to double check that the high byte values are indeed correct; I would think that 'value & 0xFF00' would need to be right-shifted by 8 bits before being cast to a byte.

There are helper methods to do short-to-byte unpacking and repacking in the loci.common.DataTools class, which you may find of use.

If the image still looks incorrect after updating that code and retrying the export, please let us know.


Thank you for the reply. Changing to '2 * (row * width + col)' and '2 * (row * width + col) + 1' did the trick. Funny because I use that same arithmetic for all of my other array access and for some reason missed it with this one.
Jameshobbs
 
Posts: 3
Joined: Thu Sep 26, 2013 7:36 pm


Return to User Discussion [Legacy]

Who is online

Users browsing this forum: No registered users and 1 guest