OMNIC SRS file format
Note
This is an independently reverse-engineered, unofficial description of the
OMNIC SRS format. See OMNIC file formats for provenance,
limitations, and certainty-level definitions. Contributors with access to
other OMNIC .srs variants are encouraged to report differences so that
this reference can be improved.
Tested files and evidence
The observations below are based on the public SpectroChemPy test fixtures and on one independently controlled series (see the note at the end of this section).
File |
Acquisition family |
Content |
Format structures exercised |
|---|---|---|---|
|
RapidScan |
interferograms (643 × 4160) |
repeated-record layout; interferogram (data-points) axis; inter-spectrum trailer |
|
RapidScan (reprocessed) |
spectral (643 × 3734) |
repeated-record layout; spectral axis; pristine/reprocessed flag |
|
HighSpeed |
spectral (897 × 13898) |
repeated-record layout; spectral axis; 4-occurrence detection signature |
|
TG/GC |
spectral (788 × 1738) |
repeated-record layout; background record; spectral axis |
|
TGA |
spectral (485 × 3630) |
repeated-record layout; background record; spectral axis |
|
TG/GC |
spectral (335 × 1868) |
repeated-record layout; spectral axis |
[ESTABLISHED] The repeated-record layout 84 + nx*4 + 16 bytes
reproduces the data arrays exactly in independent binary reconstruction for
all of the files above and all four acquisition families, covering both
spectra and rapid-scan interferograms (see Repeated spectrum records).
In addition, one independently controlled TG/GC series was used as a
physical validation oracle. Individual spectra of that series were exported by
OMNIC as SPA files and compared against the raw SRS samples: the match is
correlation = 1.000000 and RMSE = 0.000000 in the correct physical
order (see Spectral sample order). The same series also exercised
the trailer’s SeriesProfile, Gram-Schmidt, and Area structures. The source
files of that series are not distributed with SpectroChemPy; only the
public fixtures listed above are.
Overall file organization
OMNIC SRS layout (regions currently understood)
===================================================
+------------------+ file header / key-table region:
| file header / | file magic, file-level flags, key table
| key-table region | (first 304 bytes + entries)
+------------------+ ^
| series metadata /|------------------+-- series-header base at
| header region | key[0].ref_pos (152 bytes before
+------------------+ the first detection signature)
| background header | its own X endpoints
| / background data |
+------------------+
| repeated spectrum records:
| [84-byte prefix | nx * 4-byte float32 payload | 16-byte trailer]
| ... x ny
+------------------+
| post-series profile region (observed in the controlled series):
| Gram-Schmidt-related data
| copied series metadata
| SeriesProfile block(s), one per profile
+------------------+
The diagram is logical, not to scale: it shows only regions and boundaries whose relationships are reproducibly observed. The metadata associated with the series extend well beyond the 152 bytes above the first signature, and the internal subdivision of that region — like that of the trailer blocks — is only partially understood. Some boundary positions and the number/layout of the trailer blocks are version-dependent (see SeriesProfile structures).
Offset conventions
File-header / key-table offsets are file-relative (relative to the start of the file).
Series-header offsets are relative to the located series-header base (at
key[0].ref_pos, i.e. the position of the first detection signature minus 152; see Detection signatures and positioning observations).Repeated-record offsets use the record start as origin; trailer offsets use the trailer start; SeriesProfile fields use the block start as origin.
Header and key structures
File header / key-table region (file-relative)
Offset |
Size |
Type |
Meaning |
Certainty |
|---|---|---|---|---|
0 |
18 |
ASCII |
File magic |
|
18 |
274 |
bytes |
Reserved / file metadata region. |
|
292 |
1 |
UInt8 |
Level-of-processing flag: |
|
294 |
2 |
UInt16 |
Number of key-table entries (28–29 observed). |
|
304 |
n × 16 |
KeyTable[] |
Key table (see below). |
|
Key-table entry (16 bytes; entry-relative offsets)
Offset |
Size |
Type |
Meaning |
Certainty |
|---|---|---|---|---|
0 |
2 |
UInt16 |
Key type / role indicator ( |
|
4 |
4 |
UInt32 |
Often 0 in the observed files; role unclear. |
|
8 |
4 |
UInt32 |
|
|
12 |
4 |
UInt32 |
Referenced section size in bytes. |
|
Series metadata
In the files examined, the series-header base is located 152 bytes before the first detection signature (see Detection signatures and positioning observations). Offsets in the tables below are relative to that base. The metadata associated with the series extend well beyond the first 152 bytes: the exact subdivision into OMNIC internal blocks is only partially understood. Only the fields listed below have been interpreted so far; the rest of the region is unmapped.
Offset |
Size |
Type |
Meaning |
Certainty |
|---|---|---|---|---|
0 |
1 |
UInt8 |
Header type marker ( |
|
4 |
4 |
UInt32 |
|
|
8 |
1 |
UInt8 |
X-unit code: 1 = cm⁻¹, 2 = data points (interferogram), 3 = nm, 4 = µm, 32 = Raman shift (cm⁻¹). |
|
12 |
1 |
UInt8 |
Y/data-unit code: 0x11 absorbance, 0x10 transmittance (%), 0x0F single beam, 0x16 detector signal (V), 0x1A photoacoustic, 0x1F Raman intensity, among others shared with the SPA/SPG readers. |
|
16 |
4 |
Float32 |
Raw |
|
20 |
4 |
Float32 |
Raw |
|
24 |
4 |
Float32 |
Per-file varying, registration-like factor; see Unknown fields. |
|
28 |
4 |
UInt32 |
Scan point count. |
|
32 |
4 |
UInt32 |
ZPD (zero-path-difference) position. |
|
36 |
4 |
UInt32 |
Number of scans. |
|
52 |
4 |
UInt32 |
Number of background scans. |
|
56 |
4 |
Float32 |
1.0 in non-RapidScan, 0.0 in RapidScan files examined; see Unknown fields. |
|
68 |
4 |
UInt32 |
General-header collection length in 1/100 s. This is the shared acquisition-time field of the OMNIC header family; it must not be conflated with the series minimum/first time at +1002. |
|
80 |
4 |
Float32 |
Reference (laser) frequency. |
|
84 |
4 |
Float32 |
1.0 in TG/GC and HighSpeed, 2.0 in RapidScan files examined; see Unknown fields. |
|
184 |
4 |
Float32 |
Same value pattern as +84. |
|
188 |
4 |
Float32 |
Optical velocity. |
|
208 |
var |
text |
Spectrum history text; a record whose text starts with |
|
296 |
8 |
UInt64 |
Raw file size. |
|
938 |
≤ 256 |
text |
Series name (the reader splits on the first newline). |
|
1002 |
4 |
Float32 |
Series minimum / first time, in minutes — the time-axis anchor. This field was historically misinterpreted as a “collection length”; the general-header collection-length field is the one at +68. Some implementations expose it as a collection duration in seconds (value × 60) for backward compatibility. |
|
1006 |
4 |
Float32 |
Series maximum / last time, in minutes. |
|
1010 |
4 |
Float32 |
Regular time step, in minutes (historically misnamed |
|
1026 |
4 |
UInt32 |
|
|
1030 |
1 |
UInt8 |
Y-unit code; a value of 1 may mean minutes. |
|
1044 |
2 |
UInt16 |
Gram-Schmidt offset (10 in the tested family); see Gram-Schmidt data. |
|
1046 |
2 |
UInt16 |
Gram-Schmidt interferogram points (100 in the tested family); see Gram-Schmidt data. |
|
1048 |
2 |
UInt16 |
200 in the tested files; see Unknown fields. |
|
1200 |
var |
text |
Initial history text (pristine files). Reprocessed files carry their
updated history at the end of the file after a 16-byte |
|
Background data
[OBSERVED] Spectral backgrounds have their own header, located through the
second detection signature (signature position − 152), with their own X
endpoints. In the tested background records where both headers were decoded,
the endpoint ordering can differ from that of the series header: spectral
background headers store raw firstx < lastx (ascending) while the
corresponding spectral series headers store raw firstx > lastx
(descending). No single raw-order rule has been observed to apply to the
whole file; any normalization must be applied per record.
The present reader normalizes spectral backgrounds per record to the same
public descending-wavenumber grid as the series. Only a subset of background
layouts is currently decoded (the reader’s return_bg path returns no data
for some background record shapes).
Repeated spectrum records
[ESTABLISHED] In the tested SRS series, each spectrum record has the
following layout. The same repeated-record structure is also observed for the
rapid-scan interferograms in rapid_scan.srs, so the description below is
generic to both spectral series and interferogram records:
data record
├── 84-byte prefix
├── nx × 4-byte float32 intensity payload
└── 16-byte trailer
[ESTABLISHED]The spectrum name is null-terminated inside the 84-byte prefix; the prefix also carries binary metadata (per-file mostly constant fields and a per-spectrum minimum-Y value).[ESTABLISHED]The payload boundaries reproduce the data arrays exactly in independent binary reconstruction (stride84 + nx·4 + 16bytes per record).[ESTABLISHED]The trailer size (16 bytes) holds for all tested files.
Offset |
Size |
Type |
Meaning |
Certainty |
|---|---|---|---|---|
0 .. null |
var |
ASCII |
Spectrum name, null-terminated (e.g. |
|
22 .. 75 |
var |
bytes |
Mostly per-file constant metadata; semantics not fully interpreted. |
|
76 |
4 |
Float32 |
Spectrum minimum Y value. |
|
Inter-spectrum trailer (16 bytes; trailer-relative offsets)
[ESTABLISHED] Tested records (both spectral series and rapid-scan
interferograms) are followed by a 16-byte trailer.
[OBSERVED] One uint32 field in this trailer behaves as a cumulative
time counter in centiseconds (values increase with spectrum index and track
the series time axis).
[UNKNOWN] The remaining trailer fields are not fully interpreted.
Offset |
Size |
Type |
Meaning |
Certainty |
|---|---|---|---|---|
0 |
4 |
UInt32 |
|
|
4 |
4 |
UInt32 |
Cumulative elapsed-time counter in centiseconds for the next spectrum (see Time representation). |
|
8 |
8 |
UInt64 |
Zeros in the tested files. |
|
Time representation
Four quantities describe the series time axis; they must not be conflated:
Series minimum / first time — header +1002 (Float32, minutes). This is the time of the first spectrum and the anchor of the axis.
Last / max time — header +1006 (Float32, minutes).
Regular time step — header +1010 (Float32, minutes). In the independently controlled series this equals
(last time − first time) / (ny − 1)exactly to float32 precision, which confirms it is a step, not a minimum (the historicalfirstymisreading).Number of spectra —
ny(header +1026).
[OBSERVED] the time axis follows the regular model
T[i] = time_min + i * step (i = 0 .. ny-1)
where time_min reads from +1002 and step from +1010.
The trailer’s centisecond counter provides an independent confirmation:
[OBSERVED] the counter stored in the trailer of spectrum i holds the
elapsed time of spectrum i+1, quantized to integer centiseconds; dividing by
6000 (centiseconds per minute) reproduces the time axis and matches the
3-decimal times embedded in the spectrum names. In the RapidScan test file the
per-spectrum increment equals the collection period; in the independently
controlled series the increment is the regular time step above.
Spectral sample order
[OBSERVED]
In the SRS files examined, spectral intensity samples are stored in ascending-wavenumber physical order (sample 0 = lowest wavenumber). This ordering was independently confirmed for one controlled series against individual spectra exported by OMNIC as SPA files. It should not yet be treated as a guaranteed invariant for every SRS producer/version.
The independent comparison established:
correlation = 1.000000
RMSE = 0.000000
for matching raw SRS samples against the OMNIC-exported SPA data in the correct physical order (the reversed orientation does not match).
This page describes the raw storage order. Presentational conventions of particular software are separate: SpectroChemPy, for instance, presents SRS spectral datasets with a descending wavenumber axis (matching its SPA convention) while the raw file order is ascending (see SpectroChemPy implementation references).
Rapid-scan interferograms
rapid_scan.srs contains rapid-scan interferograms. [OBSERVED] These
interferogram records carry an X-axis of type “data points” (x-unit code 2,
no physical wavenumber unit), stored with ascending data-points coordinates.
Do not equate “no xunit code” with “interferogram”: a record whose X-unit code is unrecognized also lacks a physical xunit, yet is not a data-points interferogram. The current reader therefore distinguishes three cases:
a known spectral axis (x-unit codes 1/3/4/32);
an explicit data-points interferogram (x-unit code 2);
an unknown X-axis type (unrecognized code), which is neither treated as an interferogram nor spectral-normalized.
This three-way distinction is presented as the current observed evidence for the files examined, not as a universal OMNIC invariant.
SeriesProfile structures
[HYPOTHESIS] A recurring trailer structure interpreted as an OMNIC
SeriesProfile block has been identified in the tested files. The block
boundaries and value shapes are reproducible; their full meaning is not.
Offset |
Size |
Type |
Meaning |
Certainty |
|---|---|---|---|---|
0 |
4 |
UInt32 |
|
|
4 |
4 |
UInt32 |
Profile-type code: 6 for “peak area of one peak” (Area), 0 for Chemigram, in the observed files. |
|
8 |
4 |
UInt32 |
Gram-Schmidt offset, copied unchanged from the series metadata into every block examined. |
|
12 |
4 |
Float32 |
Series minimum time, copied into every block examined. |
|
16 |
4 |
Float32 |
Series maximum time, copied into every block examined. |
|
20 |
~26 |
ASCII |
Human-readable profile label, e.g. |
|
after header |
var |
padding |
Header padding to a fixed block-relative data offset in the tested version. |
|
data offset |
ny × 4 |
Float32[] |
Per-spectrum profile values vector. |
|
[OBSERVED] In the tested files the block boundaries, the per-spectrum
vector shape, and the correspondence between the ASCII label and the vector
are reproducible, and a series defines one block per profile. The exact byte
stride and header/data grouping are version-dependent: a fixed stride
observed in one OMNIC-written file was absent in a file re-saved by a
different OMNIC version (headers packed together, data vectors grouped
separately). A parser must therefore discover the profile blocks dynamically
(e.g. via the ny markers and the ASCII labels) rather than assume a fixed
count or stride.
Gram-Schmidt / Chemigram / Area structures
Gram-Schmidt data
[OBSERVED] In the independently controlled series examined, the
post-series trailer/profile region begins with a Gram-Schmidt-related region:
a small header of two uint32 values (ny and the number of Gram-Schmidt
points) followed by a matrix of ny × gs_points float32 values (5331 × 100
in that series). Whether every SRS family places this region first in the
trailer has not been verified.
[OBSERVED] The number of Gram-Schmidt interferogram points also appears
as a series-header field at +1046 (UInt16 = 100 in the tested family), and
the Gram-Schmidt offset value (header +1044, UInt16 = 10) is copied into every
SeriesProfile block examined. [UNKNOWN] The exact meaning of the offset and the
reconstruction role of the matrix are not yet established.
Chemigram / Area observations (experimental)
This subsection records reverse-engineering evidence, not a formal binary definition.
[OBSERVED] Profile vectors whose areas were recomputed from the spectral
data reproduce the stored vectors: one region matched exactly, the others
within a ≤ 0.6 % affine residual (linear correlation ~1.00000).
[HYPOTHESIS] The stored “Area” profile values follow a
baseline-subtracted integral of the intensity over the labelled region
(Area = ∫(I − baseline) dx using the ascending-wavenumber raw order, with
the baseline joining the region-endpoint intensities). The small residual is
consistent with a slight difference in the boundary-point selection at the
integration limits. No numeric copy of the integration limits has been
identified in the structures examined; in the tested blocks, the limits are
present in the ASCII label.
Detection signatures and positioning observations
[OBSERVED] In the files examined, each acquisition family is preceded by a
recognizable 10–16 byte signature. The tested files show:
RapidScan —
\x02\x00\x00\x00\x18\x00\x00\x00\x00\x00\x48\x43\x00\x50\x43\x47, occurring 3 times;HighSpeed —
\x02\x00\x00\x00\x18\x00\x00\x00\x00\x00\x48\x43\x00\xc8\xaf\x47, occurring 4 times;TG/GC —
\x02\x00\x00\x00\x18\x00\x00\x00\x00\x00(first 10 bytes), occurring 3 times; the following bytes vary between files.
Positioning relationships (all values verified in the tested corpus):
the first signature occurs 152 bytes after the series-header base (
key[0].ref_pospoints at the same header —[ESTABLISHED]forkey[0]across the six public files;[OBSERVED]as a general rule);the second signature occurs 152 bytes before the background header;
the last signature occurs 60 bytes before the series spectral-data start (data = signature position + 60);
in HighSpeed files, the third occurrence’s role is not understood and the fourth is the data-position one.
Careful wording is intentional here: these are observed positioning relationships, not guaranteed layout invariants. The exact significance of the 60-byte offset and the exact roles of the third/fourth signatures remain only partially understood, and the signatures themselves could differ in other OMNIC versions.
Unknown fields
The following fields are published for usefulness to future reverse-engineers even though their meaning is not established. They are listed as correlated observations only; no unsupported semantic is assigned.
Location |
Size |
Type |
Observed pattern |
Certainty |
|---|---|---|---|---|
Series header +24 |
4 |
Float32 |
Nonzero, per-file varying (≈ 0.001–0.023) in non-RapidScan files, 0.0 in the RapidScan files examined; registration-like. |
|
Series header +56 |
4 |
Float32 |
1.0 in non-RapidScan, 0.0 in RapidScan files examined. |
|
Series header +84 |
4 |
Float32 |
1.0 in TG/GC and HighSpeed, 2.0 in RapidScan files examined. |
|
Series header +184 |
4 |
Float32 |
Same pattern as +84. |
|
Series header +1048 |
2 |
UInt16 |
200 in the tested files. |
|
Key-table entry +4 |
4 |
UInt32 |
Often 0. |
|
Record prefix +22..75 |
var |
bytes |
Mostly per-file constant metadata. |
|
Trailer, all fields except the centisecond counter |
12 |
mixed |
|
|
Confound warning: in the current corpus, RapidScan is the only family
observed with a distinct value pattern for the fields below; the HighSpeed and
TG/GC samples (TGA_demo.srs shares the TG/GC structure) all share the
same pattern. These fields therefore “correlate” with the acquisition family
in this sample without being independently confirmed as family markers. They
are more plausibly measurement-mode or axis-registration fields, but nothing
beyond the correlation is established.
Open questions
Is ascending-wavenumber raw storage universal for every SRS producer and OMNIC version? Only one independently controlled series has SPA ground truth so far.
What are the trailer fields other than the centisecond counter?
What is the complete SeriesProfile type-code enum (only Area = 6 and Chemigram = 0 observed)?
What is the meaning of the Gram-Schmidt offset (= 10) and what role does the Gram-Schmidt matrix play?
Which boundary-point selection does OMNIC use at the area integration limits?
What is the exact significance of the +60 data-position offset and of the third HighSpeed signature occurrence?
Do the detection signatures and the −152 / +60 positioning relationships generalize across OMNIC versions?
What are the key-table fields at +4 and +12?
What is the structure and role of the trailer’s copied “series metadata” region?
SpectroChemPy implementation references
SpectroChemPy implements the knowledge above in
src/spectrochempy/core/readers/read_omnic.py:
spectrochempy.read_srsis the public entry point for.srsfiles;the internal functions
_read_srs,_read_headerand_read_srs_spectraapply the record layout, header decoding, and normalization described in this page.
The public presentation conventions of the reader are distinct from the raw storage order documented here:
spectral records are presented with a descending wavenumber axis (matching
read_spa), with data matched to that axis;interferogram records keep the raw ascending data-points coordinate;
records with an unknown X-axis type are left in raw storage orientation with a warning;
the historical
reverse_xoption is deprecated and is a no-op.
The public regression tests in tests/test_core/test_readers/test_read_omnic.py
and the public test-data files listed in Tested files and evidence serve as reference evidence for the interpretations on
this page. The independently controlled validation series is not distributed.