bioformats 0.1.3

Pure Rust reimplementation of Bio-Formats — read/write scientific image formats
Documentation

bioformats-rs

A pure-Rust translation of Bio-Formats — a library for reading (and writing) scientific image formats used in microscopy, medical imaging, and astronomy.

This package has limited real data testing, not all features are yet included

  • 2026-05-27: Further progress but incomplete. See status of translation below. However, more test data is needed for audit
  • 2026-05-26: 60-70% there. see list of libraries below. translation of mdbtools underway to support key file formats
  • 2026-05-24: started proper audit; plenty left to do on this crate

This is an LLM-mediated faithful (hopefully) translation, not the original code!

Most users should probably first see if the existing original code works for them, unless they have reason otherwise. The original source may have newer features and it has had more love in terms of fixing bugs. In fact, we aim to replicate bugs if they are present, for the sake of reproducibility! (but then we might have added a few more in the process)

There are however cases when you might prefer this Rust version. We generally agree with this manifesto but more specifically:

  • We have had many issues with ensuring that our software works using existing containers (Docker, PodMan, Singularity). One size does not fit all and it eats our resources trying to keep up with every way of delivering software
  • Common package managers do not work well. It was great when we had a few Linux distributions with stable procedures, but now there are just too many ecosystems (Homebrew, Conda). Conda has an NP-complete resolver which does not scale. Homebrew is only so-stable. And our dependencies in Python still break. These can no longer be considered professional serious options. Meanwhile, Cargo enables multiple versions of packages to be available, even within the same program(!)
  • The future is the web. We deploy software in the web browser, and until now that has meant Javascript. This is a language where even the == operator is broken. Typescript is one step up, but a game changer is the ability to compile Rust code into webassembly, enabling performance and sharing of code with the backend. Translating code to Rust enables new ways of deployment and running code in the browser has especial benefits for science - researchers do not have deep pockets to run servers, so pushing compute to the user enables deployment that otherwise would be impossible
  • Old CLI-based utilities are bad for the environment(!). A large amount of compute resources are spent creating and communicating via small files, which we can bypass by using code as libraries. Even better, we can avoid frequent reloading of databases by hoisting this stage, with up to 100x speedups in some cases. Less compute means faster compute and less electricity wasted
  • LLM-mediated translations may actually be safer to use than the original code. This article shows that running the same code on different operating systems can give somewhat different answers. This is a gap that Rust+Cargo can reduce. Typesafe interfaces also reduce coding mistakes and error handling, as opposed to typical command-line scripting

But:

  • This approach should still be considered experimental. The LLM technology is immature and has sharp corners. But there are opportunities to reap, and the genie is not going back into the bottle. This translation is as much aimed to learn how to improve the technology and get feedback on the results.
  • Translations are not endorsed by the original authors unless otherwise noted. Do not send bug reports to the original developers. Use our Github issues page instead.
  • Do not trust the benchmarks on this page. They are used to help evaluate the translation. If you want improved performance, you generally have to use this code as a library, and use the additional tricks it offers. We generally accept performance losses in order to reduce our dependency issues
  • Check the original Github pages for information about the package. This README is kept sparse on purpose. It is not meant to be the primary source of information
  • If you are the author of the original code and wish to move to Rust, you can obtain ownership of this repository and crate. Until then, our commitment is to offer an as-faithful-as-possible translation of a snapshot of your code. If we find serious bugs, we will report them to you. Otherwise we will just replicate them, to ensure comparability across studies that claim to use package XYZ v.666. Think of this like a fancy Ubuntu .deb-package of your software - that is how we treat it

This blurb might be out of date. Go to this page for the latest information and further information about how we approach translation

Quick start

use bioformats::{ImageReader, ImageWriter, ImageMetadata, PixelType};
use std::path::Path;

// --- Reading ---
let mut reader = ImageReader::open(Path::new("image.tif"))?;

let meta = reader.metadata();
println!("{}x{} px, {} planes, {:?}", meta.size_x, meta.size_y, meta.image_count, meta.pixel_type);

for i in 0..meta.image_count {
    let plane: Vec<u8> = reader.open_bytes(i)?;
    // plane is raw little-endian pixel data
}

// --- Writing ---
let mut meta = ImageMetadata::default();
meta.size_x = 512;
meta.size_y = 512;
meta.size_z = 10;
meta.image_count = 10;
meta.pixel_type = PixelType::Uint16;

let planes: Vec<Vec<u8>> = (0..10).map(|_| vec![0u8; 512 * 512 * 2]).collect();
ImageWriter::save(Path::new("output.tif"), &meta, &planes)?;

Supported formats

Read + Write

Format Extensions Notes
TIFF / OME-TIFF .tif .tiff Full IFD parser; strip and tile layout; LZW, Deflate, PackBits, JPEG, Zstd. BigTIFF is read-only.
PNG .png 8-bit and 16-bit; grayscale and RGB
JPEG .jpg .jpeg 8-bit RGB
BMP .bmp 8-bit RGB
TGA .tga 8-bit
ICS / ICS2 .ics Image Cytometry Standard; gzip optional
MRC / CCP4 .mrc .mrcs .map .ccp4 Cryo-EM; uint8/16, int16, float32/64
FITS .fits .fit .fts 2880-byte blocks; big-endian; multi-plane
NRRD .nrrd .nhdr Raw and gzip encodings
MetaImage .mha .mhd ITK/VTK; inline and detached data file

Read only

Format Extensions Notes
GIF .gif Via image crate
WebP .webp Via image crate
OpenEXR .exr Via image crate
HDR / RGBE .hdr .rgbe Radiance HDR
DDS .dds DirectDraw Surface
Farbfeld .ff
PNM / PGM / PPM .pnm .pgm .ppm .pbm .pfm Via image crate
Leica LIF .lif Binary container with UTF-16 XML metadata
Nikon ND2 .nd2 Chunk-based; uncompressed and zlib
Zeiss CZI .czi ZISRAWFILE segments; uncompressed, JPEG, LZW, Zstd
DICOM .dcm Unencapsulated pixel data; uint8/16, int16
NIfTI-1 / Analyze 7.5 .nii .nii.gz .hdr .img gzip supported
Zeiss LSM .lsm TIFF-based with CZ_LSMInfo metadata
Applied Precision DeltaVision .dv .r3d Binary header + raw planes
Andor SIF .sif ASCII header + float32 pixel data
Olympus FV1000 OIF .oif INI-style header + companion TIFFs
Gatan DM3 / DM4 .dm3 .dm4 Tag-tree structure; EM format
Bio-Rad PIC .pic Confocal microscopy
Princeton SPE .spe Spectroscopy / CCD cameras
Norpix StreamPix .seq Video sequence; raw frames
Hamamatsu DCIMG .dcimg Scientific CMOS camera format

Translation status (all readers)

A per-reader audit of the Java→Rust translation (2026-05-26, updated after a parity pass). Status reflects how complete the translation is in theory (faithful to the Java reader for the common cases), not how thoroughly it has been tested against real-world files.

  • Complete — faithful core read path; metadata + pixels work for the format's common cases with no major known gap vs the Java reader.
  • 🟡 Partial — opens and reads the common case, but a specific feature is missing (noted). Often a vendor TIFF wrapper that reads pixels via the TIFF engine but skips format-specific metadata/companion-file assembly, or a format with no Java counterpart to be faithful to.
  • Stub — detection only; set_id returns UnsupportedFormat (the format is proprietary/undocumented or needs a decoder/container parser not yet ported).

Roughly 108 complete, 42 partial, 36 stub out of ~185 registered readers (up from 66/83/36 at the first audit).

Standard image formats

Format Extensions Status Notes
TIFF / BigTIFF / OME-TIFF .tif .tiff .btf .tf8 Strips/tiles, planar, palette, YCbCr, LZW/Deflate/PackBits/JPEG/Zstd/JP2/fax, SubIFD pyramids
PNG .png 8/16-bit gray/RGB(A); animated APNG rejected
JPEG .jpg .jpeg Decodes to 8-bit RGB
BMP .bmp RAW, RLE4/8, BITFIELDS, palette
PCX .pcx RLE, planar channels, v5 palette
Imagic-5 .hed .img REAL/INTG/PACK types
TGA .tga .tpic via image crate
GIF .gif All frames read as an image stack
WebP / OpenEXR / HDR / DDS / Farbfeld / PNM .webp .exr .hdr .dds .ff .pnm .pgm .ppm .pbm .pfm via image crate
JPEG 2000 / JPX .jp2 .j2k .j2c .jpc .jpx via jpeg2k; single plane
EPS / PostScript .eps .epsi .ps Inline raster + DOS-EPS TIFF preview (matches Java; no vector interpreter)
Photoshop PSD/PSB .psd .psb Composite image (matches Java; no per-layer extraction)
Khoros VIFF .xv .viff KhorosReader parity (byte-order, pixel types, LUT)
Apple PICT .pict .pct Bitmap/pixmap/packbits + JPEG-in-PICT
ZIP container .zip Delegates primary entry to any auto-detected reader
APNG / MNG .apng .mng Animated PNG and MNG are explicitly rejected/stubbed
Text / CSV image .txt .csv 🟡 Parsed as Float32; no distinct Java counterpart
AVI (video) .avi Uncompressed/16-bit/Y8 + MSRLE, MS Video 1, Cinepak, JPEG/MJPEG
Fake (test format) .fake Synthetic gradient generator

Microscopy acquisition containers

Format Extensions Status Notes
Zeiss ZVI .zvi OLE2/CFB; each mosaic tile a separate series
Zeiss XRM/TXRM .xrm .txrm .txm CFB X-ray tomography (Java reads uncompressed only too)
OME-XML .ome Inline BinData + external <TiffData>/<UUID> companion files
Zeiss LSM .lsm TIFF + CZ_LSMInfo
Olympus FV1000 OIF/OIB .oif .oib INI + companion TIFF; .oib via OLE2/CFB
Leica LEI .lei DIMDESCR dimensions/physical sizes + companion TIFF
Leica TCS .xml Full C/Z/T from Leica handler
MicroManager metadata.txt .json Per-plane file map, multi-position, channel/calibration metadata
Visitech .xys .html .html/.xys parse + multi-position series
Zeiss CZI .czi Scene/acquisition/angle series, mosaic stitching + fusion rebalancing, per-pixel-type split, rotation→moduloZ, PALM split, pyramids; JPEG-XR needs jpegxr feature
Nikon ND2 .nd2 🟡 Raw/zlib/JPEG2000 frames; modern chunked ImageDataSeq blocked on fixtures
Prairie View .xml .cfg .env .tif Channels/metadata + stage-position multi-series
MetaMorph STK .stk .nd Per-plane UIC metadata + multi-STK .nd file-group series
Leica XLEF .xlef 🟡 XLEF/XLIF container resolved; LOF-backed references undecoded
Imaris IMS .ims 🟡 HDF5; whole-volume read cached (no hyperslab slicing)
Leica LIF .lif Detection only; container parser not ported

High-content screening (HCS)

Format Extensions Status Notes
PerkinElmer FLEX .flex .mea .res Factor scaling + well/field grouping + OME plate
InCell (GE) .xdce .xml Well/field Z/C/T series + OME plate/well/wellsample
PerkinElmer UltraVIEW .htm .tim .csv .zpo Full dataset parse, TIFF + numbered raw planes
MIAS (Maia Scientific) .tif Per-well series + tiled-mosaic stitching
Operetta / Columbus / InCell3000 / RCPNL .xml .rcpnl .frm Index → per-well/field series + plane→file mapping
ScanR / CellVoyager / BD Pathway .xml .mlf .exp Sparse-well compaction, CellVoyager tile stitching, BD montage field split
Tecan plate ASCII .asc Tab-separated plate → Float32
Yokogawa CV7000/8000 .wpi .mlf .mrf .wpi/.mlf/.mrf XML index → well/field series + OME plate
MetaXpress / SimplePCI / MIAS / Trestle / TissueFAXS / Mikroscan / Ionpath MIBI TIFFs .tif 🟡 Extension-only TIFF delegate; no format-specific assembly
Cellomics .c01 .dib 🟡 zlib + DIB decoded; sibling .mdb channel metadata via mdbtools-rs
CellWorX .htd .pnl Parses HTD dims but set_id unsupported

Whole-slide / pyramidal TIFF

Format Extensions Status Notes
Aperio SVS (+ generic WSI) .svs .ndpi .scn .vsi .afi SVS pyramid regroup + Aperio metadata
Hamamatsu NDPI .ndpi TIFF-based; vendor tags
Nikon NIS / FEI / Olympus SIS / Improvision / Zeiss ApoTome / Fluoview / Molecular Devices TIFFs .tif .tiff TIFF-based metadata scrape; pixels via TIFF engine
Leica SCN .scn XML series split + per-resolution pyramid mapping
Ventana/Roche BIF .bif BIF tile reassembly (overlap-averaged stitching)
Hamamatsu NDPIS .ndpis .ndpis multi-file channel index
Olympus cellSens VSI .vsi .ets pyramid + RAW/JPEG/J2K/PNG/BMP tiles, tag-tree dims/crop, orphan-ETS matching, dim collision-shift, prefix-gated value metadata
OpenSlide (MRXS/VMS/BIF) .mrxs .vms .bif 🟡 Feature-gated; multi-resolution

Vendor microscopy & cameras

Format Extensions Status Notes
Applied Precision DeltaVision .dv .r3d Extended headers, panels, stage positions
Gatan DM3/DM4 .dm3 .dm4 Tag-tree parse, endianness
Bio-Rad PIC .pic AXIS notes, multi-file grouping, RGB swap
IPLab .ipl .ipm Header + tag block
Bio-Rad GEL .1sc Chunk-walk, dynamic pixel offset
Li-Cor L2D .l2d .scn Manifest + companion TIFF
PCO B16 .b16 Raw uint16
Openlab Raw .raw LBLB header + raw plane
Photon Dynamics .hdr .img .pds Header + companion IMG
SM-Camera .smc 548-byte header
Andor SIF .sif SIFReader parity (Java has no v3 XML-footer path)
Princeton SPE .spe SPE 2.x + 3.x detection (Java keeps binary dims; matches Java)
Gatan DM2 .dm2 GatanDM2Reader parity (header + tag metadata)
Lab Imaging LIM .lim Matches Java (Java also rejects compressed LIM)
Hasselblad Imacon / Image-Pro IPW .fff .ipw Imacon XML tag; IPW OLE2 multi-TIFF
Hamamatsu DCIMG .dcimg 🟡 v0/v1 + four-corner correction (no Java reference)
Norpix StreamPix .seq 🟡 JPEG frames + timestamps (no Java reference)
TillVision .vws .pst 🟡 PST+INF sidecar; embedded VWS unsupported
Canon RAW / Minolta MRW / DNG (CFA) .cr2 .crw .mrw .dng CFA Bayer interpolation + bit unpacking + DNG EXIF/maker-note white-balance
Photoshop / QPTIFF / NIS TIFF wrappers .tif .qptiff .nif 🟡 Plain TIFF delegate; vendor metadata not parsed
Hamamatsu VMS/VMU .vms .vmu JPEG tile decoding not ported

Medical, volumetric & astronomy

Format Extensions Status Notes
MRC / CCP4 .mrc .mrcs .ccp4 .map .rec Endian detect, EMAN2/IMOD fixes, Y-flip
MetaImage (ITK/VTK) .mha .mhd Inline/detached, zlib, endian swap
NIfTI-1 / Analyze 7.5 .nii .nii.gz .hdr .img Single/paired/gz, color datatypes
ICS / ICS2 .ics gzip, endianness rules, dim ordering
Siemens Inveon .hdr (+.img) All data-type codes + endianness
POV-Ray DF3 .pov .df3 Raw voxel grid
SBIG astronomy .fts FITS-based
FITS .fits .fit .fts Primary HDU, big-endian, no BZERO/BSCALE (matches Java)
NRRD .nrrd .nhdr raw/gzip/ascii (Java has no bzip2 either)
DICOM .dcm .dicom .dic RLE/JPEG/JP2 encapsulation + multi-series companions (Deflate matches Java)
ECAT7 PET .v data_type 6 (matches Java, which supports only that)
Varian FDF .fdf matrix[] dims, XYTZC, bigendian honored
Molecular Dynamics GEL .gel TIFF-based; MD_FILETAG, square-root/linear scaling
Kodak BIP .bip KodakReader parity (GBiH/BSfD markers, float32 BE)
MINC .mnc MINC2/HDF5 + classic MINC-1 (pure-Rust NetCDF-3 parser)
PDS (planetary) .pds 🟡 Single-band raw; PE two-file .hdr/.IMG dialect not split

Electron / scanning-probe / AFM microscopy

Format Extensions Status Notes
FEI/Philips XL SEM .img Interlaced decode + metadata
INRIMAGE-4 .inr Header + planar read
Veeco/Nanoscope AFM .afm Text header + raw plane
Seiko / UBM / VG SAM / WA-Top SPM .xqd .pr3 .dti .wat Faithful binary headers
Unisoku STM/AFM .hdr .dat Companion hdr/dat
JPK AFM .jpk TIFF-based two-series
Scanco AIM/ISQ micro-CT .aim .isq ISQ + AIM v020/v030 headers
Zeiss TIFF SEM .tif TIFF delegate
Hitachi SEM .txt [SemImageFile] INI + companion image (HitachiReader parity)
LEO/Zeiss SEM .tif TIFF tag 34118 + AP_/DP_/SV_ metadata
RHK SPM .sm2 .sm3 .sm4 Real binary page header (XPM/text), scales, invertX/Y
TopoMetrix AFM .tfr .zfr 🟡 No Java reference; best-effort header parse
IMOD mesh / JEOL / Zeiss LMS / Quesant / PicoQuant / Bruker OPUS / ISS Vista .mod .dat .lms .afm .ptu .abs .iss Stubs — undocumented or decoder not ported

FLIM / lifetime / flow / HDF5

Format Extensions Status Notes
Lambert LI-FLIM .fli INI header, gzip, UINT12 packing
Becker & Hickl SDT .sdt .spc Multi-block; MCS-TA partial
Amira/Avizo Mesh .am .amiramesh Binary + ASCII streams
Spider EM .spi .xmp Float32 header + planar
Amnis FlowSight CIF .cif TIFF + greyscale/bitmask codecs
CellH5 .ch5 HDF5; multi-position/well series (two-pass structure)
Aperio AFI / Bio-Rad SCN .afi .scn AFI channel XML; SCN MIME-multipart parse
Bruker MicroCT / Imaris TIFF / SlideBook TIFF .ctf .ims .tif 🟡 TIFF delegate; some companion metadata skipped
SimFCS .b64 .r64 .i64 🟡 Fixed 256×256 frames (no Java reference)
BigDataViewer .h5 🟡 HDF5; single series (no Java reference)
Lambert ASCII FLIM / Amnis IM3 / iVision / Olympus OIR / SlideBook 7 / Volocity clipping .asc .im3 .ipm .oir .sld .acff Stubs

Not yet implemented (stubs)

Detection works; set_id returns a descriptive UnsupportedFormat. Mostly proprietary/undocumented formats or ones needing an unported container parser or codec.

Format Extensions Reason
QuickTime .mov .qt MOV atom container not parsed
Volocity library / MVD2 / clipping .acff .mvd2 OLE2 / Metakit container
SlideBook / SlideBook 7 .sld Proprietary undocumented
Openlab LIFF .liff Proprietary undocumented
Sedat / APL / I2I / JDCE / PCI / HRD-GDF / NAF .sedat .apl .i2i .jdce .pci .gdf .naf Proprietary undocumented
KLB .klb No pure-Rust KLB decoder
Imspector OBF/MSR .obf .msr Header parses; stack payload not decoded
Leica LOF .lof Leica LAS proprietary binary
Burleigh .img .img too generic to detect
Woolz .wlz Graph-based format
File-pattern dataset .pattern Needs glob/regex multi-file expansion
FEI SER .ser Header parses; set_id unsupported

API overview

ImageReader — auto-detecting reader

Format is detected automatically from magic bytes first, then file extension.

use bioformats::ImageReader;

let mut reader = ImageReader::open(path)?;

// Multi-series files (e.g. LIF containers with multiple acquisitions)
for s in 0..reader.series_count() {
    reader.set_series(s)?;
    let meta = reader.metadata();
    println!("Series {}: {}x{}", s, meta.size_x, meta.size_y);
}

// Read a sub-region (avoids loading the entire plane)
let tile = reader.open_bytes_region(
    /*plane*/ 0,
    /*x*/ 128, /*y*/ 128, /*w*/ 64, /*h*/ 64,
)?;

// Pyramid levels (where supported, e.g. tiled TIFF)
println!("{} resolution levels", reader.resolution_count());
reader.set_resolution(1)?; // switch to half-resolution

ImageMetadata

pub struct ImageMetadata {
    pub size_x: u32,            // width in pixels
    pub size_y: u32,            // height in pixels
    pub size_z: u32,            // number of Z planes
    pub size_c: u32,            // number of channels
    pub size_t: u32,            // number of time points
    pub pixel_type: PixelType,  // Int8/Uint8/Int16/Uint16/Int32/Uint32/Float32/Float64/Bit
    pub bits_per_pixel: u8,
    pub image_count: u32,       // total planes = size_z * size_c * size_t
    pub dimension_order: DimensionOrder,
    pub is_rgb: bool,
    pub is_interleaved: bool,   // RGBRGB… vs RRR…GGG…BBB…
    pub is_indexed: bool,       // palette image
    pub is_little_endian: bool,
    pub resolution_count: u32,
    pub series_metadata: HashMap<String, MetadataValue>, // format-specific key/values
    pub lookup_table: Option<LookupTable>,
}

ImageWriter — auto-detecting writer

use bioformats::{ImageWriter, ImageMetadata, PixelType};

// One-shot convenience
ImageWriter::save(path, &meta, &planes)?;

// Streaming (for large Z-stacks)
let mut w = ImageWriter::open(path, &meta)?;
for (i, plane) in planes.iter().enumerate() {
    w.save_bytes(i as u32, plane)?;
}
w.close()?;

Format-specific writers

Access compression options through the crate-level types:

use bioformats::{FormatWriter, TiffWriter, WriteCompression};

let mut writer = TiffWriter::new().with_compression(WriteCompression::Deflate);
writer.set_metadata(&meta)?;
writer.set_id(Path::new("compressed.tif"))?;
writer.save_bytes(0, &plane_data)?;
writer.close()?;

FormatReader trait

Implement this to add a new format:

use bioformats::{FormatReader, ImageMetadata, Result};

struct MyReader { /* ... */ }

impl FormatReader for MyReader {
    fn is_this_type_by_bytes(&self, header: &[u8]) -> bool { /* magic check */ }
    fn is_this_type_by_name(&self, path: &Path) -> bool { /* extension check */ }
    fn set_id(&mut self, path: &Path) -> Result<()> { /* parse header */ }
    fn close(&mut self) -> Result<()> { Ok(()) }
    fn series_count(&self) -> usize { 1 }
    fn set_series(&mut self, _: usize) -> Result<()> { Ok(()) }
    fn series(&self) -> usize { 0 }
    fn metadata(&self) -> &ImageMetadata { &self.meta }
    fn open_bytes(&mut self, plane: u32) -> Result<Vec<u8>> { /* read pixels */ }
    fn open_bytes_region(&mut self, plane: u32, x: u32, y: u32, w: u32, h: u32) -> Result<Vec<u8>> { /* crop */ }
    fn open_thumb_bytes(&mut self, plane: u32) -> Result<Vec<u8>> { /* small preview */ }
}

Pixel data layout

open_bytes returns a flat Vec<u8> containing raw pixel samples, row-major (top-left origin), with the following conventions:

  • Multi-byte samples (16-bit, 32-bit, float): little-endian byte order (except FITS, which is big-endian as per the standard)
  • Interleaved RGB (is_interleaved = true): R G B R G B …
  • Planar multi-channel (is_interleaved = false): all of channel 0, then all of channel 1, …
  • Palette images (is_indexed = true): each byte is a colour-map index; the table is in meta.lookup_table
let meta = reader.metadata();
let bps = meta.pixel_type.bytes_per_sample(); // bytes per sample
let row_bytes = meta.size_x as usize * meta.size_c as usize * bps;
let plane = reader.open_bytes(0)?;
assert_eq!(plane.len(), meta.size_y as usize * row_bytes);

Planned (not yet implemented)

  • ND2: full coverage of modern chunked ImageDataSeq per-plane metadata (raw/zlib/JPEG2000 frames already decode; see status table)
  • CZI: JPEG-XR compression is available behind the jpegxr feature; multi-scene series are not yet split
  • Write support for LIF, ND2, CZI, PNM
  • OME metadata: reader.ome_metadata() returns baseline OME metadata for all readers and enriches it with structured physical sizes, channel names, and plane positions where supported; richer parsing (instrument, experimenter) is partial
  • Pyramid writing for tiled multi-resolution TIFF

Performance

Reading all planes from each fixture, using one long-lived process per implementation with 5 warmup iterations and 20 measured iterations:

Fixture Java Bio-Formats bioformats-rs Ratio
CZI, 672x512, 63 planes, 43,352,064 pixel bytes 177.342 ms 51.438 ms 3.45x
OME-TIFF, test/tubhiswt_C0.ome.tif 13.463 ms 2.432 ms 5.54x

Reproduce by building the warmup harnesses and running bench/BfBench.java and bench/bench_rust.rs against the same fixture. This measures open + read all planes + close per iteration after warmup, not cold JVM startup.

How to cite

If you use this package, cite the original Bio-Formats paper:

Melissa Linkert, Curtis T. Rueden, Chris Allan, Jean-Marie Burel, Will Moore,
Andrew Patterson, Brian Loranger, Josh Moore, Carlos Neves, Donald MacDonald,
Aleksandra Tarkowska, Caitlin Sticco, Emma Hill, Mike Rossner, Kevin W.
Eliceiri, and Jason R. Swedlow (2010) Metadata matters: access to image data
in the real world. The Journal of Cell Biology 189(5), 777-782.
doi: 10.1083/jcb.201004104

License

GPL v2