Bio-Formats: Pyramidal OME-TIFF

OME Community Meeting
Dundee May 2020
OME Team

What we will cover

  • Working with large images using the existing toolsets
  • Converting to Pyramidal OME-TIFF
  • Using the command line tools
  • Scripting in the ImageJ environment

Pyramidal OME-TIFF

  • Originally designed for dealing with large whole slide images (WSI)
  • Introduced as an extension to OME-TIFF in Bio-Formats 6.0.0
  • Full support for reading and writing multi resolution pyramids
  • Additional resolutions created through down-sampling
  • Now used in many applications such as OMERO, IDR, QuPath

Command line tools

  • Can be downloaded from https://www.openmicroscopy.org/bio-formats/downloads/
  • Large variety of options for conversions
  • Using compression:
  • bfconvert -compression LZW  /path/to/input out.ome.tiff
        
  • Using tiled writing for larger images:
  • bfconvert -tilex 512 -tiley 512 /path/to/input out.ome.tiff
                        
  • Converting to pyramidal OME-TIFF:
bfconvert -noflat -pyramid-resolutions 4 -pyramid-scale 2 /path/to/input out.ome.tiff
                    

Command line tools options

  • -noflat
    • Do not flatten resolutions into individual series. This option is mandatory to read images with pyramidal levels using the sub-resolution API and generate an output image with sub-resolutions.
  • -pyramid-resolutions
    • The number of expected resolutions
    • If the input file is not multi resolution then additional resolutions calculated by downsampling
  • -pyramid-scale
    • The downsampling factor to be used when generating additional resolutions

Working with large images in ImageJ/FIJI

  • Bio-Formats Macro extensions offer basic functionality
  • Currently a limit when trying to open a single plane greater than 2GB
  • One current option to deal with this would be to convert to smaller sub resolutions
  • Full Bio-Formats Java API is available through scripting
  • New examples are available in Jython or Groovy
  • The examples can be found on GitHub

Scripting in ImageJ

  • Image reading, writing and the full Java API is available through scripting

# setup reader
reader = ImageReader()
omeMeta = MetadataTools.createOMEXMLMetadata()
reader.setMetadataStore(omeMeta)
reader.setId(file)
                    

# setup writer
writer = PyramidOMETiffWriter()
writer.setMetadataRetrieve(omeMeta)
writer.setId(outFile)
                    

Converting to OME-TIFF

  • Basic conversions for smaller images are straight forward

for series in range(reader.getSeriesCount()):
    reader.setSeries(series)
    writer.setSeries(series)
                        
    # convert each image in the current series
    for image in range(reader.getImageCount())
        # read and write image planes
        img = reader.openBytes(image)
        writer.saveBytes(image, img)
                    

Using tiling to convert large images

  • For larger planes it is recommended to use tiling

width = reader.getSizeX();
height = reader.getSizeY();

# Determined the number of tiles to read and write
nXTiles = int(math.floor(width / tileSizeX))
nYTiles = int(math.floor(height / tileSizeY))
if nXTiles * tileSizeX != width:
    nXTiles = nXTiles + 1
if nYTiles * tileSizeY != height:
    nYTiles = nYTiles + 1
                    

for y in range(nYTiles):
    for x in range(nXTiles):
        # The x and y coordinates for the current tile
        tileX = x * tileSizeX
        tileY = y * tileSizeY
                    
        # Read tiles from the input file and write them to the output OME-Tiff
        buf = reader.openBytes(image, tileX, tileY, tileSizeX, tileSizeY)
        writer.saveBytes(image, buf, tileX, tileY, tileSizeX, tileSizeY)
                    

Converting to Pyramidal OME-TIFF

  • First set the resolution sizes in metadata

# setup resolutions
for i in range(resolutions):
    divScale = Math.pow(scale, i + 1)
    omeMeta.setResolutionSizeX(PositiveInteger(int(reader.getSizeX() / divScale)), 0, i + 1)
    omeMeta.setResolutionSizeY(PositiveInteger(int(reader.getSizeY() / divScale)), 0, i + 1)
                    
  • Create a scaler to handle the down-sampling

# create ImageScaler for down-sampling
scaler = SimpleImageScaler()
                    

Converting to Pyramidal OME-TIFF

  • First write the full resolution plane as before

# read and write main image
img = reader.openBytes(0)
writer.saveBytes(0, img)
                    
  • Then create and write each sub resolution

# generate downsampled resolutions and write to output
for i in range(resolutions):
    writer.setResolution(i + 1)
    x = omeMeta.getResolutionSizeX(0, i + 1).getValue()
    y = omeMeta.getResolutionSizeY(0, i + 1).getValue()
    downsample = scaler.downsample(img, reader.getSizeX(), reader.getSizeY(), Math.pow(scale, i + 1),
        FormatTools.getBytesPerPixel(type), reader.isLittleEndian(),
        FormatTools.isFloatingPoint(type), reader.getRGBChannelCount(),reader.isInterleaved())
   writer.saveBytes(0, downsample)
                    

Combining techniques

  • Combine these techniques to handle large planes greater than 2GB
  • Using tiled reading and writing
  • Generate sub resolutions
  • Write to a new pyramidal OME-TIFF
  • Now the lower resolutions can be opened and viewed in ImageJ