TIFF family
Many microscopes, slide scanners and cameras write TIFF files with their own metadata conventions: OME-TIFF, ImageJ, Zeiss LSM, MetaMorph, NIS-Elements, Aperio, Hamamatsu, Philips, PerkinElmer, Micro-Manager and others. OpenReadout reads all of them with one reader and returns images with their pixel size, channels and planes. The table below lists each convention.
Derived from the open TIFF 6.0, BigTIFF and OME-TIFF specifications, public files, and the public documentation of tifffile (BSD-3-Clause) for the vendor conventions. See docs/provenance/tiff.md.
One reader (tiff, crate openreadout-tiff) handles the TIFF container and every microscopy convention layered on it. The container walk and the pixel decoder are shared; a sub-format (“flavor”) decides how pages become images. info names it in notes (TIFF sub-format: …).
sub-format (Flavor) |
detected by | images |
|---|---|---|
ome-tiff (OmeTiff) |
page 0 ImageDescription parses as OME-XML with Image elements, or holds BinaryOnly |
one per OME Image with TiffData |
ome-companion (OmeCompanion) |
the opened file is a *.companion.ome XML document |
as ome-tiff, pixels in the referenced TIFFs |
zeiss-lsm (Lsm) |
tag 34412 with a valid LSM info record | one per position/tile |
metamorph-stk (MetamorphStk) |
page 0 carries tags 33628 and 33629 (UIC1, UIC2) | one; the UIC2 planes as Z (or T) |
nis-elements (NisElements) |
tag 65331 holds a block named MetadataTiffV1_0 (Nikon NIS-Elements TIFF export) |
the export’s files grouped by name: one image per xy position, c/z/t from the name tokens |
aperio-svs (Svs) |
ImageDescription starts with Aperio |
the baseline (focal planes as Z) |
philips-tiff (PhilipsTiff) |
Software starts with Philips and page 0’s description is an XML DataObject of type DPUfsImport |
one pyramidal image (§ Philips TIFF) |
hamamatsu-ndpi (Ndpi) |
tag 65420 present | the largest pyramid page (focal planes as Z) |
perkinelmer-qptiff (Qptiff) |
Software starts with PerkinElmer-QPI or the description is a PerkinElmer-QPI-ImageDescription |
full-resolution pages as channels |
metaseries (MetaSeries) |
Software = MetaSeries and a <MetaData> description |
as plain, with the MetaSeries calibration, name, time and objective |
imagej (ImageJ) |
description starts with ImageJ= key/value lines |
one hyperstack |
micro-manager (MicroManager) |
tag 51123 on a file with no OME-XML/ImageJ description | as plain |
plain (Plain) |
anything else | pages grouped by identical geometry, each group one image with its pages as Z |
metamorph-nd (MetamorphNd) |
the opened file is a .nd text file starting with "NDInfoFile" |
one per stage position; wavelengths as C, time points as T, each file’s planes as Z |
Micro-Manager JSON (tag 51123) is also read on top of any other flavor (pixel size, channel, exposure, camera, time). When a flavor’s metadata is inconsistent (e.g. OME-XML that does not parse), the reader falls back to plain and says so in notes.
Container (TIFF 6.0 and BigTIFF — open specifications)
Header
| offset | size | field | meaning |
|---|---|---|---|
| 0 | 2 | byte_order |
II little-endian, MM big-endian |
| 2 | 2 | version | 42 = classic TIFF (TIFF_MAGIC), 43 = BigTIFF (BIGTIFF_MAGIC) |
| 4 | 4 (classic) | first_ifd_offset |
u32 |
| 4 | 2+2 (BigTIFF) | offset size, reserved | must be 8 and 0 |
| 8 | 8 (BigTIFF) | first_ifd_offset |
u64 |
IFD (image file directory, a “page”)
Classic: u16 entry count, 12-byte entries, u32 next-IFD offset. BigTIFF: u64 count, 20-byte entries, u64 next offset. An entry is tag (u16), field_type (u16), count (u32/u64), and either the value itself (when it fits in 4/8 bytes) or value_offset. Field types 1–13 and 16–18 are decoded (rationals are divided out); unknown types are skipped as the specification requires. The main chain is walked from the header; cycles, offsets inside the header, directories past the end of file and out-of-range values are recorded as StructureProblems (problems) and reported by check rather than aborting the open. SubIFDs (tag 330) are read on demand for pyramid levels.
Tags the reader uses
| tag | spec name | our use |
|---|---|---|
| 254 | NewSubfileType | bit 0 = reduced resolution (thumbnail, pyramid level); 1/9 = SVS label/macro |
| 256/257 | ImageWidth/ImageLength | plane size |
| 258 | BitsPerSample | with 339 → pixel type |
| 259 | Compression | 1 none, 5 LZW, 6 old-style JPEG (tables form), 7 JPEG, 8/32946 deflate, 32773 PackBits, 50000 zstd, 33003/33004/33005/34712 (JPEG 2000), 50001 WebP, 50002/52546 JPEG XL decoded; 34887 LERC recognized, decoded only in builds with the lerc feature |
| 262 | PhotometricInterpretation | 2 RGB / 6 YCbCr → interleaved RGB samples; YCbCr is converted to RGB only inside JPEG and JPEG 2000 (Aperio 33003 codes YCbCr under photometric 2) |
| 270 | ImageDescription | OME-XML, ImageJ, Aperio, QPI conventions |
| 271/272/305/306 | Make/Model/Software/DateTime | instrument and acquisition time for plain/NDPI files |
| 273/279, 324/325 | Strip/Tile offsets and byte counts | chunk table |
| 277 | SamplesPerPixel | samples per pixel |
| 278 | RowsPerStrip | strip height (default: whole image) |
| 282/283/296 | X/YResolution, ResolutionUnit | physical pixel size: µm = 10 000 / res (cm) or 25 400 / res (inch); inch resolutions of 1, 72, 96, 150, 300, 600 are treated as screen/print defaults and ignored; unit 1 (none) gives no size |
| 284 | PlanarConfiguration | 1 chunky (samples interleaved), 2 planar (one sample per chunk) |
| 317 | Predictor | 2 horizontal differencing, 3 floating point (Adobe technical note 3) |
| 322/323 | TileWidth/TileLength | tiled pages |
| 330 | SubIFDs | OME-TIFF sub-resolutions |
| 266 | FillOrder | 1 most significant bit first (default), 2 least significant first (packed samples only) |
| 339 | SampleFormat | 1 unsigned, 2 signed, 3 float, 5 complex integer, 6 complex float |
| 347 | JPEGTables | abbreviated table stream spliced before each JPEG chunk |
| 34412 | (LSM info) | Zeiss LSM record, below |
| 33628–33631 | (MetaMorph UIC1–UIC4) | STK settings, per-plane Z distance and times, wavelengths, per-plane arrays (below) |
| 65325–65333 | (NIS-Elements) | time, pixel size, stage position, metadata blocks of a NIS-Elements TIFF export (below) |
| 50838/50839 | (ImageJ metadata) | ImageJ binary metadata: info, slice labels, display ranges |
| 51123 | (Micro-Manager metadata) | JSON per plane |
| 65420–65449 | (NDPI) | NDPI flag, magnification, slide-centre offsets, focal-plane Z offset, slide label, scanner serial |
Pixel data
A page’s chunks (strips or tiles) are decoded one by one: read byte_counts[i] bytes at offsets[i], decompress, undo the byte order (big-endian files; not for predictor 3), undo the predictor, then copy the rows into the plane (edge tiles are cropped). Chunks with offset or byte count 0 were never written and read as zeros. The plane is the stored values, little-endian, rows unpadded — no photometric inversion, no palette expansion. Chunky multi-sample pages give interleaved samples (samples_per_pixel > 1, e.g. RGB); planar pages expose each sample as a channel. Pixel types: see “Sample formats” below. JPEG uses openreadout-codecs::jpeg_decode_tiff (the pure-Rust jpeg-decoder shared with the CZI reader, JPEGTables spliced in front of each chunk); the colour transform is chosen per chunk as described in “JPEG colour” below. Pyramid levels, thumbnails and macro pages of the slide corpus files decode to within 0.05 of tifffile’s mean (tests/corpus_pages.rs). JPEG decoders may differ from libjpeg-turbo by one count in a small fraction of samples (IDCT rounding), so JPEG corpus files are marked lossy and compared by plane mean when the hash differs. Planes larger than MAX_PLANE_BYTES (4 GiB) are refused whole with exit 6 (whole-slide level 0); info, info --view structure and check still work.
A tile in the last row of tiles may decode to fewer rows than the tile height when it holds only the rows inside the image (GDAL writes such PackBits tiles); it is padded with zero rows, as libtiff and tifffile do. A chunk short of rows inside the image is corrupt (exit 4).
Sample formats
PageLayout::coding maps (BitsPerSample, SampleFormat) to the returned pixel type and a SampleCoding:
| stored | returned | SampleCoding |
|---|---|---|
| 8/16/32-bit integers, 32/64-bit floats | as stored | Native |
| 64-bit integers (format 1, 2) | uint64 / int64 |
Native |
| 64/128-bit complex floats (format 6: real, imaginary) | complex / double-complex |
Native (each part byte-swapped separately) |
| 1–7, 9–15, 17–31-bit integers (format 1, 2) | uint8/int8, uint16/int16, uint32/int32 |
Packed (bits, signed): packed most significant bit first, every row starting on a byte (FillOrder 2 reverses the bits of each byte first); signed values are sign-extended |
| 16-bit floats (format 3) | float |
Float16 (IEEE half, widened exactly) |
| 24-bit floats (format 3) | float |
Float24 (1 sign, 7 exponent bits biased by 63, 16 mantissa bits, in the file’s byte order; widened exactly) |
| 32/64-bit complex integers (format 5: two int16 / int32) | complex / double-complex |
ComplexInt (widened exactly) |
A 24-bit integer is a packed sample like a 12-bit one (most significant bit first, whatever the file’s byte order), while a 24-bit float is stored in the file’s byte order: GDAL’s int24.tif decoded this way equals its int12.tif (the same test image), and float24.tif equals tifffile. Packed, half-float, 24-bit float and complex-integer samples are decoded with no compression, LZW, deflate, PackBits or zstd; the horizontal predictor is undone on complex integers, the floating-point predictor on half and 24-bit floats; other combinations exit 6. check and info report other bit depths (33 bits and more, 64-bit floats in format 1) as unsupported samples. 12-bit JPEG exits 6.
Regions and pyramid levels
read_region decodes only the chunks a rectangle overlaps (in parallel), and crops each into a region-sized buffer; uncompressed chunks are read row segment by row segment, so a window of a huge uncompressed page reads only its own bytes. Decoded chunks are kept in a 64 MiB ChunkCache per open file for the next region. Levels: OME-TIFF — the plane’s own page lists its reduced levels in SubIFDs (330), largest first; whole-slide flavours — the reduced pages of the main chain (SVS, NDPI, QPTIFF, plain reduced pages), the k-th page with the level’s size (counted from the level’s first page) belonging to the k-th distinct full-resolution page of the image (QPTIFF: one per channel; NDPI: one per focal plane). info → resolution_levels lists each level with its tile (or width × RowsPerStrip) size. Regions of every level of the corpus pyramids equal tifffile’s (corpus/oracle/regions/).
Chunks are read in file order and, for costly codecs (LZW, JPEG, JPEG 2000, deflate, zstd), decoded on the thread pool a batch (4 per thread) at a time, then pasted in order; the result does not depend on the thread count.
JPEG 2000 (compressions 33003, 33004, 33005, 34712)
Each chunk is a raw JPEG 2000 codestream (FF4F FF51; a JP2 file is unwrapped to its jp2c box), decoded by openreadout-codecs::jpeg2000_decode_limited (rust-j2k: pure Rust, OpenJPEG-style mid-point reconstruction). The codestream’s component count must equal SamplesPerPixel and its precision BitsPerSample (8 or 16, unsigned); the decoded tile is cropped or zero-padded to the chunk. Sub-sampled components (4:2:2 chroma) are replicated to the full grid. Aperio writes two flavours (tifffile’s names APERIO_JP2000_YCBC and APERIO_JP2000_RGB):
| compression | writer (from the codestream’s COM marker) | components | colour |
|---|---|---|---|
| 33005 | Kakadu (Kakadu-v6.4.1) |
R, G, B with the codestream’s irreversible colour transform (COD MCT = 1) | undone by the decoder |
| 33003 | Matrox (MIL) |
Y, Cb, Cr, no multiple-component transform, Cb/Cr at half horizontal resolution (SIZ XRsiz = 2), photometric still 2 | converted here as OpenJPEG’s sycc_to_rgb does: R = Y + (int)(1.402·Cr'), G = Y − (int)(0.344·Cb' + 0.714·Cr'), B = Y + (int)(1.772·Cb') with Cb' = Cb − 128, Cr' = Cr − 128, truncation toward zero, clamped |
Irreversible (9/7) tiles are not bit-exact across decoders: on 156 Aperio tiles (both files, levels 0, 1 and 2) our samples are within 1 grey level of OpenJPEG (tifffile + imagecodecs) for 33005 and within 2 for 33003 (the colour conversion adds one), with 0.03–0.07 % of samples differing; OpenSlide differs from OpenJPEG by up to 1 at level 0 in 42 % of 33003 samples (its own YCbCr conversion). The corpus files are marked lossy.
WebP, JPEG XL, LERC and old-style JPEG (compressions 50001, 50002/52546, 34887, 6)
Validated on GDAL’s autotest samples (crates/openreadout-tiff/tests/fixtures/codecs/, MIT; strips, tiles, planar and chunky pages): every sample equals tifffile + imagecodecs’ (libwebp, libjxl, Esri lerc).
- WebP (50001): each chunk is a WebP file (lossy VP8 or lossless VP8L), decoded by
image-webp. A lossless chunk without alpha in a 4-sample page gets opaque alpha (255); an alpha channel in a 3-sample page is dropped. - JPEG XL (50002; 52546 is the DNG 1.7 code): each chunk is a JPEG XL codestream or container, decoded by
jxl-oxide: first frame, colour channels then extra channels; integers rounded as libjxl does (v × max + 0.5). - LERC (34887), off by default:
lerc-rs0.5 panics on malformed blobs (an index out of bounds in its mask handling, an arithmetic overflow; found by fuzzing, SECURITY.md), and withpanic = "abort"that would end the process, so release builds leave LERC out (exit 6, with a hint to convert the file with GDAL). With thelerccargo feature ofopenreadout-tiff(for trusted files): each chunk is a LERC2 blob, first unwrapped asLercParameters(tag 50674) says: value 2 is 0 none, 1 deflate, 2 zstd (value 1 is the LERC version). Every blob header is validated first (rows, columns, depth, valid-pixel count, micro-block size, blob size). Pixels the blob’s validity mask marks invalid are 0 (tifffile’s convention; GDAL substitutes itsGDAL_NODATAvalue, tag 42113, which we do not apply). - Old-style JPEG (6), “tables” form (TIFF 6.0 section 22): chunks hold entropy-coded data only; the tables are separate, one per component, at the offsets in JPEGQTables (519, 64 bytes in zig-zag order, as in a DQT segment), JPEGDCTables (520) and JPEGACTables (521, each 16 code-length counts then the values, as in a DHT segment); JPEGRestartInterval (515) gives a DRI. We rebuild
SOI, DQT, DHT, DRI, an extended-sequential frame header (SOF1, so four table ids are allowed) sized to the chunk, with Y sampled at YCbCrSubSampling (530, default 2 × 2) and the others 1 × 1, and a scan header, and decode that withjpeg-decoder. A chunk that already starts withSOIis decoded as is. YCbCr (photometric 6) is converted to RGB the TIFF 6.0 section 21 way — ReferenceBlackWhite (532) and YCbCrCoefficients (529) apply, as in libtiff — not the full-range JFIF way used for compression 7. On the one public sample (corpusgdal-ojpeg-zackthecat, ReferenceBlackWhite 16–235) we are within 1.02 grey levels on average of Pillow + libtiff (9.5 with JFIF conversion; worst sample 20, at colour edges: libtiff converts from sub-sampled chroma, jpeg-decoder interpolates it first). Not decoded (exit 6): JPEGProc 14 (lossless) and pages that keep one whole JPEG stream at JPEGInterchangeFormat (513) instead of the table tags.
JPEG colour
A three-component JPEG stream holds either R, G, B or Y, Cb, Cr; the TIFF page and the stream each say which, and writers disagree. Decided per chunk from PhotometricInterpretation and the stream’s own markers (codecs::jpeg_markers: JFIF APP0, Adobe APP14 transform byte, frame component ids and sampling), following libjpeg 6b as tifffile drives it:
| photometric | planar | stream | decoded as | seen in |
|---|---|---|---|---|
| 1 / 0 (grey) | any | 1 component | grey | — |
| 2 (RGB) | 1 | JFIF | Y, Cb, Cr → R, G, B | Bio-Formats 6.x (synthetic-tiff-jpeg-bf670-*): 4:2:0 JFIF streams in photometric-RGB pages |
| 2 (RGB) | 1 | no JFIF | components as coded (R, G, B) | Aperio SVS (ids 0, 1, 2), QPTIFF and tifffile outcolorspace="RGB" (ids R,G,B, Adobe 0) |
| 2 (RGB) | 2 | 1 component per plane | grey per sample | Bio-Formats 6.x from planar input |
| 6 (YCbCr) | 1 | any | the stream decides: JFIF → YCbCr; else Adobe 0 → RGB, 1 → YCbCr; else ids R,G,B → RGB; else YCbCr |
NDPI, Bio-Formats 8.x, tifffile’s default |
| 6 (YCbCr) | 2 | — | refused (exit 6): separate Y, Cb, Cr planes are not converted | Bio-Formats 8.x from planar input (tifffile refuses too) |
| 3, 4, 5, ≥ 7 | any | — | refused (exit 6) | — |
| any | any | 2 or ≥ 4 components | refused (exit 6) | — |
Photometric RGB without JFIF but with an Adobe segment declaring YCbCr is decoded as coded (as tifffile does); check flags it jpeg_colour_ambiguous. check reads the markers of every JPEG page’s first written chunk and reports the refused combinations as unsupported_samples warnings, so a page that would decode to wrong colours is never silent. (Before 2026-09-24 photometric RGB always meant “as coded”, and Bio-Formats 6.x pages came back as Y, Cb, Cr.)
OME-TIFF (open standard: OME-TIFF specification, OME-XML schemas 2008-02 … 2016-06)
- The OME-XML block is page 0’s
ImageDescription. Element and attribute names inOmeDocumentfollow the OME schema. - Each
Image→ one image.Pixels/@SizeX/Y/Z/C/T,DimensionOrder,Type; physical sizes andTimeIncrementare converted to µm and seconds from their*Unitattributes (default µm / s). WhenTimeIncrementis absent but planes carryDeltaT, the mean step over T is used. Channel:Name,Fluor,ExcitationWavelength/EmissionWavelength(→ nm),Color(signed RGBA →#RRGGBB),AcquisitionMode+ContrastMethod→ one readableacquisition_modelabel (book/src/guides/metadata.md§ General rules); the detection band (emission_band_*_nm,emission_range_nm) from the channel’s emission filters:LightPath/EmissionFilterRef(else theEmissionFilterRefs of itsFilterSetRef) →Instrument/Filter/TransmittanceRangeCutIn/CutOut(→ nm, default nm); with several filters in the path the band all of them pass (largest cut-in, smallest cut-out); none when a filter lacks an edge or the band is empty;Plane/@ExposureTimeof the first plane of each channel →exposure_ms.SamplesPerPixel> 1 (RGB) dividesSizeC: chunky pages give interleaved samples, planar pages give one channel per sample.TiffDatamaps IFDs to planes: planes are rasterised inDimensionOrder(fastest first);IFD(default 0),FirstC/Z/T(default 0) andPlaneCount(default: every IFD of the file, or 1 whenIFDis given; legacyNumPlanesaccepted) placePlaneCountconsecutive IFDs starting at the linear index of(FirstC, FirstZ, FirstT).- Multi-file sets:
TiffData/UUIDtext names the file holding the IFDs; matched first against the UUIDs of files already in the set (OME/@UUIDof the opened file), then byUUID/@FileName, resolved in the opened file’s directory (directory components are stripped). Siblings are opened lazily. A missing sibling makes the affected planes fail with exit 4 and is reported bycheck(missing_file). A renamed single-file dataset (noOME/@UUID, everyTiffDatanaming one file that does not exist) is read from the opened file, with a note. BinaryOnly/@MetadataFile: the opened file holds pixels only; the OME-XML is read from the named file — either a*.companion.omeXML document or another OME-TIFF. A*.companion.omecan also be opened directly.Instrument(Microscope,Objective,Detector— the first one is the instrument’s detector — andFilter/FilterSet) resolved throughInstrumentRef/ObjectiveSettings(else the first).AcquisitionDate→acquired_at.StructuredAnnotationslinked byAnnotationRefappear underimages[].extra.annotations(kind, namespace, value;MapAnnotationas an object); all of them are ininfo --view full.MapAnnotations of namespaceopenreadout.dev/normalized(NORMALIZED_NS, written by our own export) hold model fields OME cannot carry exactly; linked from theImagethey giveacquired_at(full precision, used only when it agrees withAcquisitionDateto the second),instrument.software,instrument.software_versionandobjective.immersion; linked from aChannel,acquisition_mode. They are read into the model and not repeated inextra.annotations.- SubIFDs of the first plane’s page →
pyramid_levelsandinfo --view structureentries; only full resolution is read. - Our own exports (
openreadout export … -o x.ome.tiff) round-trip: geometry, pixel type, every plane hash and the normalized metadata (channels with bands and modes, objective, instrument with software, exact acquisition time, image names) are reproduced;check --againstfinds them identical (ome_tiff_round_trip_keeps_metadata_corpusfor LIF, CZI and ND2 files,crates/openreadout-ometiff/tests/metadata_round_trip.rs). What does not round-trip is listed inbook/src/guides/metadata.md§ OME-XML export.
ImageJ hyperstacks
ImageDescription lines key=value, first key ImageJ. channels, slices, frames (default 1) give C, Z, T; images without them is a plain stack exposed as Z. Page order follows order (default czt: channel fastest, then slice, then frame; the other five permutations are honoured). Pixel size = 1 / XResolution in unit (micron, um, µm, nm, mm, cm, m, inch; pixel gives none), Z step = spacing × unit, frame interval = finterval (× tunit, default seconds) or 1 / fps. RGB pages with channels equal to the sample count are one channel of interleaved RGB; planar multi-sample pages expose samples as channels. When the file holds fewer pages than planes and page 0’s data is one contiguous uncompressed block, the remaining planes follow it back to back (ImageJ “virtual” stacks and > 4 GiB files). Slice labels (binary metadata labl) become channel names only when there is one label per channel.
Zeiss LSM (tag 34412; layout from tifffile’s public documentation)
The LSM info record (little-endian) — offsets in bytes:
| offset | type | our name | meaning |
|---|---|---|---|
| 0 | u32 | magic |
0x0300494C or 0x0400494C (LSM_INFO_MAGICS) |
| 4 | i32 | record_size |
record length; later fields exist only if it covers them |
| 8, 12, 16 | i32 | dim_x, dim_y, dim_z |
voxel counts |
| 20, 24 | i32 | dim_channels, dim_time |
channels, time points |
| 28 | i32 | data_type |
1 = 8-bit, 2 = 12-bit, 5 = float, 0 = varies |
| 40, 48, 56 | f64 | voxel_size_x_m, voxel_size_y_m, voxel_size_z_m |
metres |
| 88 | u16 | scan_type |
0 xyz stack, 3 xy time series, 6/7 xyz time series (others: line/point/spline scans, not images) |
| 90 | u16 | spectral_scan |
non-zero for lambda stacks |
| 108 | u32 | (offset) | channel colours and names sub-record |
| 112 | f64 | time_interval_s |
seconds |
| 132 | u32 | (offset) | time stamps sub-record: i32 size (= 8 + 8·n), i32 n, n × f64 seconds |
| 204 | u32 | (offset) | channel wavelength ranges: i32 n, n × (f64, f64) metres |
| 264, 268 | i32 | dim_positions, dim_tiles |
positions and mosaic tiles (LSM 4.2+) |
Channel colours/names sub-record: u32 size, u32 colour count, u32 name count, u32 colour offset, u32 name offset, u32 mono; colours are RGBA bytes; names are u32-length-prefixed NUL-terminated strings. Pages alternate data and thumbnail (thumbnails have NewSubfileType 1 and are listed as attachments). Channels are the samples of each data page. Data pages run T outer, Z inner, per position. In compressed LSM files StripByteCounts holds uncompressed sizes; the reader uses the distance to the next strip instead. Time increment = (last − first time stamp) / (T − 1) when stamps exist, else time_interval_s.
MetaMorph STK, MetaSeries and .nd series (layout from corpus files and tifffile’s public documentation)
STK (StkInfo, parse_stk, is_stk). Page 0 carries four private tags whose declared counts do not describe their sizes, so they are read raw from their value_offset (little-endian files only):
| tag | our const | declared | stored | our fields |
|---|---|---|---|---|
| 33628 | STK_TAG_SETTINGS |
LONG × n | n (id u32, value u32) pairs; ids 4/5/6/7/16/17 hold an offset | 3 → calibrated (non-zero = on), 4/5 → x_calibration/y_calibration (u32 / u32 at the offset), 6 → calibration_units, 7 → name (u32 length + text at the offset) |
| 33629 | STK_TAG_PLANES |
RATIONAL × planes | 6 u32 per plane | plane_count = count; z_distance (rational); created = (MetaMorph day number, milliseconds since midnight); modification times are not used |
| 33630 | STK_TAG_WAVELENGTHS |
RATIONAL × planes | as declared | wavelengths_nm |
| 33631 | STK_TAG_PLANE_ARRAYS |
LONG × planes | u16 id + data, until id 0 | 28 → stage_x/stage_y (per plane x num, x den, y num, y den; signed numerators), 37 → stage_labels (per plane u32 length + text), 40 → absolute_z (per plane signed rational), 29/41 skipped; an id of unknown size ends the walk |
ImageDescription holds one NUL-separated text per plane (plane_texts); Exposure: 40 ms in the first gives exposure_ms. Planes: page 0’s strips cover plane 0; plane i starts at the first strip offset + i × plane bytes (uncompressed and contiguous only; anything else exits 6). plane_axis: Z when every Z distance is non-zero, else T when the creation times differ, else Z. pixel_size_um = the calibration × unit_um(calibration_units) when calibration is on; otherwise the TIFF resolution tags (as Bio-Formats does: 312 pixels/cm → 32.05 µm). z_step_um = the common Z distance. metamorph_time: day number + 1 = Julian day number (2440588 = 1970-01-01), local time, no zone; metamorph_seconds for differences. acquired_at = the first plane’s time; time_increment_s for T stacks. extra.metamorph (to_json), extra.stage_position_um {x, y, z} of plane 0, extra.stage_label. Frames (info --view full): per plane z (or t), delta_t_s, time_local, stage_x_um, stage_y_um, stage_z_um, wavelength_nm.
MetaSeries (MetaSeriesPlane, parse_metaseries). Software = MetaSeries; the description is <MetaData> with prop/custom-prop elements (id, type, value) at the root, in PlaneInfo and in SetInfo (kept as props: root, plane, set). Used: spatial-calibration-state/-x/-y/-units → pixel_size_um; image-name → image_name (image name); acquisition-time-local (20240816 09:49:42.978, metaseries_time) → acquired_local → acquired_at (local time); _IllumSetting_ → illumination (channel name); _MagSetting_ → objective (with magnification from a leading 20x), _MagNA_ → objective_na; Description Exposure: … → exposure_ms; stage-position-x/y, z-position → stage_um; wavelength → wavelength_nm; ApplicationName/ApplicationVersion → application/application_version (software); number-of-planes → plane_count. extra.metaseries keeps stage-label, camera-binning-x/y, wavelength, acquisition-time-local.
.nd series (NdFile, parse_nd). A text file of "Key", value lines (values quoted or not; a Description value continues on the following lines until the next quoted key; "EndFile" ends it) — all kept in keys. NDInfoFile → version; StartTime1 → start_time → every image’s acquired_at (local time, a note says so); DoTimelapse/NTimePoints → timelapse/time_points; DoStage/NStagePositions/Stage<j> → stages (image names); DoWave/NWavelengths/WaveName<i>/WaveDoZ<i> → waves (channel names)/wave_do_z; WaveInFileName → wave_in_file_name; DoZSeries/NZSteps → z_steps; ZStepSize (decimal point or comma) → z_step_um. image_count = stages (≥ 1), channel_count = wavelengths (≥ 1).
File names (file_stem): the .nd stem, then _w<i> + the wavelength name when WaveInFileName (only with wavelengths), _s<j> (only with stages), _t<k> (only with a time lapse), all one-based; extension .tif, .stk or .tiff in any case, matched against the directory listing case-insensitively. Each file is a member of the data set (info --view structure lists them, check opens them); a missing file makes its planes fail with exit 4 and check report missing_file. The geometry, pixel type and calibration come from the first member that opens (STK calibration, else MetaSeries, else resolution tags); Z = NZSteps (or the first file’s plane count when the .nd declares no Z series, with a note), Z step = ZStepSize (else the STK Z distance). Plane (c, z, t) of image s is plane z of the file of (s, c, t): an STK stack plane read contiguously, or page z of another TIFF. A wavelength with WaveDoZ FALSE in a Z series repeats its single plane at every Z (inferred; no corpus file, a note says so). Files named like the series past NTimePoints are counted in a note and not read (Figshare 7583960 holds a 25th time point). extra: description, stage_index, files (the first 64 member names). vendor: nd (every key), first_file_stk, first_file_metaseries.
Nikon NIS-Elements TIFF exports (layout from corpus files; see docs/provenance/tiff.md)
NIS-Elements’ “export ND document to TIFF” writes one single-page TIFF per position, channel, Z plane and time point. Detection (parse_nis, NisPage): tag 65331 (NIS_TAG_METADATA) is a block whose directory names MetadataTiffV1_0 (UTF-16 at bytes 8–40). Values read (all DOUBLE, meanings inferred — see provenance): 65325 (NIS_TAG_TIME) → time_ms, time since the acquisition started; 65326 (NIS_TAG_PIXEL_SIZE) → pixel_size_um → physical size X = Y (0.1625 µm in the corpus: 6.5 µm camera pixels behind a 40× objective, and neighbouring positions 329.5 µm apart ≈ one 2048-pixel field); 65329 (NIS_TAG_STAGE) → stage_um (X, Y, Z µm; a fourth value repeats the time); 65327/65328 → unknown (1.0; kept in vendor.nis_elements). The LV-encoded blocks of 65330/65331 (objective, per-channel names, text information — the structures the ND2 reader decodes) and the XML of 65332 are not decoded.
File sequence. parse_sequence_name (SequenceName: prefix, indices, extension) reads index tokens from the end of the file stem: xy<n> (SequenceAxis::Position), c<n> (Channel), z<n> (Z), t<n> (Time), e.g. single_plane_1-2000_2xy01c1.tif → prefix single_plane_1-2000_2, xy 1, c 1 (the depositors describe xy as the stage positions and c1/c2 as the two channels; z and t are inferred from the pattern). Opening any file of an export reads every file in the same directory with the same prefix, extension and token letters (axes, index) as one data set: one image per distinct xy (named <prefix>xy<nn>), channels, Z planes and time points by sorted distinct numbers (channels named c<n>), plane (c, z, t) of an image = page 0 of its file. A missing combination is an unmapped plane (check: missing_planes). Files are members (info --view structure, index). The opened file’s own image carries extra.nis_elements {file, time_ms} and extra.stage_position_um. A NIS export whose name has no tokens is one image. Bio-Formats (black box) reads each file on its own; its planes equal ours file by file.
Aperio SVS
ImageDescription = header line(s) + |-separated key = value pairs (AperioDescription: header, keys). MPP → physical size X = Y (µm); AppMag → objective nominal magnification; Date (MM/DD/YY) + Time → acquired_at; ScanScope ID, Filename, all keys under images[0].extra.aperio. Page roles: page 0 tiled baseline; page 1 stripped thumbnail; further tiled pages of the baseline size are focal planes (Z), smaller ones pyramid levels; remaining stripped pages are label (NewSubfileType 1) and macro (9) — listed by info --view structure as attachments, not images.
Leica SCN
An SCN file (Leica SCN400/SCN400F; extension .scn, shared with Bio-Rad gel images, which are not TIFF) is a BigTIFF whose first ImageDescription is an XML document in the namespace http://www.leica-microsystems.com/scn/2010/10/01 (parse_scn → ScnDocument: collection_name, uuid, collection_size_nm, barcode, images). Layout read from the corpus files and tifffile’s SCN series (BSD-3, documentation and oracle); OpenSlide’s public format page was also consulted.
collection(sizeX,sizeY: the slide in nanometres;barcodeas stored, base64 text in the corpus) holds oneimageper scan: the slide overview(s) and the scanned regions, each an image of ours in document order (ScnImage:name,uuid,creation_date,device_model,device_version,size_x,size_y,dimensions,view_size_nm,view_offset_nm,spacing_z,objective,numerical_aperture,illumination,channels).pixels/dimension(ScnDimension:size_x,size_y,r,c,z,ifd) names the page of each resolutionr(0 = full), channelcand focal planez(both 0 when absent). Level 0 pages are the image’s planes (C = distinctc, Z = distinctz; an RGB page with onecis three interleaved samples); everyr > 0that lists a page for every plane is a pyramid level whose pages are taken from the dimensions (Level.pages), not guessed by size. Pages no image lists areassociatedattachments. An image whose level-0 pages are missing or of another size is skipped with a note.view(sizeX,sizeY,offsetX,offsetY, nanometres): the image’s extent and position on the slide. Pixel size = view size / pixel count (0.5 µm for a 20x region, 16.44 µm for the overview; equal to the pages’ resolution tags in the corpus),extra.scn.view_size_nm,view_offset_nm;extra.scn.covers_slidemarks an image whose view is the whole collection from its origin (an overview), also named in a note.creationDate→acquired_at;device@model/@version(;-separated: scanner, then software) → instrument model and software version (first part), manufacturer Leica, both whole inextra.scn;objectiveSettings/objective→ nominal magnification (0.60833 for the overview camera),illuminationSettings/numericalAperture→ lens NA,illuminationSource(brightfield,fluorescence) → the channels’acquisition_mode.- Fluorescence
channelSettings/channel(ScnChannel:index,name,rgb,excitation_filter,suppression_filter,dichroic,exposure_raw,ccd_gain): name, colour (#RRGGBB), excitation = the centre of the excitation filter (BP 405/60→ 405 nm,filter_centre_nm), emission band = the suppression filter’s centre ± half its width (470/50→ 445–495 nm,filter_band_nm).exposureTime(105000, 146000) is copied as stored (extra.scn.channels[].exposure_time_raw): its unit is not recorded.
Ventana BIF
A Roche Ventana BIF (.bif; iScan HT/Coreo, DP 200/600) is a BigTIFF marked by an XMP packet (tag 700) holding an iScan element (bif_iscan: the first of the first 16 pages that has one; OpenSlide’s public format page gives the same rule). Pages are named by their ImageDescription (page_role → BifPage): level=N mag=M quality=Q (Level with level, magnification) are the pyramid of one image (level 0 the full resolution; Level.pages names each level’s page), Label_Image / Label Image (Label, the slide overview), Probability_Image (Probability, the tissue map), Thumbnail (Thumbnail) and anything else (Other) are attachments (info --view structure), as for SVS.
iScanattributes (iscan_attributes, all underextra.bif.iscanas stored):ScanRes→ pixel size at level 0 (µm, as OpenSlide and tifffile read it; 0.25 in the corpus, equal to the resolution tags),Magnification→ objective nominal magnification (else the level-0 page’smag=),ScannerModel→ instrument model,BuildVersion→ software version (Softwaretag → software).- The full-resolution page’s XMP holds
EncodeInfo(stitching→BifStitching:rows,cols,tile_width,tile_heightfromSlideStitchInfo/ImageInfo;joints,overlapping_joints,max_overlap,directionsfrom itsTileJointInfoentries), summarised underextra.bif.stitching. The scanner records where neighbouring camera tiles overlap (OverlapX,OverlapY, aDirection); tifffile does not stitch and neither do we: tiles are returned on their stored grid. When any joint records an overlap (105 of 922 joints, 24 px, in the corpus file) a note says so and the assurance marks the pixels as partially decoded (--strictrefuses them). No public description establishes how the joints place tiles, so no stitching is attempted. - Volumetric BIF (ImageDepth, tag 32997, > 1 on the level-0 page) exits 6.
Philips TIFF
A Philips TIFF (the export format of Philips’ scanners and of converters that write it; .tiff,
BigTIFF) is detected by Software starting with Philips and page 0’s ImageDescription being an
XML DataObject of ObjectType DPUfsImport (parse_philips → PhilipsObject { object_type,
attributes, arrays }, attr, array, to_json). Rules from OpenSlide’s public format page;
layout read from the corpus files (docs/provenance/tiff.md).
- The XML is a tree:
Attributeelements (Name,Group,Element,PMSVRtype:IString,IUInt16,IStringArray,IDoubleArray— quoted lists,string_array) andIDataObjectArrayattributes holdingArray/DataObjectchildren.PIM_DP_SCANNED_IMAGESholds oneDPScannedImageperPIM_DP_IMAGE_TYPE(WSI,LABELIMAGE,MACROIMAGE;scanned_image); the WSI’sPIIM_PIXEL_DATA_REPRESENTATION_SEQUENCEonePixelDataRepresentationper pyramid level (…_NUMBER,…_COLUMNS,…_ROWS, its ownDICOM_PIXEL_SPACING). The whole tree (image data left out) isimages[0].extra.philips.metadata. - Page 0 is level 0; the reduced tiled pages smaller than it are the pyramid, in page order; pages
whose description starts with
LabelorMacroare attachments (info --view structure), as are other pages. - Level k is representation k: its downsample is its pixel spacing over level 0’s (snapped to an
integer within 0.1 %, the spacings having 6 significant digits), and the level’s size is level
0’s divided by it, rounded down — the TIFF pages are padded to whole tiles (level 4 of
Philips-1is stored 3072 × 2560 for a 2816 × 2240 image). The level is the page’s top-left. These sizes equal OpenSlide’s and tifffile’s. - Pixel size: the WSI’s
DICOM_PIXEL_SPACING(pixel_spacing_um: millimetres, row then column → µm, x from the column spacing), as OpenSlide reports it. InPhilips-1(converted from an NDPI) it is 0.226907 µm while level 0’s representation spacing and the resolution tags hold a rounded 0.227273 (tifffile’s value): the corpus oracle takes OpenSlide’s. - Tiles outside the scanned regions have offset and byte count 0: they are returned white
(
PageLayout.sparse_fill= 255), as the scanner renders them in its own downsampled levels (and as OpenSlide’s transparent tiles are over a white background); other TIFFs keep zeros. DICOM_MANUFACTURER→ manufacturer,DICOM_MANUFACTURERS_MODEL_NAME→ model,DICOM_SOFTWARE_VERSIONS(first) → software version (all inextra.philips.software_versions),Softwaretag → software,DICOM_ACQUISITION_DATETIME(YYYYMMDDhhmmss.ffffff,dicom_datetime) →acquired_at;extra.philips:barcode(PIM_DP_UFS_BARCODE, base64,base64_decode),device_serial_number,interface_version,derivation,representations.- The XML’s
LABELIMAGE/MACROIMAGEPIM_DP_IMAGE_DATA(base64 JPEG) are the attachmentslabelandmacro(attachments,export --attachment).
Hamamatsu NDPI
Tag 65420 marks the file. 65421 = source-lens magnification (> 0 pyramid page, −1 macro, −2 map); 65422/65423 = X/Y offset of the image centre from the slide centre (nm); 65424 = focal-plane Z offset (nm); 65427 = slide label; 65442 = scanner serial. The largest pyramid page is the image, same-size pyramid pages are focal planes (Z), smaller ones pyramid levels. Physical size from X/YResolution (centimetres). Levels are single JPEG strips; files over 4 GiB (offset high bits in tag 65324) are not supported. The full-resolution page (too large to decode whole) is read by its JPEG restart intervals: tag 65426 lists the byte offset of every interval relative to the strip (65432: their high 32 bits), and the page is a grid of restart interval × MCU width by MCU height tiles (2048 × 8 in the corpus slide). A tile is decoded as a small JPEG: the page’s header up to the start of scan with the frame size set to one tile and the restart interval (DRI) removed (jpeg_frame), the interval’s bytes without their trailing RST marker, and EOI. Only baseline/sequential JPEG; a progressive full-resolution page is refused (exit 6).
PerkinElmer QPTIFF (Vectra / inForm)
Each page’s ImageDescription is an XML PerkinElmer-QPI-ImageDescription (QpiPage): ImageType (FullResolution, Thumbnail, ReducedResolution, Overview, Label), Name (channel name), Color (R,G,B), Objective, ExposureTime (unit not documented publicly; raw values under images[].extra.qpi_exposure_time_raw), SlideID, AcquisitionSoftware, InstrumentType, and (deep) Magnification, PixelSizeMicrons. Consecutive full-resolution pages of equal size are the channels; ReducedResolution pages are pyramid levels; the rest are attachments. Pixel size from PixelSizeMicrons, else X/YResolution.
Thermo Fisher EER (Falcon electron-event movies)
Falcon 4, 4i and C cameras write movies as EER files (.eer): a BigTIFF (little-endian) whose every page is one detector frame of the sensor size (4096 × 4096 in the corpus), stored as one strip with compression 65000, 65001 or 65002 and no BitsPerSample. A page whose compression is one of these marks the file (Flavor::Eer, dialect thermo-eer); pages of another geometry or compression are skipped with a note.
Event coding (EerCoding: skip_bits, horz_bits, vert_bits; read as imagecodecs’ imcd_eer_decode does, BSD-3). The strip is a bit stream read least significant bit first. Each code starts with a run length of skip_bits bits: the run’s pixels (in raster order) hold no event. A run of all ones (127 for 7 bits) continues into the next code without an event. Any other run is followed by horz_bits + vert_bits of sub-pixel position, and the event lies on the pixel the run reached; the next run starts on the pixel after it. The stream ends when a run reaches the last pixel or the bits run out; a run past the last pixel is corrupt. 65000 is 8 + 2 + 2 bits, 65001 is 7 + 2 + 2, 65002 reads the three sizes from tags 65007, 65008 and 65009 (7, 2, 2 when absent); other sizes (run lengths outside 2–16 bits, codes over 24 bits) are refused (exit 6).
Returned samples. Each frame is an uint8 plane of event counts on the sensor grid; a pixel holds at most one event per frame (the run after an event starts on the next pixel), so frames are 0/1 and a dose image is the sum over T. The sub-pixel bits (super-resolution positions) are not used, the gain reference is not applied (it is a separate file, below) and the TIFF Orientation (2 or 5 in the corpus) is reported (extra.eer.orientation, note), not applied — as tifffile does.
Metadata. Tag 65001 of page 0 is an XML block <metadata><item name=".." unit="..">value</item>…</metadata> (parse_items: numbers as numbers, Yes/No as booleans; extra.eer.acquisition, units in extra.eer.units). Used: sensorPixelSize.width/.height (unit m) → physical size X/Y (the nominal pixel size at the specimen, e.g. 7.06e-11 m; the 2022 writer omits it); exposureTime (s) / numberOfFrames → time_increment_s and the channel’s exposure_ms; timestamp → acquired_at (ISO 8601 with its offset, as stored); commercialName (cameraName) → detector. extra.eer also has compression, run_length_bits, subpixel_bits. Tag 65002 of each page is the frame’s block (frameID, dose in e/pixel, timestamp, rleCodeLength, nrOfSubPixelPerDirection, orientation, pixelFormat, decompressionAlgorithmVersion): info --view full lists it per frame (extra.frames); the 2022 writer has none.
Dose check. The frame dose is the frame’s event count divided by the pixel count, to 6 decimals, in every frame of the corpus files that record it: check decodes every frame (eer_event_counts) and reports eer_dose_mismatch when they differ by more than 1e-6, eer_bad_frame for a stream that does not decode, and the event total (eer_events, info).
Gain references (.gain) are ordinary TIFFs (4096 × 4096 float32, LZW) whose tag 65001 holds an EER metadata block after an XML declaration; they are read as plain TIFF and the block is copied to extra.eer.acquisition.
Molecular Dynamics GEL (Typhoon, Storm and FLA scanners)
.gel files of Molecular Dynamics, Amersham/GE/Cytiva Typhoon and FLA scanners are TIFF pages
with private tags (names as tifffile documents them): 33445 MDFileTag, 33446 MDScalePixel
(rational), 33447 MDColorTable, 33448 MDLabName, 33449 MDSampleInfo (key=value lines:
serial number, date and time, laser, filter, PMT voltage, pixel size, scan mode, software), 33450
MDPrepDate (YYYY:MM:DD), 33451 MDPrepTime, 33452 MDFileUnits (Counts). MDFileTag 2
means the samples are square roots of the counts: counts = value² × MDScalePixel; 128 means
linear samples: counts = value × MDScalePixel. When a plain TIFF (no other sub-format) carries
MDFileTag 2 or 128 on its first page and every image is single-sample 8- or 16-bit, every plane
is returned as the counts, float32 (the arithmetic in float32, as tifffile’s mdgel series does
it, so the planes agree bit for bit), with a note, images[].extra.md_gel (encoding, scale,
units, lab name, preparation date, the sample-info pairs, the stored pixel type) and the scan date
as acquired_at. Other MDFileTag values are reported and left as stored. A .gel re-saved by
ImageJ has lost these tags and is an ordinary TIFF. Image Lab .scn files imported from .gel
scans keep their square-root samples as stored (openreadout-gel).
Micro-Manager
Tag 51123 holds per-plane JSON. PixelSizeUm fills the pixel size when nothing else did; Channel and Exposure-ms fill a single channel’s name and exposure; Camera → detector; Time → acquired_at when absent. Selected keys are under images[0].extra.micromanager; the whole object is in info --view full. Micro-Manager stacks carry OME-XML, which determines the geometry.
Integrity checks (check)
Codes (EER: eer_bad_frame, eer_dose_mismatch, eer_events, § EER): truncated (IFD, field value, strip/tile or contiguous stack past end of file), bad_ifd_offset, ifd_cycle, too_many_ifds, bad_field, bad_page, missing_chunks (fewer offsets/counts than the geometry needs), sparse_chunks (info), unsupported_samples / unsupported_compression (warnings; the structure is valid; unsupported_samples also covers JPEG colour codings that are refused, see “JPEG colour”), jpeg_colour_ambiguous (warning), bad_chunk (a JPEG page whose first chunk has no readable frame header), missing_planes (planes not mapped to any IFD), bad_tiffdata (IFD index beyond the referenced file), missing_file / unreadable_file (OME sibling or metadata file), uuid_mismatch (warning), short_acquisition (LSM with fewer pages than declared). NIS-Elements export files are opened like OME-TIFF members; a (position, c, z, t) without a file is missing_planes. Contiguous stacks (ImageJ virtual stacks, STK files, STK members of a .nd series) must end inside their file (truncated); .nd members are opened like OME-TIFF members (missing_file, unreadable_file). Any error → exit 4.
Oracle mapping (corpus harness)
oracle/gen.py uses tifffile series as ground truth (STK files included: tifffile’s stk series, planes on the Z axis). Series that are our images: all series for OME, Micro-Manager, generic/uniform/shaped files; series 0 only for ImageJ, LSM, SVS, NDPI and QPI (the rest are thumbnails/labels/macros/levels). Axes: Y, X the plane; S last or C after X → interleaved samples; C → channel; S before Y/X (planar samples) → channel, c = c_C · S + s; Z, I, Q → z; T → t; P, M → separate images; any other axis is an oracle error. Physical sizes come from the scales tifffile’s format code sets (coord_scales with µm-convertible units); axes without one are not compared. BinaryOnly files follow their metadata file; companion files are resolved per the OME-TIFF specification with tifffile reading each page.
Vocabulary (every public identifier in openreadout-tiff must appear here)
| identifier | meaning |
|---|---|
TiffReader, TiffDataset, FORMAT_ID, EXTENSIONS, open, path |
reader entry points |
Flavor { OmeTiff, OmeCompanion, Lsm, Svs, Ndpi, Qptiff, ImageJ, MicroManager, LeicaScn, VentanaBif, PhilipsTiff, Eer, Plain }, id |
detected sub-format |
PhilipsObject { object_type, attributes, arrays }, attr, array, to_json, parse_philips, scanned_image, string_array, pixel_spacing_um, dicom_datetime, base64_decode, sparse_fill |
Philips TIFF (§ Philips TIFF) |
BifPage { Level, Label, Probability, Thumbnail, Other }, level, magnification, page_role, iscan_attributes, BifStitching, stitching, rows, cols, tile_width, tile_height, joints, overlapping_joints, max_overlap, directions, XMP |
Ventana BIF (§ Ventana BIF) |
ScnDocument, ScnImage, ScnDimension, ScnChannel, SCN_NAMESPACE, parse_scn, pixel_size_um, covers_collection, filter_centre_nm, filter_band_nm, collection_name, collection_size_nm, barcode, creation_date, device_model, device_version, dimensions, view_size_nm, view_offset_nm, spacing_z, numerical_aperture, illumination, r, c, z, rgb, excitation_filter, suppression_filter, dichroic, exposure_raw, ccd_gain |
Leica SCN description (§ Leica SCN; uuid, images, name, size_x, size_y, ifd, index, objective, channels as elsewhere) |
EerCoding, skip_bits, horz_bits, vert_bits, event_bits, check, of, is_eer_compression, walk, decode_counts, parse_items, has_metadata, eer, ACQUISITION_METADATA, FRAME_METADATA, SKIP_BITS, HORZ_SUB_BITS, VERT_SUB_BITS |
Thermo Fisher EER (§ EER); eer is the page’s coding in PageLayout |
TiffFile, TiffHeader, ByteOrder { Little, Big }, byte_order, big_tiff, first_ifd_offset, header, ifds, file_len, problems, TIFF_MAGIC, BIGTIFF_MAGIC, looks_like_tiff |
container |
Ifd, offset, fields, next_offset, field, uint, uints, float, text, bytes, read_ifd |
image file directory and typed accessors |
Field, tag, field_type, count, value_offset, value, FieldValue { Unsigned, Signed, Float, Ascii, Bytes }, type_size |
IFD entries |
StructureProblem, code, detail |
problems recorded during the walk |
ByteSource, len, order, big, read_at, u16_of, u32_of, u64_of, from_bytes |
bounds-checked random access with the file’s byte order; from_bytes (on ByteSource and TiffFile) reads a TIFF held in memory, e.g. a stream of an Olympus OIB compound file; open_in (on ByteSource and TiffFile) opens a path in a byte-source namespace (openreadout_core::source::Fs), e.g. a member of an OIF folder dropped into a browser |
FileSetMember, name, uuid, metadata_only |
members of a multi-file OME-TIFF set |
SampleCoding (Native, Packed with bits, signed, Float16, Float24, ComplexInt), coding, fill_order |
how stored samples become returned samples (Sample formats) |
PageLayout, from_ifd, width, height, bits_per_sample, sample_format, samples_per_pixel, planar, compression, photometric, predictor, tiled, chunk_width, chunk_height, offsets, byte_counts, jpeg_tables, pixel_type, chunks_across, chunks_down, chunks_per_plane, expected_chunks, is_rgb, compression_name |
page geometry and sample layout |
SampleSelect { All, One }, read_page, MAX_PLANE_BYTES, is_decoded |
plane decoding; is_decoded tells whether chunks of a compression code are decoded |
read_region, ChunkCache, jpeg_frame, ndpi_mcu_starts |
region decoding: the chunks a rectangle overlaps, decoded-chunk cache, NDPI per-interval JPEG header and restart-interval offsets |
NdpiHeader, header, tile_width, tile_height, MCU_STARTS, MCU_STARTS_HIGH, parse_header, tiled_layout, frame |
NDPI full-resolution page read by restart intervals |
JpegDecision, color, conflict, jpeg_color, jpeg_page_supported |
JPEG colour choice per chunk (§ JPEG colour) |
SampleSelect { All, One }, read_page, MAX_PLANE_BYTES |
plane decoding |
OmeDocument, parse, parse_ome_xml, schema, creator, binary_only, images, instruments, annotations |
parsed OME-XML |
OmeBinaryOnly, metadata_file |
BinaryOnly reference |
OmeImage, description, acquisition_date, instrument_ref, objective_ref, annotation_refs, pixels |
Image |
OmePixels, dimension_order, size_x, size_y, size_z, size_c, size_t, physical_size_x, physical_size_y, physical_size_z, time_increment, significant_bits, big_endian, interleaved, channels, tiff_data, planes, effective_size_c, plane_axes, plane_at, linear_of, plane_count |
Pixels and plane rasterisation |
OmeChannel, fluor, excitation_wavelength, emission_wavelength, color, acquisition_mode, illumination_type, contrast_method, pinhole_size, emission_filter_refs, filter_set_ref |
Channel (its LightPath emission filters, FilterSetRef; annotation_refs as for Image) |
OmeFilter, cut_in_nm, cut_out_nm, OmeFilterSet, filters, filter_sets |
Instrument/Filter (TransmittanceRange) and FilterSet |
NORMALIZED_NS, normalized_value, instrument_of, channel_mode, channel_band, acquired_at, objective_and_instrument |
our export’s normalized annotations and the OmeDocument → model helpers shared with the OME-Zarr reader |
OmeTiffData, ifd, first_c, first_z, first_t, file_name |
TiffData and its UUID |
OmePlane, the_c, the_z, the_t, delta_t, exposure_time, position_x, position_y, position_z |
Plane |
OmeInstrument, microscope_manufacturer, microscope_model, objectives, detectors, OmeObjective, manufacturer, model, nominal_magnification, lens_na, immersion, OmeDetector, detector_type |
Instrument |
OmeAnnotation, kind, namespace |
StructuredAnnotations entries |
length_to_um, time_to_s, unit_to_um |
unit conversion |
ImageJInfo, keys, channels, slices, frames, hyperstack, unit, spacing, finterval, mode, parse_description, page_of, parse_binary |
ImageJ description and binary metadata |
LsmInfo, LSM_INFO_MAGICS, magic, record_size, dim_x, dim_y, dim_z, dim_channels, dim_time, data_type, voxel_size_x_m, voxel_size_y_m, voxel_size_z_m, scan_type, spectral_scan, time_interval_s, dim_positions, dim_tiles, channel_names, channel_colors, time_stamps, channel_wavelengths_m, stack_axes |
Zeiss LSM record |
AperioDescription, parse_aperio, number, acquired_at |
Aperio description (header, keys, text as above) |
QpiPage, parse_qpi, image_type, magnification, pixel_size_um, slide_id, acquisition_software, instrument_type |
PerkinElmer QPI page XML (name, color, objective, fields as above) |
MicroManagerPlane, parse_micromanager, json, channel, exposure_ms, camera, time |
Micro-Manager JSON (pixel_size_um as above) |
id, objective |
image id / objective text fields of the parsed conventions |
MetamorphStk, MetaSeries, MetamorphNd |
the MetaMorph sub-formats (Flavor) |
StkInfo, STK_TAG_SETTINGS, STK_TAG_PLANES, STK_TAG_WAVELENGTHS, STK_TAG_PLANE_ARRAYS, is_stk, parse_stk, plane_count, z_distance, created, wavelengths_nm, calibrated, x_calibration, y_calibration, calibration_units, stage_x, stage_y, stage_labels, absolute_z, plane_texts, exposure_ms, pixel_size_um, z_step_um, plane_axis, to_json |
MetaMorph STK (above) |
metamorph_time, metamorph_seconds, unit_um, magnification, metaseries_time |
MetaMorph day numbers, calibration units, objective magnification, MetaSeries time text |
MetaSeriesPlane, parse_metaseries, props, image_name, acquired_local, illumination, objective_na, stage_um, wavelength_nm, application, application_version |
MetaSeries description (above) |
NdFile, parse_nd, version, start_time, time_points, timelapse, stages, waves, wave_do_z, wave_in_file_name, z_steps, z_step_um, image_count, channel_count, file_stem |
MetaMorph .nd series (above; keys, description, name as elsewhere) |
NisElements, NisPage, parse_nis, NIS_TAG_TIME, NIS_TAG_PIXEL_SIZE, NIS_TAG_STAGE, NIS_TAG_METADATA, time_ms, unknown |
NIS-Elements TIFF export tags (pixel_size_um, stage_um as above) |
SequenceName, SequenceAxis { Position, Time, Z, Channel }, parse_sequence_name, prefix, indices, extension, index, axes |
NIS-Elements export file names |
MdGel, file_tag, scale, stored, units, meta, prepared_at, sample_info, linearize, info, MD_FILE_TAG, MD_SCALE_PIXEL, MD_LAB_NAME, MD_SAMPLE_INFO, MD_PREP_DATE, MD_PREP_TIME, MD_FILE_UNITS, md_gel, apply_md_gel |
Molecular Dynamics GEL (§ Molecular Dynamics GEL) |
Performance / robustness merge (2026-09-23)
OME-XML is parsed once at open; companion XML keeps its 256 MiB capped read. LZW strips use bounded output and a reusable 64 KiB per-thread staging buffer.
How this reader was derived, file by file: provenance log.