Skip to content

API Reference

Thyra's Python API centres on two functions: convert_msi, which does the work, and preview_msi, which tells you what an input is without converting it. For most use cases those are all you need. The remaining sections document configuration types, metadata objects, and base classes for advanced users who want to inspect results or extend Thyra with new formats.


Converting Data

The primary entry point. Detects the input format, reads metadata, and writes a SpatialData/Zarr directory.

Basic usage

from thyra import convert_msi

# Minimal -- auto-detects format, pixel size, and streaming
success = convert_msi("input.imzML", "output.zarr")

# With explicit parameters
success = convert_msi(
    "data/experiment.d",
    "output/experiment.zarr",
    dataset_id="hippocampus",
    pixel_size_um=10.0,
)

The Python API does not resample by default

resampling_config defaults to None, and nothing builds one for you, so the call above keeps the original mass axis. The thyra command-line tool is the opposite -- it resamples unless you pass --no-resample, because the default is applied in the CLI layer rather than in convert_msi. Pass a resampling_config explicitly to get the CLI's behaviour from Python.

With resampling configuration

success = convert_msi(
    "input.imzML",
    "output.zarr",
    resampling_config={
        "method": "nearest_neighbor",
        "axis_type": "orbitrap",
        "target_bins": 50000,
    },
)

Multi-region dataset (select one region)

region takes either a .mis Area Name or a DB RegionNumber. A string is matched against the area names first and only parsed as a number if no name matches, so the two forms below are not interchangeable -- area '03' need not be RegionNumber 3.

# By the name flexImaging gives the area
success = convert_msi(
    "data/slide.d",
    "output/tissue_only.zarr",
    region="03",
)

# By the database's own region number, which starts at 0
success = convert_msi(
    "data/slide.d",
    "output/tissue_only.zarr",
    region=0,
)

Thyra logs the RegionNumber-to-Area-Name mapping at INFO when it opens a multi-region dataset. See --region for the detail.

Force streaming for large datasets

success = convert_msi(
    "data/large_dataset.d",
    "output/large.zarr",
    streaming=True,
)

Full signature

convert_msi(input_path: Union[str, Path], output_path: Union[str, Path], format_type: str = 'spatialdata', dataset_id: str = 'msi_dataset', pixel_size_um: Optional[float] = None, handle_3d: bool = False, z_spacing_um: Optional[float] = None, resampling_config: Optional[Dict[str, Any]] = None, reader_options: Optional[Dict[str, Any]] = None, sparse_format: str = 'csc', include_optical: bool = True, apply_optical_alignment: bool = True, streaming: Union[bool, Literal['auto']] = 'auto', region: Optional[Union[int, str]] = None, **kwargs: Any) -> bool

Convert MSI data to the specified format.

Provides automatic pixel size detection from metadata or accepts user-specified values.

Parameters:

Name Type Description Default
input_path Union[str, Path]

Path to input MSI data file or directory

required
output_path Union[str, Path]

Path for output file

required
format_type str

Output format type (default: "spatialdata")

'spatialdata'
dataset_id str

Identifier for the dataset

'msi_dataset'
pixel_size_um Optional[float]

In-plane pixel size in micrometers (None for auto)

None
handle_3d bool

Whether to process as 3D data (default: False)

False
z_spacing_um Optional[float]

Distance between consecutive slices in micrometers. Only meaningful together with handle_3d=True; ignored (with a warning) otherwise. None (default) means nothing supplied one, in which case the in-plane pitch is reused and the store records z_spacing_source="assumed_isotropic" so the guess is not mistaken for a measurement. Supply it whenever the section thickness is known -- the in-plane pitch matches it only by coincidence.

None
resampling_config Optional[Dict[str, Any]]

Optional resampling configuration

None
reader_options Optional[Dict[str, Any]]

Optional format-specific reader options: - intensity_threshold: float - Minimum intensity to include. Default: None (no filtering). - use_recalibrated_state: bool - For Bruker data, use active/recalibrated calibration (default True). - max_mass_axis_length: int - For processed-mode imzML converted with --no-resample, give up once the raw mass axis exceeds this many unique m/z values. This matters when the peak lists share no m/z values, where the raw axis grows to roughly one column per peak in the whole dataset. Default: 10,000,000, which is the limit SCiLS Lab places on the same quantity (2026b User Guide, p.76). Pass None for no limit. - spectrum_type: str - For imzML, declare the spectrum representation explicitly: 'profile' or 'centroid' (the full CV names are accepted too). Outranks the file's own MS:1000127/MS:1000128, so it can correct a file that declares the wrong thing; contradicting a declaration is logged as a warning. SCiLS Lab spells this --rep_type (2026b User Guide, p.81). Default: None, meaning detect. Changes stored values for files where it disagrees with what detection would have chosen, because the representation feeds instrument and axis-type selection.

None
sparse_format str

Sparse matrix format ('csc' or 'csr')

'csc'
include_optical bool

Include optical images (default: True)

True
apply_optical_alignment bool

If True (default) and the MSI source carries FlexImaging Area metadata, MSI elements are placed in optical-image pixel space at "global". Set to False to keep MSI in pure micrometer coordinates -- needed when a downstream tool (e.g. Ousia's wizard) computes its own MSI-to-target registration.

True
streaming Union[bool, Literal['auto']]

Use streaming converter for large datasets. - "auto": Auto-detect based on dataset size >10GB (default) - True: Force streaming converter - False: Force standard converter

'auto'
region Optional[Union[int, str]]

For multi-region datasets (e.g. Bruker timsTOF), select a specific region. Accepts an int (DB RegionNumber) or a str (matched against .mis Area Name, falling back to integer parse). None (default) converts all regions. Passed to the reader as reader_options["region"].

None
**kwargs Any

Additional keyword arguments

{}

Returns:

Type Description
bool

True if conversion was successful, False otherwise


Previewing an Input

preview_msi answers "what is this file?" without converting it. It detects the format, builds the reader, and reads metadata only -- no spectra are decoded and no store is written -- so it is cheap enough to call while a user waits. It is the entry point behind the Ousia import wizard's per-sample preview card.

Two properties make it usable directly from UI code:

  • It never raises. Any failure -- a path that does not exist, an unrecognised format, a truncated file -- comes back as an MsiPreview with readable=False and error set to the message. Check readable before reading the numeric fields; they hold zeroes when it is False.
  • It is fast. The design budget is under 500 ms for inputs up to about 50 GB, because it never touches the spectra.
from pathlib import Path

from thyra import preview_msi

p = preview_msi(Path("example_data/synthetic_brain.imzML"))

if p.readable:
    print(p.grid_dims)        # (48, 36)
    print(p.n_pixels)         # 1728
    print(p.mz_range)         # (250.0, 1200.0)
    print(p.pixel_size_um)    # 25.0
    print(p.instrument_type)  # AxisType.CONSTANT
else:
    print("cannot read:", p.error)

Failure looks the same shape, which is the point:

p = preview_msi(Path("no/such/file.imzML"))
print(p.readable, p.error)
# False Path does not exist: no\such\file.imzML

instrument_type is the AxisType the resampling decision tree would pick for this input, so a caller can show the default before the user commits to it. has_escdat_folder reports whether <path>/EscDat/ exists, which is how a downstream tool decides whether EscDat-derived registration is available.

preview_msi(path: Path) -> MsiPreview

Return a metadata-only snapshot of an MSI input.

This is the entry point used by the Ousia Import Wizard to drive the per-sample preview card in step 2. It detects the format, constructs the appropriate reader, and calls :meth:BaseMSIReader.get_essential_metadata (plus :meth:get_comprehensive_metadata for the instrument-type guess). No spectra are decoded.

Parameters:

Name Type Description Default
path Path

Filesystem path to an .imzML file or a Bruker .d directory. May or may not exist; nonexistent paths are returned as readable=False rather than raising.

required

Returns:

Type Description
MsiPreview

class:MsiPreview -- always returns; never raises. Check

MsiPreview

attr:MsiPreview.readable before using the numeric fields.

Example

from thyra import preview_msi p = preview_msi(Path("slice_01.imzML")) if p.readable: ... print(p.grid_dims, p.mz_range)

MsiPreview(mz_range: Tuple[float, float], n_pixels: int, grid_dims: Tuple[int, int], instrument_type: Optional[AxisType], pixel_size_um: Optional[float], has_escdat_folder: bool, readable: bool, error: Optional[str] = None, resampling_method: Optional[ResamplingMethod] = None) dataclass

Metadata-only snapshot of an MSI input.

Attributes:

Name Type Description
mz_range Tuple[float, float]

(min_mz, max_mz) of the source mass axis, in Da. (0.0, 0.0) when readable=False.

n_pixels int

Total number of spectra (pixels) in the dataset. 0 when readable=False.

grid_dims Tuple[int, int]

Grid dimensions as (width, height) in pixels -- i.e. (x, y) from :attr:EssentialMetadata.dimensions. (0, 0) when readable=False.

instrument_type Optional[AxisType]

The :class:AxisType the :class:ResamplingDecisionTree picks for this input, or None if metadata could not be extracted. The wizard uses this as a default in step 4; the user may always override.

pixel_size_um Optional[float]

Pixel pitch in micrometres. Thyra stores a (x_um, y_um) tuple internally; preview returns the x component and logs a debug message when x != y (real MSI rasters are essentially always isotropic). None when the source metadata does not declare a pixel size.

has_escdat_folder bool

True iff <path>/EscDat/ exists and is a directory. Used by step 3 to decide whether the EscDat-derived registration is available for this sample. For non-Bruker inputs this is virtually always False, which is the intended behaviour.

readable bool

True iff format detection succeeded, a reader was built, and get_essential_metadata returned without raising.

error Optional[str]

The exception's str() when readable=False; None otherwise.

resampling_method Optional[ResamplingMethod]

The :class:ResamplingMethod the :class:ResamplingDecisionTree picks for this input, or None if metadata could not be extracted. Report this rather than re-deriving a method from :attr:instrument_type: the two are separate answers from the detector, and inferring one from the other gets it wrong wherever a detector pairs them unconventionally. A caller that assumed constant implied profile MALDI -- and so tic_preserving -- reached exactly that bug on PHI ToF-SIMS, which the catch-all default reports as constant while asking for nearest_neighbor.


Resampling Configuration

When you pass resampling_config to convert_msi, the dictionary keys map to the fields of ResamplingConfig. You can pass a plain dict (as shown in the examples above) or construct the dataclass directly:

from thyra.resampling.types import ResamplingConfig, ResamplingMethod, AxisType

config = ResamplingConfig(
    method=ResamplingMethod.TIC_PRESERVING,
    axis_type=AxisType.ORBITRAP,
    target_bins=50000,
)

success = convert_msi("input.imzML", "output.zarr", resampling_config=config)

ResamplingConfig(method: Optional[ResamplingMethod] = None, axis_type: Optional[AxisType] = None, target_bins: Optional[int] = None, mass_width_da: Optional[float] = None, reference_mz: float = DEFAULT_REFERENCE_MZ, min_mz: Optional[float] = None, max_mz: Optional[float] = None, gap_tolerance_da: Optional[float] = None) dataclass

Configuration for resampling operations.

All fields default to None (auto-detect from instrument metadata). You can override individual fields while leaving the rest automatic.

Attributes:

Name Type Description
method Optional[ResamplingMethod]

Resampling algorithm. None auto-selects based on the instrument type.

axis_type Optional[AxisType]

Mass axis spacing model. None auto-detects from the instrument metadata.

target_bins Optional[int]

Number of bins in the resampled axis. None derives the count from mass_width_da at reference_mz across the mass range. Note that this is a fixed target width, not a measurement: the source spectra's own point spacing is never consulted, so the resulting axis can be finer or coarser than the data it will hold.

mass_width_da Optional[float]

Bin width in Daltons at reference_mz. Alternative to target_bins -- specify one or the other.

reference_mz float

Reference m/z for mass_width_da. Default 1000.0 Da, matching the --resample-reference-mz CLI default.

min_mz Optional[float]

Override the lower bound of the mass range.

max_mz Optional[float]

Override the upper bound of the mass range.

gap_tolerance_da Optional[float]

How far, in Daltons, a target bin may sit from the nearest source m/z before tic_preserving discards its interpolated value instead of trusting it. None -- the default -- means no check, which is how np.interp behaves: straight lines are drawn across regions where nothing was measured. Only affects tic_preserving; nearest_neighbor never invents a bin. See :mod:thyra.resampling.gaps.

ResamplingMethod

Bases: Enum

Available resampling methods.

Attributes:

Name Type Description
NONE

No resampling -- keep the original mass axis.

NEAREST_NEIGHBOR

Snap each peak to the nearest target bin.

TIC_PRESERVING

Redistribute intensity so the total ion count is preserved after rebinning (recommended for quantitative work).

NEAREST_NEIGHBOR = 'nearest_neighbor' class-attribute instance-attribute

NONE = 'none' class-attribute instance-attribute

TIC_PRESERVING = 'tic_preserving' class-attribute instance-attribute

AxisType

Bases: Enum

Mass axis spacing model, determined by the analyser physics.

The axis type controls how target bins are distributed across the mass range. When set to None in :class:ResamplingConfig, the type is auto-detected from instrument metadata.

Attributes:

Name Type Description
CONSTANT

Equidistant spacing (constant Da per bin).

LINEAR_TOF

Linear TOF -- spacing proportional to sqrt(m/z).

REFLECTOR_TOF

Reflector TOF -- spacing proportional to m/z.

ORBITRAP

Orbitrap -- spacing proportional to m/z^(3/2).

FTICR

FTICR -- spacing proportional to m/z^2.

UNKNOWN

Unknown analyser; falls back to constant spacing.

CONSTANT = 'constant' class-attribute instance-attribute

FTICR = 'fticr' class-attribute instance-attribute

LINEAR_TOF = 'linear_tof' class-attribute instance-attribute

ORBITRAP = 'orbitrap' class-attribute instance-attribute

REFLECTOR_TOF = 'reflector_tof' class-attribute instance-attribute

UNKNOWN = 'unknown' class-attribute instance-attribute


Metadata Types

Readers expose metadata through two dataclasses. EssentialMetadata contains everything needed for conversion decisions (grid size, mass range, memory estimate). ComprehensiveMetadata wraps essential metadata and adds vendor-specific details for provenance and QC.

from thyra.readers.imzml import ImzMLReader

with ImzMLReader("sample.imzML") as reader:
    meta = reader.get_essential_metadata()
    print(f"Grid: {meta.dimensions}")
    print(f"m/z range: {meta.mass_range}")
    print(f"Spectra: {meta.n_spectra}")
    print(f"Est. memory: {meta.estimated_memory_gb:.1f} GB")

EssentialMetadata(dimensions: Tuple[int, int, int], coordinate_bounds: Tuple[float, float, float, float], mass_range: Tuple[float, float], pixel_size: Optional[Tuple[float, float]], n_spectra: int, total_peaks: int, estimated_memory_gb: float, source_path: str, coordinate_offsets: Optional[Tuple[int, int, int]] = None, spectrum_type: Optional[str] = None, peak_counts_per_pixel: Optional[NDArray[np.int32]] = None, z_spacing_um: Optional[float] = None) dataclass

Critical metadata for processing decisions and interpolation setup.

Attributes:

Name Type Description
dimensions Tuple[int, int, int]

Grid dimensions as (x, y, z).

coordinate_bounds Tuple[float, float, float, float]

Spatial extent as (min_x, max_x, min_y, max_y).

mass_range Tuple[float, float]

Mass-to-charge range as (min_mz, max_mz).

pixel_size Optional[Tuple[float, float]]

In-plane pixel dimensions as (x_um, y_um) in micrometres, or None when not detected. This is the raster pitch and says nothing about z; see z_spacing_um.

n_spectra int

Total number of spectra in the dataset.

total_peaks int

Total number of peaks across all spectra (used for sparse matrix pre-allocation).

estimated_memory_gb float

Estimated dense memory footprint in GB.

source_path str

Absolute path to the source data.

coordinate_offsets Optional[Tuple[int, int, int]]

Raw coordinate offsets (x, y, z) used to normalise coordinates to 0-based indexing.

spectrum_type Optional[str]

Spectrum type string (e.g. "centroid spectrum"), used to guide resampling decisions.

peak_counts_per_pixel Optional[NDArray[int32]]

Per-pixel peak counts for CSR indptr construction in the streaming converter. Array of size n_pixels where arr[pixel_idx] = peak_count and pixel_idx = z * (n_x * n_y) + y * n_x + x.

z_spacing_um Optional[float]

Distance between consecutive slices in micrometres, or None when the source cannot report it. This is a physical distance set by how the sections were cut, and is unrelated to the in-plane pitch in pixel_size -- the two agree only by coincidence. No reader populates this today: imzML has no standard term for it, and 3D acquisitions are frequently non-consecutive sections, so the value usually has to be supplied by the user. The field exists so a format that does record it has somewhere to report it, and so the converter's precedence chain has something to consult.

has_pixel_size: bool property

Check if pixel size information is available.

is_3d: bool property

Check if dataset is 3D (z > 1).

ComprehensiveMetadata(essential: EssentialMetadata, format_specific: Dict[str, Any], acquisition_params: Dict[str, Any], instrument_info: Dict[str, Any], raw_metadata: Dict[str, Any]) dataclass

Complete metadata including format-specific details.

Wraps :class:EssentialMetadata and adds vendor-specific information that is not needed for conversion but useful for provenance and QC.

Attributes:

Name Type Description
essential EssentialMetadata

Core metadata required for conversion.

format_specific Dict[str, Any]

Vendor-specific metadata (e.g. ImzML CV params, Bruker property tables).

acquisition_params Dict[str, Any]

Acquisition parameters such as polarity, scan range, and laser settings.

instrument_info Dict[str, Any]

Instrument model, serial number, and software version.

raw_metadata Dict[str, Any]

Unprocessed metadata exactly as read from the source file, preserved for round-trip fidelity.

coordinate_bounds: Tuple[float, float, float, float] property

Convenience access to coordinate bounds from essential metadata.

dimensions: Tuple[int, int, int] property

Convenience access to dimensions from essential metadata.

pixel_size: Optional[Tuple[float, float]] property

Convenience access to pixel size from essential metadata.


Metadata Schema

The versioned, ontology-mapped uns["msi_metadata"] block every store carries. See Metadata Schema for the storage contract, the CLI (thyra validate, thyra export-metaspace), and the versioning rules.

from thyra.metadata.schema import (
    read_msi_metadata_blocks,
    to_metaspace,
    validate_document,
)

blocks = read_msi_metadata_blocks("output.zarr")
meta, issues = validate_document(blocks["msi_dataset_z0"])
submission, warnings = to_metaspace(meta)

MSIMetadata

Bases: _SchemaModel

The versioned MSI metadata document.

sample and preparation cannot be auto-populated from raw files and default to empty; ms_analysis and provenance are written by the converter for every store.

to_uns_dict() -> Dict[str, Any]

Serialise for storage in table.uns.

None fields are dropped, and the sample / preparation / processing sections are omitted entirely when empty -- following the store convention that a section the source has nothing for is omitted rather than written empty, so consumers can tell "not available" from "available and empty".

processing is stored as a JSON string: it is a list of objects, which AnnData/zarr cannot round-trip (the same reason uns["regions"] is JSON). read_msi_metadata_blocks and validate_document both decode it transparently.

validate_document(doc: Any) -> Tuple[Optional[MSIMetadata], List[ValidationIssue]]

Validate one metadata document.

Parameters:

Name Type Description Default
doc Any

The parsed document (normally a dict read from a store's uns["msi_metadata"] or from a JSON file).

required

Returns:

Type Description
Optional[MSIMetadata]

(model, issues). The model is None when structural

List[ValidationIssue]

validation failed; issues carry everything found, errors first

Tuple[Optional[MSIMetadata], List[ValidationIssue]]

is not guaranteed -- filter on severity.

read_msi_metadata_blocks(store_path: Union[str, Path]) -> Dict[str, Dict[str, Any]]

Read every table's msi_metadata block from a SpatialData store.

Parameters:

Name Type Description Default
store_path Union[str, Path]

Path to a converted .zarr store.

required

Returns:

Type Description
Dict[str, Dict[str, Any]]

Mapping of table name to the block as a plain dict. Tables

Dict[str, Dict[str, Any]]

without a block are skipped; a store written by a Thyra version

Dict[str, Dict[str, Any]]

that predates the schema therefore returns an empty mapping.

Raises:

Type Description
ValueError

If the path is not a SpatialData store (no tables group).

to_metaspace(meta: MSIMetadata) -> Tuple[Dict[str, Any], List[str]]

Render the METASPACE submission metadata document.

Parameters:

Name Type Description Default
meta MSIMetadata

A validated MSI metadata document.

required

Returns:

Type Description
Dict[str, Any]

(document, warnings) -- the submission JSON structure, and

List[str]

one warning per required field that had to be left empty.

build_msi_metadata(comprehensive: Optional[ComprehensiveMetadata], *, pixel_size_um: Tuple[float, float], pixel_size_source: Optional[str] = None, source_format: Optional[str] = None, processing: Optional[List[ProcessingStep]] = None) -> MSIMetadata

Build an :class:MSIMetadata document from extracted metadata.

Parameters:

Name Type Description Default
comprehensive Optional[ComprehensiveMetadata]

The reader's comprehensive metadata, or None when unavailable -- the document is still built, carrying the pixel size and provenance.

required
pixel_size_um Tuple[float, float]

Resolved in-plane pixel pitch (x_um, y_um). Required: conversion refuses to run without one, so a block without it describes no store Thyra ever wrote.

required
pixel_size_source Optional[str]

How the pixel size was determined ("automatic" / "manual" / "default").

None
source_format Optional[str]

Detected input format name ("imzml", "bruker", ...), when known.

None
processing Optional[List[ProcessingStep]]

Ordered processing steps performed so far, oldest first (see :class:ProcessingStep).

None

Returns:

Type Description
MSIMetadata

The populated document. Fields the source does not report are

MSIMetadata

left unset.


Reader Base Class

All format readers (ImzML, Bruker, Waters, PHI) inherit from this base class. If you are writing a custom reader for a new format, subclass BaseMSIReader and implement the abstract methods below. See Supported Formats for which optional methods are worth implementing and what each one buys you.

BaseMSIReader(data_path: Path, intensity_threshold: Optional[float] = None, **kwargs: object)

Bases: ABC

Abstract base class for reading MSI data formats.

Initialize the reader with the path to the data.

Parameters:

Name Type Description Default
data_path Path

Path to the data file or directory

required
intensity_threshold Optional[float]

Minimum intensity value to include. Values below this threshold are filtered out during iteration. Useful for removing detector noise in continuous mode data. Default: None (no filtering, include all values).

None
**kwargs object

Additional reader-specific parameters

{}

has_shared_mass_axis: bool property

Check if all spectra share the same m/z axis.

For continuous ImzML data, all spectra have identical m/z values, so get_common_mass_axis() only needs to read the first spectrum. For processed/centroid data, each spectrum may have different m/z values, requiring iteration through all spectra.

Returns:

Type Description
bool

True if all spectra share the same m/z axis (continuous mode),

bool

False if each spectrum has different m/z values (processed mode).

get_essential_metadata() -> EssentialMetadata

Get essential metadata for processing.

get_comprehensive_metadata() -> ComprehensiveMetadata

Get complete metadata.

get_common_mass_axis() -> NDArray[np.float64] abstractmethod

Return the common mass axis for all spectra.

This method must always return a valid array. If no common mass axis can be created, implementations should raise an exception.

get_mass_axis_annotations() -> Optional[dict]

Get extra per-channel columns to store alongside the m/z axis.

Returns a mapping of column name to an array with one entry per entry of :meth:get_common_mass_axis. These are written into the table's var next to mz.

This exists so a format whose native axis is not m/z can keep that axis in the output. Time-of-flight instruments measure flight time and derive m/z from a calibration, so storing the flight time makes the conversion reversible without the reader: a later recalibration can be applied to the stored times directly.

Annotations are dropped if their length does not match the axis the converter actually writes, which is what happens when resampling is enabled and the axis is rebuilt.

Returns:

Type Description
Optional[dict]

Mapping of column name to per-channel values, or None.

get_optical_image_paths() -> List[Path]

Get paths to optical/microscopy images associated with this data.

Returns list of TIFF file paths that contain optical images of the sample. These images can be stored alongside MSI data in SpatialData output for multimodal analysis.

Default implementation returns empty list. Subclasses should override to return paths to optical images specific to their format.

Returns:

Type Description
List[Path]

List of paths to TIFF files, empty if no optical images available.

get_peak_counts_per_pixel() -> Optional[NDArray[np.int32]]

Get per-pixel peak counts for CSR indptr construction.

This method enables optimized streaming conversion by providing pre-computed peak counts, avoiding the need for a separate counting pass.

Returns:

Type Description
Optional[NDArray[int32]]

Array of size n_pixels where arr[pixel_idx] = peak_count.

Optional[NDArray[int32]]

pixel_idx = z * (n_x * n_y) + y * n_x + x

Optional[NDArray[int32]]

Returns None if not supported/available for this reader.

Note

Override in subclass to enable optimized streaming conversion. The default implementation returns None, which causes the streaming converter to fall back to a two-pass approach.

Warning

When intensity_threshold is set, the actual peak counts after filtering may be lower than the values returned here, since this method typically returns pre-computed counts from metadata that don't account for intensity filtering. The streaming converter handles this gracefully by using a two-pass approach.

iter_spectra(batch_size: Optional[int] = None) -> Generator[Tuple[Tuple[int, int, int], NDArray[np.float64], NDArray[np.float64]], None, None] abstractmethod

Iterate through spectra with optional batch processing.

Parameters:

Name Type Description Default
batch_size Optional[int]

Optional batch size for spectrum iteration

None

Yields:

Type Description
Tuple[Tuple[int, int, int], NDArray[float64], NDArray[float64]]

Tuple containing:

  • Coordinates (x, y, z) using 0-based indexing
  • m/z values array

  • Intensity values array

Note

Subclasses should apply intensity threshold filtering by calling _apply_intensity_filter() on the intensities before yielding.

get_region_map() -> Optional[dict]

Get per-pixel region mapping for multi-region datasets.

Returns a dictionary mapping normalized (0-based) (x, y) coordinate tuples to integer region numbers. This enables the converter to annotate each pixel with its acquisition region in obs["region_number"].

Default implementation returns None (single-region or no region info). Subclasses should override when region information is available.

Returns:

Type Description
Optional[dict]

Dict mapping (x, y) tuples to region numbers, or None if

Optional[dict]

region information is not available.

get_region_info() -> Optional[list]

Get summary information about acquisition regions.

Returns a list of dictionaries, each describing one region with at minimum: {"region_number": int, "n_spectra": int}. Additional keys (e.g. "name") are format-specific and optional.

Default implementation returns None (single-region or no region info). Subclasses should override when region information is available.

Returns:

Type Description
Optional[list]

List of region summary dicts, or None if region information

Optional[list]

is not available.

close() -> None abstractmethod

Close all open file handles.


Converter Base Class

All output converters inherit from this base class. Currently only SpatialData output is supported, but the architecture allows adding new output formats by subclassing BaseMSIConverter.

BaseMSIConverter(reader: BaseMSIReader, output_path: Union[str, Path, PathLike[str]], dataset_id: str = 'msi_dataset', pixel_size_um: float = 1.0, pixel_size_source: PixelSizeSource = PixelSizeSource.DEFAULT, compression_level: int = 5, handle_3d: bool = False, z_spacing_um: Optional[float] = None, **kwargs: Any)

Bases: ABC

Base class for MSI data converters with shared functionality.

Implements common processing steps while allowing format-specific customization.

Initialize the MSI converter.

Parameters:

Name Type Description Default
reader BaseMSIReader

MSI data reader instance

required
output_path Union[str, Path, PathLike[str]]

Path for output file

required
dataset_id str

Identifier for the dataset

'msi_dataset'
pixel_size_um float

In-plane pixel pitch in micrometers

1.0
pixel_size_source PixelSizeSource

How pixel size was determined

DEFAULT
compression_level int

Compression level for output

5
handle_3d bool

Whether to process as 3D data

False
z_spacing_um Optional[float]

Distance between consecutive slices in micrometers. Only meaningful with handle_3d=True. None (default) means "nobody said", which falls back to the in-plane pitch and records that it was assumed -- see :meth:_resolve_z_spacing.

None
**kwargs Any

Additional keyword arguments

{}

Raises:

Type Description
ValueError

If z_spacing_um is given and is not positive.

pixel_size_um = pixel_size_um instance-attribute

pixel_size_source = pixel_size_source instance-attribute

dataset_id = dataset_id instance-attribute

handle_3d = handle_3d instance-attribute

convert() -> bool

Template method defining the conversion workflow.

Returns:

bool: True if conversion was successful, False otherwise.


Format Detection and Plugin Registry

Thyra uses a registry to map file extensions and directory structures to the correct reader and converter classes. The public functions below let you detect formats programmatically or register your own reader/converter.

Detecting a format

from pathlib import Path
from thyra.core.registry import detect_format

fmt = detect_format(Path("experiment.imzML"))  # "imzml"
fmt = detect_format(Path("data.d"))            # "bruker" or "rapiflex"
fmt = detect_format(Path("data.raw"))          # "waters"

Registering a custom reader

from thyra.core.registry import register_reader
from thyra.core.base_reader import BaseMSIReader

@register_reader("my_format")
class MyFormatReader(BaseMSIReader):
    ...

detect_format(input_path: Path) -> str

Detect MSI format from input path.

Parameters:

Name Type Description Default
input_path Path

Path to MSI data file or directory

required

Returns:

Type Description
str

Format name ('imzml', 'bruker', 'rapiflex', 'waters', or 'phi')

register_reader(format_name: str)

Decorator for reader registration.

register_converter(format_name: str)

Decorator for converter registration.