Olympus/Evident OIR
FluoView on Olympus/Evident FV3000 and FV4000 confocal systems saves each acquisition as an .oir file. OpenReadout returns its planes with their dimensions and the XML metadata. Derived from public files by hex dump, then cross-checked against the documentation and source of oirfile 2026.9.6 (BSD-3-Clause). Provenance: docs/provenance/oir.md.
OIR (“Olympus Image format Raw”) is written by FluoView (FV3000, FV4000 confocal systems). One .oir file holds one acquisition: up to T (time) × λ (lambda, spectral) × Z × channels planes of one XY size, a reference image, a bitmap thumbnail and XML metadata. Large acquisitions continue in files named <stem>_00001, <stem>_00002, … (no extension) next to <stem>.oir; they have the same binary layout and hold further pixel and frame-property blocks, while the .oir holds the metadata. Nothing is compressed.
Byte layout (all integers little-endian)
File header (OirHeader, 96 bytes)
| offset | size | field (our name) | value / meaning |
|---|---|---|---|
| 0 | 16 | MAGIC |
ASCII OLYMPUSRAWFORMAT |
| 16 | 4 × u32 | header_words |
12, 0, 1, 2 in every corpus file (meaning unknown) |
| 32 | u64 | declared_size |
file size as written. Stale bytes may follow it (zenodo13680725-stitch-a01-g001: 237 bytes of an older, longer index) — a check warning; a declared size beyond the end of the file means truncation |
| 40 | u64 | index_offset |
offset of the block index |
| 48 | u32 | block_count |
number of entries in the index |
| 52 | 3 × u32 | — | 0, 1, 0 |
| 64 | u64 | thumbnail_offset |
offset of the thumbnail block, or 0xFFFFFFFFFFFFFFFF |
| 72 | 8 | producer |
ASCII FLUOVIEW |
| 80 | u32, u32, u64 | — | 3, 2, 0xFFFFFFFFFFFFFFFF |
| 96 | FIRST_BLOCK_OFFSET |
first block |
Block index (at index_offset)
u32 INDEX_MARKER (0xFFFFFFFF), then block_count × u64 block offsets, in file order, up to declared_size. Blocks tile the file with no gaps from byte 96 to the index (checked in every corpus file). When the index is unusable (truncated file, bad marker) the reader recovers the blocks by walking the chain from byte 96 (payload_len gives each block’s end); check then reports bad_index.
Blocks (Block)
| offset | size | field | meaning |
|---|---|---|---|
| 0 | u32 | payload_len |
bytes after this 8-byte header |
| 4 | u32 | kind |
BlockKind below |
| 8 | payload_len |
payload |
| kind | BlockKind |
payload |
|---|---|---|
| 0 | Documents |
several XML documents (below); two per file: a snapshot written after the first frame and the complete set written at the end — we use the last one |
| 1 | FrameProperties |
one lsmframe:frameProperties document per frame (a frame = one t, λ, z position, all channels) |
| 2 | Thumbnail |
ASCII BMP then a Windows bitmap (e.g. 116×86, 24-bit): exposed as attachment thumbnail |
| 3 | ChunkTag |
u32 plane_offset, u32 chunk_len, u32 name_len, name — names the pixel block that follows |
| 4 | Pixels |
raw samples of one chunk of one plane |
| 5 | Separator |
empty; written after each frame |
The payload of kinds 0 and 1 starts with 36 bytes of words we do not interpret, then each document as u32 length + ASCII XML (the four bytes before every <?xml equal the document’s length). lut:LUT documents are additionally preceded by u32 36 + the ASCII uuid of the channel they belong to (documents, XmlDoc.channel).
Plane chunks (ChunkTag, ChunkName)
Every plane (one channel at one t, λ, z) is stored as one or more chunks, each a ChunkTag block followed immediately by a Pixels block of chunk_len bytes. plane_offset is the chunk’s byte offset inside its plane (e.g. 47 chunks of 11 264 bytes, the last 6 144, for a 512×512×2 plane). Chunks of different channels may interleave (A0, B0, A1, B1, …), so chunks are placed by plane_offset, not by file order; check verifies that a plane’s chunks cover it exactly.
Chunk names:
| form | example | meaning |
|---|---|---|
[t###][l###][z###]_<a>_<b>_<uuid>_<n> |
l002z001_0_1_93e4632f-…_17 |
1-based time (t), lambda (l), z (z) indices (absent axes are omitted), a pair of numbers that is _0_1 in every corpus file, the channel uuid, the chunk number |
REF_<source>_<uuid>_<n> |
REF_LSM0_d59928c9-…_1 |
reference image of that channel (source e.g. LSM0) |
Distinct index values are mapped to consecutive positions (oirfile does the same); check warns (sparse_axis) when they are not 1..n. Other axis letters are counted (unknown_axes, check warning unknown_axis) and not exposed.
Continuation files
For <dir>/<stem>.oir the reader opens <dir>/<stem>_00001, _00002, … until the first missing number. Each has its own header, blocks and index; their chunk tags add planes (in zenodo13680725-stitch-a01-g001: 60 of 106 planes in the .oir, 46 in _00001). A continuation file on its own is detected as OIR with a note to open the .oir. check compares the planes stored with the planes the acquisition declares (imageInfo axes, below) × channels and reports missing_planes, naming the continuation file that would come next when it is absent; info adds a note.
XML documents
Element names are matched by local name. The documents of the last documents block, by root element:
| root | our key in vendor |
what we normalize from it |
|---|---|---|
fileinfo:fileInfomation (sic) |
file_info |
version → format_version (e.g. 2.1.2.3) |
lsmimage:imageProperties |
image_properties |
ImageProperties: creation time, system name/version, microscope, channels, acquisition axes, pixel size, objective, laser lines |
annotation:annotationStore |
annotations |
— |
overlay:contents |
overlays |
— |
lut:LUT (one per channel) |
luts (with @channel) |
lut:name → channel color when it is a plain colour name |
lsmimage:lsmChannel (one per channel) |
channel_settings |
ChannelSettings: detector (deviceName), dye name and excitation/emission maxima |
base:imageDefinition |
image_definitions |
reference-image geometry |
event:eventList |
event_list |
— |
cameraimage:* |
camera_images |
reference-image sample width (elementChannel/depth); no corpus file |
lsmframe:frameProperties (frame blocks) |
frame_properties.first + count |
FrameRecord: plane geometry (FrameGeometry: width, height, depth bytes/sample, bitCounts, colorType) and axisValue positions |
vendor keeps every document as JSON with its own names; text values longer than 4096 characters (the 512 KiB hex tables in lut:LUT) are replaced by <N characters omitted>. Per-frame values are exposed by info --view full under images[0].extra.frames rather than repeating every frame document.
Normalized model
| field | from |
|---|---|
| images | image 0: the planes of the main chunks; image 1: the reference image (REF_ chunks) when present, extra.kind = "reference". A file without chunks (e.g. the overview map zenodo13680725-map-a01, whose frame properties declare a 45858×30169 canvas) has no images and a note |
size_x, size_y |
first frame’s imageDefinition width/height; size_y grows to the stored rows when a plane holds more bytes (line scans; no corpus file) |
pixel_type |
depth 1 → uint8, 2 → uint16, 4 → float (4 from oirfile only; no corpus file) |
size_z, size_t |
distinct z, t indices in chunk names |
size_c |
channels with pixels × lambda steps, channel-major: c = channel * n_lambda + lambda_index (the same rule as the LIF reader); extra.lambda has the wavelengths |
| channel order | channel elements with an id in imageProperties, first occurrence per id, stably sorted by @order (oirfile does the same); channels with pixels but no description follow, sorted by uuid |
channel name |
channel/name (CH1, …); lambda channels get "<name> <nm> nm" |
fluorophore |
lsmChannel/dyeName |
excitation_nm |
the channel’s laserDataIds → imagingMainLaser[@id] with @enable="true" → wavelength, when exactly one wavelength results |
emission_nm |
lambda channels: the lambda wavelength; otherwise the dye’s emissionWavelength |
emission_range_nm |
startWavelength/endWavelength under the channel (detection band), non-lambda channels |
color |
the channel’s LUT name when it is a plain colour |
physical_size.x/y |
first length/x, length/y in imageProperties, unit from the sibling pixelUnit/x (MICRO_METER, NANO_METER, …) |
physical_size.z |
ZSTACK axis step (absolute), else the spacing of the frames’ ZSTACK positions |
time_increment_s |
difference of the TIMELAPSE positions (ms) of frame 0 and frame n_lambda × n_z, else the TIMELAPSE axis step (s) |
| acquisition axes | imageProperties/imageInfo/axis (axis, startPosition, endPosition, step, maxSize); the first-frame snapshot has none, and then the acquisition-settings axis elements with @enable="true" (and not @paramEnable="false") |
objective |
objectiveLens: name, magnification, naValue, immersion (DRY, OIL, …) |
instrument |
manufacturer Olympus/Evident; model = systemName (FV4000); software = header producer (FLUOVIEW); version = systemVersion. extra.microscope = microscope stand name (IX83P2ZF) |
acquired_at |
imageProperties creationDateTime (ISO-8601 with offset, as written) |
| extras | significant_bits, color_type, channel_ids (uuids), channel_detectors, continuation_files |
| frames | one record per frame-properties block: index, name, t, lambda, z (0-based positions), time_s, z_um, lambda_nm, created |
Integrity checks (check)
Header (signature, recorded size vs actual: truncated / trailing_bytes, index usable: bad_index, block count: block_count_mismatch, thumbnail offset: bad_thumbnail_offset); block chain contiguous from byte 96 to the index (gap, overlapping_blocks, unknown_block, truncated, bad_block); each chunk tag followed by a pixel block of the declared length (orphan_chunk_tag, chunk_length_mismatch, unparsed_chunk_name); each plane covered exactly (incomplete_plane, overlapping_chunks, oversized_plane); planes declared vs stored (missing_planes, naming the missing continuation file); frame count (frame_count_mismatch); XML (bad_xml, missing_metadata, unknown_channel); continuation files that do not open (bad_continuation). Any error → exit 4.
Vocabulary (every public identifier in openreadout-oir must appear here)
| identifier | meaning |
|---|---|
OirReader, OirDataset, OirFile, FORMAT_ID, open |
reader, opened dataset, one opened container file (main or continuation) |
OirHeader, header_words, declared_size, index_offset, block_count, thumbnail_offset, producer |
file header fields |
MAGIC, FIRST_BLOCK_OFFSET, INDEX_MARKER, looks_like_oir |
signature, first block offset, index marker, signature test |
Block, offset, kind, payload_len, payload_offset, end, payload, pixels_after |
a block, where its payload is, the pixel block after a chunk tag |
BlockKind { Documents, FrameProperties, Thumbnail, ChunkTag, Pixels, Separator, Unknown }, from_code, code, name |
block kinds |
ChunkTag, plane_offset, chunk_len, chunk_tags |
a chunk tag (where the next pixel block goes in its plane) |
ChunkName, parse, t, lambda, z, unknown_axes, channel, reference, chunk |
parts of a chunk name |
XmlDoc, root, text, documents |
a length-prefixed XML document and the extractor |
path, file_len, header, blocks, index_problem, truncated_at, bad_block_at |
opened-file state and what went wrong while indexing |
FrameGeometry, width, height, depth, bit_count, color_type |
plane geometry |
FrameRecord, created, geometry, positions |
one frame’s properties |
AxisDesc, axis, start, step, max_size |
an acquisition axis |
ChannelDesc, id, order, band_start_nm, band_end_nm, laser_ids |
a channel in the image properties |
LaserLine, wavelength_nm, enabled |
a laser line |
ObjectiveDesc, magnification, numerical_aperture, immersion, working_distance_mm |
the objective |
ImageProperties, system_name, system_version, microscope, channels, axes, pixel_length_x, pixel_length_y, pixel_unit, objective, lasers |
what we take from imageProperties |
ChannelSettings, detector, dye_name, dye_excitation_nm, dye_emission_nm |
what we take from lsmChannel |
image_properties, channel_settings, frame_record, geometry, lut_name, lut_color, unit_to_um |
XML readers: image properties, channel settings, frame properties, plane geometry, LUT name, LUT colour, pixel-unit factor to µm |
read_at, read_into |
internal windowed file reads (block headers; pixel chunks) |
How this reader was derived, file by file: provenance log.