Getting Started¶
Installation¶
Requirements
Python 3.12 or 3.13. Windows, macOS, and Linux are all supported. Bruker readers require the vendor SDK DLLs (bundled for Windows).
Your First Conversion¶
Command Line¶
# ImzML file
thyra input.imzML output.zarr
# Bruker .d folder
thyra data.d output.zarr
# Waters .raw folder
thyra data.raw output.zarr
# PHI SmartSoft-TOF ToF-SIMS .raw file
thyra tofsims_run.raw output.zarr
That's it -- Thyra auto-detects the format, reads the pixel size from metadata, resamples onto a common mass axis, and writes a SpatialData/Zarr directory.
See Supported Formats for the full list, how each one is detected, and what metadata each can supply.
Python API¶
You can pass explicit parameters when needed:
success = convert_msi(
"data/experiment.d",
"output/experiment.zarr",
dataset_id="hippocampus",
pixel_size_um=10.0,
)
Checking the result
After conversion, load the output and inspect:
import spatialdata as sd
sdata = sd.read_zarr("output/sample.zarr")
print(list(sdata.tables.keys()))
print(list(sdata.images.keys()))
Multi-Dataset Folders¶
When you point Thyra at a folder containing multiple .d datasets (e.g., a slide
with several tissue sections), it prompts you to choose:
$ thyra "slide_folder/" output.zarr
Found 3 datasets in slide_folder:
[1] C1501.d
[2] E2501.d
[3] E2506.d
Select dataset to convert: 3
-> E2506.d
Each .d dataset is matched to its own .mis file for correct optical alignment,
so the TIC overlay lands on the right tissue section in the shared optical image.
Converting a specific region
If a single dataset contains multiple acquisition regions (e.g., tissue +
matrix), you can select one with --region:
Resampling¶
Mass axis resampling is enabled by default. This maps all spectra onto a common m/z axis, which is required for most downstream analysis tools.
# Default -- resampling is on, method and binning auto-detected
thyra input.imzML output.zarr
# Disable resampling (keeps original m/z values per spectrum)
thyra input.imzML output.zarr --no-resample
For advanced control you can specify the method and instrument type:
# Physics-based resampling for Orbitrap data
thyra input.imzML output.zarr \
--resample-method nearest_neighbor \
--mass-axis-type orbitrap
nearest_neighbor is the right pairing for a non-uniform axis:
tic_preserving applies one scaling factor to the whole spectrum, which
cannot account for bin widths that vary across the mass range. See
Resampling for the measured cost of getting that
combination wrong.
When to disable resampling
Disable resampling (--no-resample) if you need the raw, unmodified spectra
-- for example, when doing your own peak picking or centroiding downstream.
Note that without resampling, each pixel may have a different m/z axis.
See Resampling for how the method, axis type, and bin count are chosen, and the CLI Reference for all options.
Optical Images¶
For Bruker data, Thyra automatically includes optical (microscopy) images and
aligns them to the MSI data using teaching point calibration from the .mis file.
# Optical images included by default
thyra data.d output.zarr
# Skip optical images
thyra data.d output.zarr --no-optical
The TIC image is stored with an affine transform that maps it into the optical image's coordinate space, so overlays work out of the box.
3D Data¶
By default, Thyra treats each z-slice as a separate 2D dataset with _z{i} suffixes:
# Default: separate 2D slices (msi_dataset_z0, msi_dataset_z1, ...)
thyra volume.imzML output.zarr
# Combined 3D volume (single table with x, y, z coordinates)
thyra volume.imzML output.zarr --handle-3d
Streaming Mode¶
Thyra automatically switches to streaming mode for large datasets (estimated >10 GB). You can also force it:
# Force streaming on
thyra large_dataset.d output.zarr --streaming true
# Force streaming off
thyra large_dataset.d output.zarr --streaming false
How streaming works
Streaming mode processes spectra in chunks and writes incrementally to Zarr on disk, keeping memory usage roughly constant regardless of dataset size. The output is identical -- only the processing strategy changes.
Troubleshooting¶
"WinError 5: Access is denied"¶
Windows has no atomic file replace, so Zarr's metadata writes have to delete the destination before renaming the new copy over it. If any handle is open on that file at that instant the rename fails outright instead of waiting.
Thyra retries these renames a few times, which clears the transient contention Zarr's own concurrent writes create. A failure that survives the retries means something is holding the store open for longer than that -- typically a Python session, napari, or a Jupyter notebook that loaded it.
Fix: Close any program that has the zarr open, or write to a different output path.
Windows: long output paths¶
Windows caps a normal path at 260 characters, and the limit applies to every
file inside the .zarr directory rather than to the path you typed. Thyra's
deepest metadata key sits roughly 95 characters below the output path, so an
output path over about 165 characters would once fail part-way through the
write with a confusing error naming a file you never asked for:
Error saving SpatialData: [Errno 2] No such file or directory:
'...\output.zarr\tables\msi_dataset_z0\uns\format_specific\imzml_version\zarr.<hash>.partial'
Thyra now detects this before writing and opens the store through an
extended-length (\\?\) path, which is exempt from the 260-character limit, so
long output paths convert normally. A log line records when this happens.
Reading a store at a long path
The store is written correctly, but other tools still face the same limit
when reading it. Either enable long path support system-wide
(HKLM\SYSTEM\CurrentControlSet\Control\FileSystem, LongPathsEnabled = 1;
needs administrator rights and a reboot), pass the \\?\ prefix yourself:
C:\msi\out.zarr.
Failed conversions exit non-zero and move the partial store aside
Any failed conversion exits with status 1, so a script or CI job wrapping
thyra sees the failure. The partially written store is renamed to
<output>.zarr.failed rather than left at the destination, which keeps it
available for diagnosis and leaves the output path free for a retry.
An imzML is refused before conversion starts¶
Thyra checks what the imzML declares against the .ibd on disk before it reads
a single spectrum, and refuses rather than converting something it cannot decode
correctly. A truncated .ibd is reported with the spectrum index and both byte
figures:
imzML spectrum 3 declares a m/z array ending at byte 236, but sample.ibd is 196
bytes (3 spectra are affected; the furthest ends at 356). The .ibd is truncated
or its offsets are wrong -- pyimzml would return empty arrays for these spectra
without raising, and they would simply be missing from the output.
The usual cause is an incomplete copy of the .ibd -- copy it again and
compare the byte count.
The other refusals are declarations Thyra cannot honour:
MS:1000574 zlib compressionon either binary array. The underlying parser has no decompression path, so it would read the compressed bytes and decode them as numbers.- A param group declaring no precision term, two at once, or one that disagrees with the precision the parser resolved. The decode width would be arbitrary.
64-bit integerarrays, whose width differs between Windows and Linux.32-bit integeris spec-legal and is not refused.- A first spectrum whose
IMS:1000104encoded byte length contradicts its value count at the declared precision -- the signature of an array that decodes at the wrong width but exactly the right length. - A negative offset or length, or a spectrum whose m/z and intensity arrays declare different numbers of values.
Re-export the file from the vendor software with 64-bit float m/z and no compression.
Not everything unusual is refused. Trailing bytes in the .ibd, offsets that do
not follow document order, and more than one <scanSettings> block are legal
and are logged as warnings.
Before this check these files converted "successfully": a truncated .ibd
produced a store containing only the pixels before the cut, with no error.
"No module named 'timsdata'" or Bruker SDK errors¶
The Bruker SDK DLLs are bundled for Windows. On Linux/macOS, Bruker data requires
the vendor's libtimsdata.so / libtimsdata.dylib to be installed separately.
Pixel size not detected¶
Thyra never prompts for one. If the source metadata declares no pixel size the
run fails immediately -- before the converter is built, so nothing is written
and there is no partial store to clean up -- logging Pixel size not found in
metadata followed by Use --pixel-size parameter (e.g., --pixel-size 25), and
exits 1.
Pass the value yourself and re-run:
Memory errors on large datasets¶
Force streaming mode:
Or reduce the number of resampling bins:
Verbose logging for debugging¶
What Next?¶
- Tutorial -- a full walkthrough on real and example data
- CLI Reference -- all command-line options
- Resampling -- how the mass axis is chosen, and how to control it
- Output Format -- understanding the zarr output structure
- API Reference -- Python API documentation