Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 61 additions & 54 deletions src/spatialdata_io/converters/legacy_anndata.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

import warnings
from collections.abc import Mapping
from typing import TYPE_CHECKING

import numpy as np
Expand All @@ -12,6 +12,7 @@
to_circles,
)
from spatialdata._core.operations._utils import transform_to_data_extent
from spatialdata._logging import logger
from spatialdata.models import Image2DModel, ShapesModel, TableModel, get_table_keys
from spatialdata.transformations import Identity, Scale

Expand Down Expand Up @@ -250,6 +251,7 @@ def from_legacy_anndata(adata: AnnData) -> SpatialData:
IMAGES = "images"
HIRES = "hires"
LOWRES = "lowres"
DEFAULT_SCALE_FACTOR = 1.0

# SpatialData keys
REGION = "locations"
Expand All @@ -265,72 +267,77 @@ def from_legacy_anndata(adata: AnnData) -> SpatialData:
if SPATIAL in adata.uns:
dataset_ids = list(adata.uns[SPATIAL].keys())
for dataset_id in dataset_ids:
# read the image data and the scale factors for the shapes
keys = set(adata.uns[SPATIAL][dataset_id].keys())
tissue_hires_scalef = None
tissue_lowres_scalef = None
hires = None
lowres = None
if SCALEFACTORS in keys:
scalefactors = adata.uns[SPATIAL][dataset_id][SCALEFACTORS]
if TISSUE_HIRES_SCALEF in scalefactors:
tissue_hires_scalef = scalefactors[TISSUE_HIRES_SCALEF]
if TISSUE_LOWRES_SCALEF in scalefactors:
tissue_lowres_scalef = scalefactors[TISSUE_LOWRES_SCALEF]
if SPOT_DIAMETER_FULLRES in scalefactors:
spot_diameter_fullres_list.append(scalefactors[SPOT_DIAMETER_FULLRES])
if IMAGES in keys:
image_data = adata.uns[SPATIAL][dataset_id][IMAGES]
if HIRES in image_data:
hires = image_data[HIRES]
if LOWRES in image_data:
lowres = image_data[LOWRES]

# construct the spatialdata elements
if hires is not None:
# prepare the hires image
assert tissue_hires_scalef is not None, (
"tissue_hires_scalef is required when an the hires image is present"
dataset_data = adata.uns[SPATIAL][dataset_id]
keys = set(dataset_data.keys())
scalefactors_raw = dataset_data.get(SCALEFACTORS, {}) if isinstance(dataset_data, Mapping) else {}
scalefactors = scalefactors_raw if isinstance(scalefactors_raw, Mapping) else {}

if SPOT_DIAMETER_FULLRES in scalefactors:
spot_diameter_fullres_list.append(scalefactors[SPOT_DIAMETER_FULLRES])

image_data = dataset_data.get(IMAGES, {}) if IMAGES in keys and isinstance(dataset_data, Mapping) else {}
if not isinstance(image_data, Mapping):
image_data = {}
for image_key, image_value in image_data.items():
if image_key not in (HIRES, LOWRES):
logger.warning(
"Found non-standard image key '%s' in dataset '%s' - attempting to parse.",
image_key,
dataset_id,
)
# prefer scalefactors keyed by the image name, fall back to legacy hires/lowres names,
# then to tissue_<image_key>_scalef, finally default to 1.0.
scalefactor = None
scalefactor_source = None
if image_key in scalefactors:
scalefactor = scalefactors[image_key]
scalefactor_source = image_key
elif f"tissue_{image_key}_scalef" in scalefactors:
scalefactor = scalefactors[f"tissue_{image_key}_scalef"]
scalefactor_source = f"tissue_{image_key}_scalef"
elif image_key == HIRES and TISSUE_HIRES_SCALEF in scalefactors:
scalefactor = scalefactors[TISSUE_HIRES_SCALEF]
scalefactor_source = TISSUE_HIRES_SCALEF
elif image_key == LOWRES and TISSUE_LOWRES_SCALEF in scalefactors:
scalefactor = scalefactors[TISSUE_LOWRES_SCALEF]
scalefactor_source = TISSUE_LOWRES_SCALEF

if scalefactor is None:
scalefactor = DEFAULT_SCALE_FACTOR
logger.warning(
"Scale factor missing for image '%s' in dataset '%s'; defaulting to %s",
image_key,
dataset_id,
DEFAULT_SCALE_FACTOR,
)
else:
logger.warning(
"Using scalefactor '%s' for image '%s' in dataset '%s'",
scalefactor_source,
image_key,
dataset_id,
)

transform_name = f"{dataset_id}_{image_key}"
image_name = f"{dataset_id}_{image_key}"
images[image_name] = Image2DModel.parse(
image_value, dims=("y", "x", "c"), transformations={transform_name: Identity()}
)
hires_image = Image2DModel.parse(
hires, dims=("y", "x", "c"), transformations={f"{dataset_id}_downscaled_hires": Identity()}
)
images[f"{dataset_id}_hires_image"] = hires_image

# prepare the transformation to the hires image for the shapes
scale_hires = Scale([tissue_hires_scalef, tissue_hires_scalef], axes=("x", "y"))
shapes_transformations[f"{dataset_id}_downscaled_hires"] = scale_hires
if lowres is not None:
# prepare the lowres image
assert tissue_lowres_scalef is not None, (
"tissue_lowres_scalef is required when an the lowres image is present"
)
lowres_image = Image2DModel.parse(
lowres, dims=("y", "x", "c"), transformations={f"{dataset_id}_downscaled_lowres": Identity()}
)
images[f"{dataset_id}_lowres_image"] = lowres_image

# prepare the transformation to the lowres image for the shapes
scale_lowres = Scale([tissue_lowres_scalef, tissue_lowres_scalef], axes=("x", "y"))
shapes_transformations[f"{dataset_id}_downscaled_lowres"] = scale_lowres
shapes_transformations[transform_name] = Scale([scalefactor, scalefactor], axes=("x", "y"))

# validate the spot_diameter_fullres value
if len(spot_diameter_fullres_list) > 0:
d = np.array(spot_diameter_fullres_list)
if not np.allclose(d, d[0]):
warnings.warn(
logger.warning(
"spot_diameter_fullres is not constant across datasets. Using the average value.",
UserWarning,
stacklevel=2,
)
spot_diameter_fullres = d.mean()
else:
spot_diameter_fullres = d[0]
else:
warnings.warn(
logger.warning(
f"spot_diameter_fullres is not present. Using {SPOT_DIAMETER_FULLRES_DEFAULT} as default value.",
UserWarning,
stacklevel=2,
)
spot_diameter_fullres = SPOT_DIAMETER_FULLRES_DEFAULT

Expand Down
Loading