Skip to content

Api

spatialdata_xenium_explorer.write(path, sdata, image_key=None, shapes_key=None, points_key=None, gene_column=None, pixel_size=0.2125, spot=False, layer=None, polygon_max_vertices=13, lazy=True, ram_threshold_gb=4, mode=None)

Transform a SpatialData object into inputs for the Xenium Explorer. After running this function, double-click on the experiment.xenium file to open it.

Software download

Make sure you have the latest version of the Xenium Explorer

Note

This function will create up to 6 files, depending on the SpatialData object and the arguments:

  • experiment.xenium contains some experiment metadata. Double-click on this file to open the Xenium Explorer. This file can also be created with write_metadata.

  • morphology.ome.tif is the primary image. This file can also be created with write_image. Add more images with align.

  • analysis.zarr.zip contains the cells categories (or clusters), i.e. adata.obs. This file can also be created with write_cell_categories.

  • cell_feature_matrix.zarr.zip contains the cell-by-gene counts. This file can also be created with write_gene_counts.

  • cells.zarr.zip contains the cells polygon boundaries. This file can also be created with write_polygons.

  • transcripts.zarr.zip contains transcripts locations. This file can also be created with write_transcripts.

Parameters:

Name Type Description Default
path str

Path to the directory where files will be saved.

required
sdata SpatialData

A SpatialData object.

required
image_key str | None

Name of the image of interest (key of sdata.images). This argument doesn't need to be provided if there is only one image.

None
shapes_key str | None

Name of the cell shapes (key of sdata.shapes). This argument doesn't need to be provided if there is only one shapes key or a table with only one region.

None
points_key str | None

Name of the transcripts (key of sdata.points). This argument doesn't need to be provided if there is only one points key.

None
gene_column str | None

Column name of the points dataframe containing the gene names.

None
pixel_size float

Number of microns in a pixel. Invalid value can lead to inconsistent scales in the Explorer.

0.2125
spot bool

Whether the technology is based on spots

False
layer str | None

Layer of sdata.table where the gene counts are saved. If None, uses sdata.table.X.

None
polygon_max_vertices int

Maximum number of vertices for the cell polygons. A higher value will display smoother cells.

13
lazy bool

If True, will not load the full images in memory (except if the image memory is below ram_threshold_gb).

True
ram_threshold_gb int | None

Threshold (in gygabytes) from which image can be loaded in memory. If None, the image is never loaded in memory.

4
mode str

string that indicated which files should be created. "-ib" means everything except images and boundaries, while "+tocm" means only transcripts/observations/counts/metadata (each letter corresponds to one explorer file). By default, keeps everything.

None
Source code in spatialdata_xenium_explorer/converter.py
def write(
    path: str,
    sdata: SpatialData,
    image_key: str | None = None,
    shapes_key: str | None = None,
    points_key: str | None = None,
    gene_column: str | None = None,
    pixel_size: float = 0.2125,
    spot: bool = False,
    layer: str | None = None,
    polygon_max_vertices: int = 13,
    lazy: bool = True,
    ram_threshold_gb: int | None = 4,
    mode: str = None,
) -> None:
    """
    Transform a SpatialData object into inputs for the Xenium Explorer.
    After running this function, double-click on the `experiment.xenium` file to open it.

    !!! note "Software download"
        Make sure you have the latest version of the [Xenium Explorer](https://www.10xgenomics.com/support/software/xenium-explorer)

    Note:
        This function will create up to 6 files, depending on the `SpatialData` object and the arguments:

        - `experiment.xenium` contains some experiment metadata. Double-click on this file to open the Xenium Explorer. This file can also be created with [`write_metadata`](./#spatialdata_xenium_explorer.write_metadata).

        - `morphology.ome.tif` is the primary image. This file can also be created with [`write_image`](./#spatialdata_xenium_explorer.write_image). Add more images with `align`.

        - `analysis.zarr.zip` contains the cells categories (or clusters), i.e. `adata.obs`. This file can also be created with [`write_cell_categories`](./#spatialdata_xenium_explorer.write_cell_categories).

        - `cell_feature_matrix.zarr.zip` contains the cell-by-gene counts. This file can also be created with [`write_gene_counts`](./#spatialdata_xenium_explorer.write_gene_counts).

        - `cells.zarr.zip` contains the cells polygon boundaries. This file can also be created with [`write_polygons`](./#spatialdata_xenium_explorer.write_polygons).

        - `transcripts.zarr.zip` contains transcripts locations. This file can also be created with [`write_transcripts`](./#spatialdata_xenium_explorer.write_transcripts).

    Args:
        path: Path to the directory where files will be saved.
        sdata: A `SpatialData` object.
        image_key: Name of the image of interest (key of `sdata.images`). This argument doesn't need to be provided if there is only one image.
        shapes_key: Name of the cell shapes (key of `sdata.shapes`). This argument doesn't need to be provided if there is only one shapes key or a table with only one region.
        points_key: Name of the transcripts (key of `sdata.points`). This argument doesn't need to be provided if there is only one points key.
        gene_column: Column name of the points dataframe containing the gene names.
        pixel_size: Number of microns in a pixel. Invalid value can lead to inconsistent scales in the Explorer.
        spot: Whether the technology is based on spots
        layer: Layer of `sdata.table` where the gene counts are saved. If `None`, uses `sdata.table.X`.
        polygon_max_vertices: Maximum number of vertices for the cell polygons. A higher value will display smoother cells.
        lazy: If `True`, will not load the full images in memory (except if the image memory is below `ram_threshold_gb`).
        ram_threshold_gb: Threshold (in gygabytes) from which image can be loaded in memory. If `None`, the image is never loaded in memory.
        mode: string that indicated which files should be created. "-ib" means everything except images and boundaries, while "+tocm" means only transcripts/observations/counts/metadata (each letter corresponds to one explorer file). By default, keeps everything.
    """
    path: Path = Path(path)
    _check_explorer_directory(path)

    image_key, image = utils.get_spatial_image(sdata, image_key, return_key=True)

    ### Saving cell categories and gene counts
    if sdata.table is not None:
        adata = sdata.table

        region = adata.uns["spatialdata_attrs"]["region"]
        region = region if isinstance(region, list) else [region]

        if len(region) == 1:
            assert (
                shapes_key is None or shapes_key == region[0]
            ), f"Found only one region ({region[0]}), but `shapes_key` was provided with a different value ({shapes_key})"
            shapes_key = region[0]

        if _should_save(mode, "c"):
            write_gene_counts(path, adata, layer=layer)
        if _should_save(mode, "o"):
            write_cell_categories(path, adata)

    ### Saving cell boundaries
    shapes_key, geo_df = utils.get_element(sdata, "shapes", shapes_key, return_key=True)

    if _should_save(mode, "b") and geo_df is not None:
        geo_df = utils.to_intrinsic(sdata, geo_df, image_key)

        if sdata.table is not None:
            geo_df = geo_df.loc[adata.obs[adata.uns["spatialdata_attrs"]["instance_key"]]]

        geo_df = utils._standardize_shapes(geo_df)

        write_polygons(path, geo_df.geometry, polygon_max_vertices, pixel_size=pixel_size)

    ### Saving transcripts
    if spot and sdata.table is not None:
        df, gene_column = utils._spot_transcripts_origin(adata)
    else:
        df = utils.get_element(sdata, "points", points_key)
        df = utils.to_intrinsic(sdata, df, image_key)

    if _should_save(mode, "t") and df is not None:
        if gene_column is not None:
            write_transcripts(path, df, gene_column, pixel_size=pixel_size)
        else:
            log.warn("The argument 'gene_column' has to be provided to save the transcripts")

    ### Saving image
    if _should_save(mode, "i"):
        write_image(
            path, image, lazy=lazy, ram_threshold_gb=ram_threshold_gb, pixel_size=pixel_size
        )

    ### Saving experiment.xenium file
    if _should_save(mode, "m"):
        write_metadata(path, image_key, shapes_key, _get_n_obs(sdata, geo_df), pixel_size)

    log.info(f"Saved files in the following directory: {path}")
    log.info(f"You can open the experiment with 'open {path / FileNames.METADATA}'")

spatialdata_xenium_explorer.write_image(path, image, lazy=True, tile_width=1024, n_subscales=5, pixel_size=0.2125, ram_threshold_gb=4, is_dir=True)

Convert an image into a morphology.ome.tif file that can be read by the Xenium Explorer

Parameters:

Name Type Description Default
path str

Path to the Xenium Explorer directory where the image will be written

required
image SpatialImage | ndarray

Image of shape (C, Y, X)

required
lazy bool

If False, the image will not be read in-memory (except if the image size is below ram_threshold_gb). If True, all the images levels are always loaded in-memory.

True
tile_width int

Xenium tile width (do not update).

1024
n_subscales int

Number of sub-scales in the pyramidal image.

5
pixel_size float

Xenium pixel size (do not update).

0.2125
ram_threshold_gb int | None

If an image (of any level of the pyramid) is below this threshold, it will be loaded in-memory.

4
is_dir bool

If False, then path is a path to a single file, not to the Xenium Explorer directory.

True
Source code in spatialdata_xenium_explorer/core/images.py
def write_image(
    path: str,
    image: SpatialImage | np.ndarray,
    lazy: bool = True,
    tile_width: int = 1024,
    n_subscales: int = 5,
    pixel_size: float = 0.2125,
    ram_threshold_gb: int | None = 4,
    is_dir: bool = True,
):
    """Convert an image into a `morphology.ome.tif` file that can be read by the Xenium Explorer

    Args:
        path: Path to the Xenium Explorer directory where the image will be written
        image: Image of shape `(C, Y, X)`
        lazy: If `False`, the image will not be read in-memory (except if the image size is below `ram_threshold_gb`). If `True`, all the images levels are always loaded in-memory.
        tile_width: Xenium tile width (do not update).
        n_subscales: Number of sub-scales in the pyramidal image.
        pixel_size: Xenium pixel size (do not update).
        ram_threshold_gb: If an image (of any level of the pyramid) is below this threshold, it will be loaded in-memory.
        is_dir: If `False`, then `path` is a path to a single file, not to the Xenium Explorer directory.
    """
    path = utils.explorer_file_path(path, FileNames.IMAGE, is_dir)

    if isinstance(image, np.ndarray):
        assert len(image.shape) == 3, "Can only write channels with shape (C,Y,X)"
        log.info(f"Converting image of shape {image.shape} into a SpatialImage (with dims: C,Y,X)")
        image = SpatialImage(image, dims=["c", "y", "x"], name="image")

    image: MultiscaleSpatialImage = to_multiscale(image, [2] * n_subscales)

    image_writer = MultiscaleImageWriter(image, pixel_size=pixel_size, tile_width=tile_width)
    image_writer.write(path, lazy=lazy, ram_threshold_gb=ram_threshold_gb)

spatialdata_xenium_explorer.write_cell_categories(path, adata, is_dir=True)

Write a analysis.zarr.zip file containing the cell categories/clusters (i.e., from adata.obs)

Parameters:

Name Type Description Default
path str

Path to the Xenium Explorer directory where the cell-categories file will be written

required
adata AnnData

An AnnData object

required
is_dir bool

If False, then path is a path to a single file, not to the Xenium Explorer directory.

True
Source code in spatialdata_xenium_explorer/core/table.py
def write_cell_categories(path: str, adata: AnnData, is_dir: bool = True) -> None:
    """Write a `analysis.zarr.zip` file containing the cell categories/clusters (i.e., from `adata.obs`)

    Args:
        path: Path to the Xenium Explorer directory where the cell-categories file will be written
        adata: An `AnnData` object
        is_dir: If `False`, then `path` is a path to a single file, not to the Xenium Explorer directory.
    """
    path = explorer_file_path(path, FileNames.CELL_CATEGORIES, is_dir)

    adata.strings_to_categoricals()
    cat_columns = [name for name, cat in adata.obs.dtypes.items() if cat == "category"]

    log.info(f"Writing {len(cat_columns)} cell categories: {', '.join(cat_columns)}")

    ATTRS = cell_categories_attrs()
    ATTRS["number_groupings"] = len(cat_columns)

    with zarr.ZipStore(path, mode="w") as store:
        g = zarr.group(store=store)
        cell_groups = g.create_group("cell_groups")

        for i, name in enumerate(cat_columns):
            if adata.obs[name].isna().any():
                NA = "NA"
                log.warn(f"Column {name} has nan values. They will be displayed as '{NA}'")
                adata.obs[name] = adata.obs[name].cat.add_categories(NA).fillna(NA)

            categories = list(adata.obs[name].cat.categories)
            ATTRS["grouping_names"].append(name)
            ATTRS["group_names"].append(categories)

            _write_categorical_column(cell_groups, i, adata.obs[name], categories)

        cell_groups.attrs.put(ATTRS)

spatialdata_xenium_explorer.write_transcripts(path, df, gene='gene', max_levels=15, is_dir=True, pixel_size=0.2125)

Write a transcripts.zarr.zip file containing pyramidal transcript locations

Parameters:

Name Type Description Default
path Path

Path to the Xenium Explorer directory where the transcript file will be written

required
df DataFrame

DataFrame representing the transcripts, with "x", "y" column required, as well as the gene column (see the corresponding argument)

required
gene str

Column of df containing the genes names.

'gene'
max_levels int

Maximum number of levels in the pyramid.

15
is_dir bool

If False, then path is a path to a single file, not to the Xenium Explorer directory.

True
pixel_size float

Number of microns in a pixel. Invalid value can lead to inconsistent scales in the Explorer.

0.2125
Source code in spatialdata_xenium_explorer/core/points.py
def write_transcripts(
    path: Path,
    df: dd.DataFrame,
    gene: str = "gene",
    max_levels: int = 15,
    is_dir: bool = True,
    pixel_size: float = 0.2125,
):
    """Write a `transcripts.zarr.zip` file containing pyramidal transcript locations

    Args:
        path: Path to the Xenium Explorer directory where the transcript file will be written
        df: DataFrame representing the transcripts, with `"x"`, `"y"` column required, as well as the `gene` column (see the corresponding argument)
        gene: Column of `df` containing the genes names.
        max_levels: Maximum number of levels in the pyramid.
        is_dir: If `False`, then `path` is a path to a single file, not to the Xenium Explorer directory.
        pixel_size: Number of microns in a pixel. Invalid value can lead to inconsistent scales in the Explorer.
    """
    path = explorer_file_path(path, FileNames.POINTS, is_dir)

    # TODO: make everything using dask instead of pandas
    df = df.compute()

    num_transcripts = len(df)
    grid_size = ExplorerConstants.GRID_SIZE / ExplorerConstants.PIXELS_TO_MICRONS * pixel_size
    df[gene] = df[gene].astype("category")

    location = df[["x", "y"]]
    location *= pixel_size
    location = np.concatenate([location, np.zeros((num_transcripts, 1))], axis=1)

    if location.min() < 0:
        log.warn("Some transcripts are located outside of the image (pixels < 0)")
    log.info(f"Writing {len(df)} transcripts")

    xmax, ymax = location[:, :2].max(axis=0)

    gene_names = list(df[gene].cat.categories)
    num_genes = len(gene_names)

    codeword_gene_mapping = list(range(num_genes))

    valid = np.ones((num_transcripts, 1))
    uuid = np.stack([np.arange(num_transcripts), np.full(num_transcripts, 65535)], axis=1)
    transcript_id = np.stack([np.arange(num_transcripts), np.full(num_transcripts, 65535)], axis=1)
    gene_identity = df[gene].cat.codes.values[:, None]
    codeword_identity = np.stack([gene_identity[:, 0], np.full(num_transcripts, 65535)], axis=1)
    status = np.zeros((num_transcripts, 1))
    quality_score = np.full((num_transcripts, 1), ExplorerConstants.QUALITY_SCORE)

    ATTRS = {
        "codeword_count": num_genes,
        "codeword_gene_mapping": codeword_gene_mapping,
        "codeword_gene_names": gene_names,
        "gene_names": gene_names,
        "gene_index_map": {name: index for name, index in zip(gene_names, codeword_gene_mapping)},
        "number_genes": num_genes,
        "spatial_units": "micron",
        "coordinate_space": "refined-final_global_micron",
        "major_version": 4,
        "minor_version": 1,
        "name": "RnaDataset",
        "number_rnas": num_transcripts,
        "dataset_uuid": "unique-id-test",
        "data_format": 0,
    }

    GRIDS_ATTRS = {
        "grid_key_names": ["grid_x_loc", "grid_y_loc"],
        "grid_zip": False,
        "grid_size": [grid_size],
        "grid_array_shapes": [],
        "grid_number_objects": [],
        "grid_keys": [],
    }

    with zarr.ZipStore(path, mode="w") as store:
        g = zarr.group(store=store)
        g.attrs.put(ATTRS)

        grids = g.create_group("grids")

        for level in range(max_levels):
            log.info(f"   > Level {level}: {len(location)} transcripts")
            level_group = grids.create_group(level)

            tile_size = grid_size * 2**level

            indices = np.floor(location[:, :2] / tile_size).clip(0).astype(int)
            tiles_str_indices = np.array([f"{tx},{ty}" for (tx, ty) in indices])

            GRIDS_ATTRS["grid_array_shapes"].append([])
            GRIDS_ATTRS["grid_number_objects"].append([])
            GRIDS_ATTRS["grid_keys"].append([])

            n_tiles_x, n_tiles_y = max(1, ceil(xmax / tile_size)), max(1, ceil(ymax / tile_size))

            for tx in range(n_tiles_x):
                for ty in range(n_tiles_y):
                    str_index = f"{tx},{ty}"
                    loc = np.where(tiles_str_indices == str_index)[0]

                    n_points_tile = len(loc)
                    chunks = (n_points_tile, 1)

                    if n_points_tile == 0:
                        continue

                    GRIDS_ATTRS["grid_array_shapes"][-1].append({})
                    GRIDS_ATTRS["grid_keys"][-1].append(str_index)
                    GRIDS_ATTRS["grid_number_objects"][-1].append(n_points_tile)

                    tile_group = level_group.create_group(str_index)
                    tile_group.array(
                        "valid",
                        valid[loc],
                        dtype="uint8",
                        chunks=chunks,
                    )
                    tile_group.array(
                        "status",
                        status[loc],
                        dtype="uint8",
                        chunks=chunks,
                    )
                    tile_group.array(
                        "location",
                        location[loc],
                        dtype="float32",
                        chunks=chunks,
                    )
                    tile_group.array(
                        "gene_identity",
                        gene_identity[loc],
                        dtype="uint16",
                        chunks=chunks,
                    )
                    tile_group.array(
                        "quality_score",
                        quality_score[loc],
                        dtype="float32",
                        chunks=chunks,
                    )
                    tile_group.array(
                        "codeword_identity",
                        codeword_identity[loc],
                        dtype="uint16",
                        chunks=chunks,
                    )
                    tile_group.array(
                        "uuid",
                        uuid[loc],
                        dtype="uint32",
                        chunks=chunks,
                    )
                    tile_group.array(
                        "id",
                        transcript_id[loc],
                        dtype="uint32",
                        chunks=chunks,
                    )

            if n_tiles_x * n_tiles_y == 1 and level > 0:
                GRIDS_ATTRS["number_levels"] = level + 1
                break

            sub_indices = subsample_indices(len(location))

            location = location[sub_indices]
            valid = valid[sub_indices]
            status = status[sub_indices]
            gene_identity = gene_identity[sub_indices]
            quality_score = quality_score[sub_indices]
            codeword_identity = codeword_identity[sub_indices]
            uuid = uuid[sub_indices]
            transcript_id = transcript_id[sub_indices]

        grids.attrs.put(GRIDS_ATTRS)

spatialdata_xenium_explorer.write_gene_counts(path, adata, layer=None, is_dir=True)

Write a cell_feature_matrix.zarr.zip file containing the cell-by-gene transcript counts (i.e., from adata.X)

Parameters:

Name Type Description Default
path str

Path to the Xenium Explorer directory where the cell-by-gene file will be written

required
adata AnnData

An AnnData object. Note that adata.X has to be a sparse matrix (and contain the raw counts), else use the layer argument.

required
layer str | None

If not None, adata.layers[layer] should be sparse (and contain the raw counts).

None
is_dir bool

If False, then path is a path to a single file, not to the Xenium Explorer directory.

True
Source code in spatialdata_xenium_explorer/core/table.py
def write_gene_counts(
    path: str, adata: AnnData, layer: str | None = None, is_dir: bool = True
) -> None:
    """Write a `cell_feature_matrix.zarr.zip` file containing the cell-by-gene transcript counts (i.e., from `adata.X`)

    Args:
        path: Path to the Xenium Explorer directory where the cell-by-gene file will be written
        adata: An `AnnData` object. Note that `adata.X` has to be a sparse matrix (and contain the raw counts), else use the `layer` argument.
        layer: If not `None`, `adata.layers[layer]` should be sparse (and contain the raw counts).
        is_dir: If `False`, then `path` is a path to a single file, not to the Xenium Explorer directory.
    """
    path = explorer_file_path(path, FileNames.TABLE, is_dir)

    log.info(f"Writing table with {adata.n_vars} columns")
    counts = adata.X if layer is None else adata.layers[layer]
    counts = csr_matrix(counts.T)

    feature_keys = list(adata.var_names) + ["Total transcripts"]
    feature_ids = feature_keys
    feature_types = ["gene"] * len(adata.var_names) + ["aggregate_gene"]

    ATTRS = {
        "major_version": 3,
        "minor_version": 0,
        "number_cells": adata.n_obs,
        "number_features": adata.n_vars + 1,
        "feature_keys": feature_keys,
        "feature_ids": feature_ids,
        "feature_types": feature_types,
    }

    total_counts = counts.sum(1).A1
    loc = total_counts > 0

    data = np.concatenate([counts.data, total_counts[loc]])
    indices = np.concatenate([counts.indices, np.where(loc)[0]])
    indptr = counts.indptr
    indptr = np.append(indptr, indptr[-1] + loc.sum())

    cell_id = np.ones((adata.n_obs, 2))
    cell_id[:, 0] = np.arange(adata.n_obs)

    with zarr.ZipStore(path, mode="w") as store:
        g = zarr.group(store=store)
        cells_group = g.create_group("cell_features")
        cells_group.attrs.put(ATTRS)

        cells_group.array("cell_id", cell_id, dtype="uint32", chunks=cell_id.shape)
        cells_group.array("data", data, dtype="uint32", chunks=data.shape)
        cells_group.array("indices", indices, dtype="uint32", chunks=indices.shape)
        cells_group.array("indptr", indptr, dtype="uint32", chunks=indptr.shape)

spatialdata_xenium_explorer.write_polygons(path, polygons, max_vertices, is_dir=True, pixel_size=0.2125)

Write a cells.zarr.zip file containing the cell polygonal boundaries

Parameters:

Name Type Description Default
path Path

Path to the Xenium Explorer directory where the transcript file will be written

required
polygons Iterable[Polygon]

A list of shapely polygons to be written

required
max_vertices int

The number of vertices per polygon (they will be transformed to have the right number of vertices)

required
is_dir bool

If False, then path is a path to a single file, not to the Xenium Explorer directory.

True
pixel_size float

Number of microns in a pixel. Invalid value can lead to inconsistent scales in the Explorer.

0.2125
Source code in spatialdata_xenium_explorer/core/shapes.py
def write_polygons(
    path: Path,
    polygons: Iterable[Polygon],
    max_vertices: int,
    is_dir: bool = True,
    pixel_size: float = 0.2125,
) -> None:
    """Write a `cells.zarr.zip` file containing the cell polygonal boundaries

    Args:
        path: Path to the Xenium Explorer directory where the transcript file will be written
        polygons: A list of `shapely` polygons to be written
        max_vertices: The number of vertices per polygon (they will be transformed to have the right number of vertices)
        is_dir: If `False`, then `path` is a path to a single file, not to the Xenium Explorer directory.
        pixel_size: Number of microns in a pixel. Invalid value can lead to inconsistent scales in the Explorer.
    """
    path = explorer_file_path(path, FileNames.SHAPES, is_dir)

    log.info(f"Writing {len(polygons)} cell polygons")
    coordinates = np.stack([pad_polygon(p, max_vertices) for p in polygons])
    coordinates *= pixel_size

    num_cells = len(coordinates)
    cells_fourth = ceil(num_cells / 4)
    cells_half = ceil(num_cells / 2)

    GROUP_ATTRS = group_attrs()
    GROUP_ATTRS["number_cells"] = num_cells

    polygon_vertices = np.stack([coordinates, coordinates])
    num_points = polygon_vertices.shape[2]
    n_vertices = num_points // 2

    with zarr.ZipStore(path, mode="w") as store:
        g = zarr.group(store=store)
        g.attrs.put(GROUP_ATTRS)

        g.array(
            "polygon_vertices",
            polygon_vertices,
            dtype="float32",
            chunks=(1, cells_fourth, ceil(num_points / 4)),
        )

        cell_id = np.ones((num_cells, 2))
        cell_id[:, 0] = np.arange(num_cells)
        g.array("cell_id", cell_id, dtype="uint32", chunks=(cells_half, 1))

        cell_summary = np.zeros((num_cells, 7))
        cell_summary[:, 2] = [p.area for p in polygons]
        g.array(
            "cell_summary",
            cell_summary,
            dtype="float64",
            chunks=(num_cells, 1),
        )
        g["cell_summary"].attrs.put(cell_summary_attrs())

        g.array(
            "polygon_num_vertices",
            np.full((2, num_cells), n_vertices),
            dtype="int32",
            chunks=(1, cells_half),
        )

        g.array(
            "seg_mask_value",
            np.arange(num_cells),
            dtype="uint32",
            chunks=(cells_half,),
        )

spatialdata_xenium_explorer.write_metadata(path, image_key='NA', shapes_key='NA', n_obs=0, is_dir=True, pixel_size=0.2125)

Create an experiment.xenium file that can be open by the Xenium Explorer.

Note

This function alone is not enough to actually open an experiment. You will need at least to wrun write_image, or create all the outputs with write_explorer.

Parameters:

Name Type Description Default
path str

Path to the Xenium Explorer directory where the metadata file will be written

required
image_key str

Key of SpatialData object containing the primary image used on the explorer.

'NA'
shapes_key str

Key of SpatialData object containing the boundaries shown on the explorer.

'NA'
n_obs int

Number of cells

0
is_dir bool

If False, then path is a path to a single file, not to the Xenium Explorer directory.

True
pixel_size float

Number of microns in a pixel. Invalid value can lead to inconsistent scales in the Explorer.

0.2125
Source code in spatialdata_xenium_explorer/converter.py
def write_metadata(
    path: str,
    image_key: str = "NA",
    shapes_key: str = "NA",
    n_obs: int = 0,
    is_dir: bool = True,
    pixel_size: float = 0.2125,
):
    """Create an `experiment.xenium` file that can be open by the Xenium Explorer.

    Note:
        This function alone is not enough to actually open an experiment. You will need at least to wrun `write_image`, or create all the outputs with `write_explorer`.

    Args:
        path: Path to the Xenium Explorer directory where the metadata file will be written
        image_key: Key of `SpatialData` object containing the primary image used on the explorer.
        shapes_key: Key of `SpatialData` object containing the boundaries shown on the explorer.
        n_obs: Number of cells
        is_dir: If `False`, then `path` is a path to a single file, not to the Xenium Explorer directory.
        pixel_size: Number of microns in a pixel. Invalid value can lead to inconsistent scales in the Explorer.
    """
    path = utils.explorer_file_path(path, FileNames.METADATA, is_dir)

    with open(path, "w") as f:
        metadata = experiment_dict(image_key, shapes_key, n_obs, pixel_size)
        json.dump(metadata, f, indent=4)

spatialdata_xenium_explorer.int_cell_id(explorer_cell_id)

Transforms an alphabetical cell id from the Xenium Explorer to an integer ID

E.g., int_cell_id('aaaachba-1') = 10000

Source code in spatialdata_xenium_explorer/utils.py
def int_cell_id(explorer_cell_id: str) -> int:
    """Transforms an alphabetical cell id from the Xenium Explorer to an integer ID

    E.g., int_cell_id('aaaachba-1') = 10000"""
    code = explorer_cell_id[:-2] if explorer_cell_id[-2] == "-" else explorer_cell_id
    coefs = [ord(c) - 97 for c in code][::-1]
    return sum(value * 16**i for i, value in enumerate(coefs))

spatialdata_xenium_explorer.str_cell_id(cell_id)

Transforms an integer cell ID into an Xenium Explorer alphabetical cell id

E.g., str_cell_id(10000) = 'aaaachba-1'

Source code in spatialdata_xenium_explorer/utils.py
def str_cell_id(cell_id: int) -> str:
    """Transforms an integer cell ID into an Xenium Explorer alphabetical cell id

    E.g., str_cell_id(10000) = 'aaaachba-1'"""
    coefs = []
    for _ in range(8):
        cell_id, coef = divmod(cell_id, 16)
        coefs.append(coef)
    return "".join([chr(97 + coef) for coef in coefs][::-1]) + "-1"

spatialdata_xenium_explorer.align(sdata, image, transformation_matrix_path, image_key=None, image_models_kwargs=None, overwrite=False)

Add an image to the SpatialData object after alignment with the Xenium Explorer.

Parameters:

Name Type Description Default
sdata SpatialData

A SpatialData object

required
image SpatialImage

A SpatialImage object. Note that image.name is used as the key for the aligned image.

required
transformation_matrix_path str

Path to the .csv transformation matrix exported from the Xenium Explorer

required
image_key str

Optional name of the image on which it has been aligned. Required if multiple images in the SpatialData object.

None
image_models_kwargs dict | None

Kwargs to the Image2DModel model.

None
overwrite bool

Whether to overwrite the image, if already existing.

False
Source code in spatialdata_xenium_explorer/core/images.py
def align(
    sdata: SpatialData,
    image: SpatialImage,
    transformation_matrix_path: str,
    image_key: str = None,
    image_models_kwargs: dict | None = None,
    overwrite: bool = False,
):
    """Add an image to the `SpatialData` object after alignment with the Xenium Explorer.

    Args:
        sdata: A `SpatialData` object
        image: A `SpatialImage` object. Note that `image.name` is used as the key for the aligned image.
        transformation_matrix_path: Path to the `.csv` transformation matrix exported from the Xenium Explorer
        image_key: Optional name of the image on which it has been aligned. Required if multiple images in the `SpatialData` object.
        image_models_kwargs: Kwargs to the `Image2DModel` model.
        overwrite: Whether to overwrite the image, if already existing.
    """
    image_name = image.name
    image_models_kwargs = _default_image_models_kwargs(image_models_kwargs)

    to_pixel = Affine(
        np.genfromtxt(transformation_matrix_path, delimiter=","),
        input_axes=("x", "y"),
        output_axes=("x", "y"),
    )

    default_image = utils.get_spatial_image(sdata, image_key)
    pixel_cs = utils.get_intrinsic_cs(sdata, default_image)

    image = Image2DModel.parse(
        image,
        dims=("c", "y", "x"),
        transformations={pixel_cs: to_pixel},
        c_coords=image.coords["c"].values,
        **image_models_kwargs,
    )

    log.info(f"Adding image {image.name}:\n{image}")
    sdata.images[image_name] = image

spatialdata_xenium_explorer.save_column_csv(path, adata, key)

Save one column of the AnnData object as a CSV that can be open interactively in the explorer, under the "cell" panel.

Parameters:

Name Type Description Default
path str

Path where to write the CSV that will be open in the Xenium Explorer

required
adata AnnData

An AnnData object

required
key str

Key of adata.obs containing the column to convert

required
Source code in spatialdata_xenium_explorer/core/table.py
def save_column_csv(path: str, adata: AnnData, key: str):
    """Save one column of the AnnData object as a CSV that can be open interactively in the explorer, under the "cell" panel.

    Args:
        path: Path where to write the CSV that will be open in the Xenium Explorer
        adata: An `AnnData` object
        key: Key of `adata.obs` containing the column to convert
    """
    df = pd.DataFrame({"cell_id": adata.obs_names, "group": adata.obs[key].values})
    df.to_csv(path, index=None)