spatialdata-io 0.1.4__tar.gz → 0.1.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/CHANGELOG.md +12 -1
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/PKG-INFO +2 -2
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/_version.py +2 -2
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/pyproject.toml +1 -1
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/_constants/_constants.py +3 -3
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/_utils/_utils.py +2 -2
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/codex.py +1 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/cosmx.py +1 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/mcmicro.py +6 -4
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/merscope.py +2 -1
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/steinbock.py +1 -1
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/visium.py +16 -9
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/visium_hd.py +53 -18
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/xenium.py +41 -19
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/.bumpversion.cfg +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/.codecov.yaml +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/.editorconfig +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/.flake8 +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/.github/codecov.yml +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/.github/workflows/build.yaml +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/.github/workflows/prepare_test_data.yaml +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/.github/workflows/release.yaml +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/.github/workflows/test.yaml +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/.gitignore +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/.mypy.ini +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/.pre-commit-config.yaml +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/.readthedocs.yaml +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/LICENSE +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/README.md +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/docs/Makefile +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/docs/_static/.gitkeep +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/docs/_templates/.gitkeep +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/docs/_templates/autosummary/class.rst +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/docs/api.md +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/docs/changelog.md +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/docs/conf.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/docs/contributing.md +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/docs/extensions/.gitkeep +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/docs/extensions/typed_returns.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/docs/index.md +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/docs/make.bat +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/docs/references.bib +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/docs/references.md +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/docs/template_usage.md +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/__init__.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/_constants/__init__.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/_constants/_enum.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/_docs.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/_utils.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/converters/__init__.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/converters/legacy_anndata.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/experimental/__init__.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/__init__.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/_utils/__init__.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/_utils/_read_10x_h5.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/curio.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/dbit.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/iss.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/seqfish.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/stereoseq.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/tests/__init__.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/tests/converters/__init__.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/tests/converters/test_legacy_anndata.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/tests/test_basic.py +0 -0
- {spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/tests/test_xenium.py +0 -0
|
@@ -8,7 +8,18 @@ and this project adheres to [Semantic Versioning][].
|
|
|
8
8
|
[keep a changelog]: https://keepachangelog.com/en/1.0.0/
|
|
9
9
|
[semantic versioning]: https://semver.org/spec/v2.0.0.html
|
|
10
10
|
|
|
11
|
-
## [0.1.
|
|
11
|
+
## [0.1.5] - 2024-09-25
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- (Xenium) added `dims` parameter for more control in `xenium_aligned_image()`
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Passing `rgb=None` to image model parser for both visium and visiumhd, leading to 3-4 channel images being
|
|
20
|
+
interpreted as RGB(A)
|
|
21
|
+
- Fix header bug Visium data #200
|
|
22
|
+
- (Visium HD) Fix path parsing when images are missing #204 #206
|
|
12
23
|
|
|
13
24
|
## [0.1.4] - 2024-08-07
|
|
14
25
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: spatialdata-io
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: SpatialData IO for common techs
|
|
5
5
|
Project-URL: Documentation, https://spatialdata-io.readthedocs.io/
|
|
6
6
|
Project-URL: Source, https://github.com/scverse/spatialdata-io
|
|
@@ -48,7 +48,7 @@ Requires-Dist: pyarrow
|
|
|
48
48
|
Requires-Dist: readfcs
|
|
49
49
|
Requires-Dist: scanpy
|
|
50
50
|
Requires-Dist: scikit-image
|
|
51
|
-
Requires-Dist: spatialdata>=0.2.
|
|
51
|
+
Requires-Dist: spatialdata>=0.2.2
|
|
52
52
|
Requires-Dist: tifffile>=2023.8.12
|
|
53
53
|
Provides-Extra: dev
|
|
54
54
|
Requires-Dist: bump2version; extra == 'dev'
|
|
@@ -353,9 +353,9 @@ class VisiumHDKeys(ModeEnum):
|
|
|
353
353
|
TISSUE_POSITIONS_FILE = "tissue_positions.parquet"
|
|
354
354
|
|
|
355
355
|
# images
|
|
356
|
-
IMAGE_HIRES_FILE = "
|
|
357
|
-
IMAGE_LOWRES_FILE = "
|
|
358
|
-
IMAGE_CYTASSIST = "
|
|
356
|
+
IMAGE_HIRES_FILE = "tissue_hires_image.png"
|
|
357
|
+
IMAGE_LOWRES_FILE = "tissue_lowres_image.png"
|
|
358
|
+
IMAGE_CYTASSIST = "cytassist_image.tiff"
|
|
359
359
|
|
|
360
360
|
# scalefactors
|
|
361
361
|
SCALEFACTORS_FILE = "scalefactors_json.json"
|
|
@@ -100,7 +100,7 @@ def mcmicro(
|
|
|
100
100
|
assert len(samples) == 1
|
|
101
101
|
data = imread(samples[0], **imread_kwargs)
|
|
102
102
|
# , scale_factors=[2, 2]
|
|
103
|
-
data = Image2DModel.parse(data, transformations=_get_transformation(), **image_models_kwargs)
|
|
103
|
+
data = Image2DModel.parse(data, transformations=_get_transformation(), rgb=None, **image_models_kwargs)
|
|
104
104
|
images["tma_map"] = data
|
|
105
105
|
|
|
106
106
|
image_dir = path / McmicroKeys.IMAGES_DIR_TMA
|
|
@@ -116,7 +116,7 @@ def mcmicro(
|
|
|
116
116
|
image_id = core_id
|
|
117
117
|
|
|
118
118
|
data = imread(sample, **imread_kwargs)
|
|
119
|
-
data = Image2DModel.parse(data, c_coords=marker_names, **image_models_kwargs)
|
|
119
|
+
data = Image2DModel.parse(data, c_coords=marker_names, rgb=None, **image_models_kwargs)
|
|
120
120
|
transformations = _get_transformation(
|
|
121
121
|
tma=int(core_id) if tma else None, tma_centroids=tma_centroids, raster_data=data
|
|
122
122
|
)
|
|
@@ -131,7 +131,9 @@ def mcmicro(
|
|
|
131
131
|
raw_name = raw_image.with_name(raw_image.stem).with_suffix("").stem
|
|
132
132
|
|
|
133
133
|
data = imread(raw_image, **imread_kwargs)
|
|
134
|
-
images[raw_name] = Image2DModel.parse(
|
|
134
|
+
images[raw_name] = Image2DModel.parse(
|
|
135
|
+
data, transformations={raw_name: Identity()}, rgb=None, **image_models_kwargs
|
|
136
|
+
)
|
|
135
137
|
|
|
136
138
|
illumination_dir = path / McmicroKeys.ILLUMINATION_DIR
|
|
137
139
|
if illumination_dir.exists():
|
|
@@ -143,7 +145,7 @@ def mcmicro(
|
|
|
143
145
|
|
|
144
146
|
data = imread(illumination_image, **imread_kwargs)
|
|
145
147
|
images[illumination_name] = Image2DModel.parse(
|
|
146
|
-
data, transformations={raw_name: Identity()}, **image_models_kwargs
|
|
148
|
+
data, transformations={raw_name: Identity()}, rgb=None, **image_models_kwargs
|
|
147
149
|
)
|
|
148
150
|
|
|
149
151
|
samples_labels = list((path / McmicroKeys.LABELS_DIR).glob("*/*" + McmicroKeys.IMAGE_SUFFIX))
|
|
@@ -273,7 +273,7 @@ def _rioxarray_load_merscope(
|
|
|
273
273
|
dim="c",
|
|
274
274
|
)
|
|
275
275
|
|
|
276
|
-
return Image2DModel.parse(im, c_coords=stainings, **image_models_kwargs)
|
|
276
|
+
return Image2DModel.parse(im, c_coords=stainings, rgb=None, **image_models_kwargs)
|
|
277
277
|
|
|
278
278
|
|
|
279
279
|
def _dask_image_load_merscope(
|
|
@@ -292,6 +292,7 @@ def _dask_image_load_merscope(
|
|
|
292
292
|
im,
|
|
293
293
|
dims=("c", "y", "x"),
|
|
294
294
|
c_coords=stainings,
|
|
295
|
+
rgb=None,
|
|
295
296
|
**image_models_kwargs,
|
|
296
297
|
)
|
|
297
298
|
|
|
@@ -97,7 +97,7 @@ def _get_images(
|
|
|
97
97
|
image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
|
|
98
98
|
) -> Union[SpatialImage, MultiscaleSpatialImage]:
|
|
99
99
|
image = imread(path / SteinbockKeys.IMAGES_DIR / f"{sample}{SteinbockKeys.IMAGE_SUFFIX}", **imread_kwargs)
|
|
100
|
-
return Image2DModel.parse(data=image, transformations={sample: Identity()}, **image_models_kwargs)
|
|
100
|
+
return Image2DModel.parse(data=image, transformations={sample: Identity()}, rgb=None, **image_models_kwargs)
|
|
101
101
|
|
|
102
102
|
|
|
103
103
|
def _get_labels(
|
|
@@ -4,7 +4,6 @@ import json
|
|
|
4
4
|
import os
|
|
5
5
|
import re
|
|
6
6
|
from collections.abc import Mapping
|
|
7
|
-
from functools import partial
|
|
8
7
|
from pathlib import Path
|
|
9
8
|
from types import MappingProxyType
|
|
10
9
|
from typing import Any
|
|
@@ -138,7 +137,6 @@ def visium(
|
|
|
138
137
|
if (path / "spatial" / VisiumKeys.SPOTS_FILE_1).exists() or (
|
|
139
138
|
tissue_positions_file is not None and str(VisiumKeys.SPOTS_FILE_1) in str(tissue_positions_file)
|
|
140
139
|
):
|
|
141
|
-
read_coords = partial(pd.read_csv, header=None, index_col=0)
|
|
142
140
|
tissue_positions_file = (
|
|
143
141
|
path / "spatial" / VisiumKeys.SPOTS_FILE_1
|
|
144
142
|
if tissue_positions_file is None
|
|
@@ -147,7 +145,6 @@ def visium(
|
|
|
147
145
|
elif (path / "spatial" / VisiumKeys.SPOTS_FILE_2).exists() or (
|
|
148
146
|
tissue_positions_file is not None and str(VisiumKeys.SPOTS_FILE_2) in str(tissue_positions_file)
|
|
149
147
|
):
|
|
150
|
-
read_coords = partial(pd.read_csv, header=0, index_col=0)
|
|
151
148
|
tissue_positions_file = (
|
|
152
149
|
path / "spatial" / VisiumKeys.SPOTS_FILE_2
|
|
153
150
|
if tissue_positions_file is None
|
|
@@ -155,11 +152,18 @@ def visium(
|
|
|
155
152
|
)
|
|
156
153
|
else:
|
|
157
154
|
raise ValueError(f"Cannot find `tissue_positions` file in `{path}`.")
|
|
158
|
-
coords =
|
|
155
|
+
coords = pd.read_csv(tissue_positions_file, header=None, index_col=0)
|
|
159
156
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
157
|
+
if tissue_positions_file.name == VisiumKeys.SPOTS_FILE_1:
|
|
158
|
+
# spaceranger_version < 2.0.0 but header detected: https://github.com/scverse/spatialdata-io/issues/146
|
|
159
|
+
if "in_tissue" in coords.iloc[0].tolist():
|
|
160
|
+
coords = pd.read_csv(tissue_positions_file, header=0, index_col=0)
|
|
161
|
+
else:
|
|
162
|
+
assert "in_tissue" not in coords.columns
|
|
163
|
+
coords.columns = ["in_tissue", "array_row", "array_col", "pxl_row_in_fullres", "pxl_col_in_fullres"]
|
|
164
|
+
else:
|
|
165
|
+
assert tissue_positions_file.name == VisiumKeys.SPOTS_FILE_2
|
|
166
|
+
coords = pd.read_csv(tissue_positions_file, header=0, index_col=0)
|
|
163
167
|
|
|
164
168
|
adata.obs = pd.merge(adata.obs, coords, how="left", left_index=True, right_index=True)
|
|
165
169
|
coords = adata.obs[[VisiumKeys.SPOTS_X, VisiumKeys.SPOTS_Y]].values
|
|
@@ -217,6 +221,7 @@ def visium(
|
|
|
217
221
|
full_image,
|
|
218
222
|
scale_factors=[2, 2, 2, 2],
|
|
219
223
|
transformations={"global": transform_original},
|
|
224
|
+
rgb=None,
|
|
220
225
|
**image_models_kwargs,
|
|
221
226
|
)
|
|
222
227
|
else:
|
|
@@ -226,13 +231,15 @@ def visium(
|
|
|
226
231
|
image_hires = imread(path / VisiumKeys.IMAGE_HIRES_FILE, **imread_kwargs).squeeze().transpose(2, 0, 1)
|
|
227
232
|
image_hires = DataArray(image_hires, dims=("c", "y", "x"))
|
|
228
233
|
images[dataset_id + "_hires_image"] = Image2DModel.parse(
|
|
229
|
-
image_hires, transformations={"downscaled_hires": Identity()}
|
|
234
|
+
image_hires, transformations={"downscaled_hires": Identity()}, rgb=None
|
|
230
235
|
)
|
|
231
236
|
if (path / VisiumKeys.IMAGE_LOWRES_FILE).exists():
|
|
232
237
|
image_lowres = imread(path / VisiumKeys.IMAGE_LOWRES_FILE, **imread_kwargs).squeeze().transpose(2, 0, 1)
|
|
233
238
|
image_lowres = DataArray(image_lowres, dims=("c", "y", "x"))
|
|
234
239
|
images[dataset_id + "_lowres_image"] = Image2DModel.parse(
|
|
235
|
-
image_lowres,
|
|
240
|
+
image_lowres,
|
|
241
|
+
transformations={"downscaled_lowres": Identity()},
|
|
242
|
+
rgb=None,
|
|
236
243
|
)
|
|
237
244
|
|
|
238
245
|
return SpatialData(images=images, shapes=shapes, table=table)
|
|
@@ -86,15 +86,14 @@ def visium_hd(
|
|
|
86
86
|
SpatialData object for the Visium HD data.
|
|
87
87
|
"""
|
|
88
88
|
path = Path(path)
|
|
89
|
+
all_files = [file for file in path.rglob("*") if file.is_file()]
|
|
89
90
|
tables = {}
|
|
90
91
|
shapes = {}
|
|
91
92
|
images: dict[str, Any] = {}
|
|
92
93
|
|
|
93
94
|
if dataset_id is None:
|
|
94
95
|
dataset_id = _infer_dataset_id(path)
|
|
95
|
-
|
|
96
|
-
else:
|
|
97
|
-
filename_prefix = ""
|
|
96
|
+
filename_prefix = f"{dataset_id}_"
|
|
98
97
|
|
|
99
98
|
def load_image(path: Path, suffix: str, scale_factors: list[int] | None = None) -> None:
|
|
100
99
|
_load_image(
|
|
@@ -127,8 +126,12 @@ def visium_hd(
|
|
|
127
126
|
]
|
|
128
127
|
)
|
|
129
128
|
|
|
130
|
-
if VisiumHDKeys.BINNED_OUTPUTS in
|
|
131
|
-
|
|
129
|
+
all_path_bins = [path_bin for path_bin in all_files if VisiumHDKeys.BINNED_OUTPUTS in str(path_bin)]
|
|
130
|
+
if len(all_path_bins) != 0:
|
|
131
|
+
path_bins_parts = all_path_bins[
|
|
132
|
+
-1
|
|
133
|
+
].parts # just choosing last one here as users might have tar file which would be first
|
|
134
|
+
path_bins = Path(*path_bins_parts[: path_bins_parts.index(VisiumHDKeys.BINNED_OUTPUTS) + 1])
|
|
132
135
|
else:
|
|
133
136
|
path_bins = path
|
|
134
137
|
all_bin_sizes = _get_bins(path_bins)
|
|
@@ -266,15 +269,26 @@ def visium_hd(
|
|
|
266
269
|
fullres_image_filenames = [
|
|
267
270
|
f for f in os.listdir(path_fullres) if os.path.isfile(os.path.join(path_fullres, f))
|
|
268
271
|
]
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
272
|
+
fullres_image_paths = [path_fullres / image_filename for image_filename in fullres_image_filenames]
|
|
273
|
+
elif list((path_fullres := (path / f"{filename_prefix}tissue_image")).parent.glob(f"{path_fullres.name}.*")):
|
|
274
|
+
fullres_image_paths = list(path_fullres.parent.glob(f"{path_fullres.name}.*"))
|
|
275
|
+
else:
|
|
276
|
+
fullres_image_paths = []
|
|
277
|
+
if len(fullres_image_paths) > 1:
|
|
278
|
+
warnings.warn(
|
|
279
|
+
f"Multiple files found in {path_fullres}, using the first one: {fullres_image_paths[0].stem}. Please"
|
|
280
|
+
" specify the path to the full resolution image manually using the `fullres_image_file` argument.",
|
|
281
|
+
UserWarning,
|
|
282
|
+
stacklevel=2,
|
|
283
|
+
)
|
|
284
|
+
if len(fullres_image_paths) == 0:
|
|
285
|
+
warnings.warn(
|
|
286
|
+
"No full resolution image found. If incorrect, please specify the path in the "
|
|
287
|
+
"`fullres_image_file` parameter when calling the `visium_hd` reader function.",
|
|
288
|
+
UserWarning,
|
|
289
|
+
stacklevel=2,
|
|
290
|
+
)
|
|
291
|
+
fullres_image_file = fullres_image_paths[0] if len(fullres_image_paths) > 0 else None
|
|
278
292
|
|
|
279
293
|
if fullres_image_file is not None:
|
|
280
294
|
load_image(
|
|
@@ -284,8 +298,15 @@ def visium_hd(
|
|
|
284
298
|
)
|
|
285
299
|
|
|
286
300
|
# hires image
|
|
301
|
+
hires_image_path = [path for path in all_files if VisiumHDKeys.IMAGE_HIRES_FILE in str(path)]
|
|
302
|
+
if len(hires_image_path) == 0:
|
|
303
|
+
warnings.warn(
|
|
304
|
+
f"No image path found containing the hires image: {VisiumHDKeys.IMAGE_HIRES_FILE}",
|
|
305
|
+
UserWarning,
|
|
306
|
+
stacklevel=2,
|
|
307
|
+
)
|
|
287
308
|
load_image(
|
|
288
|
-
path=
|
|
309
|
+
path=hires_image_path[0],
|
|
289
310
|
suffix="_hires_image",
|
|
290
311
|
)
|
|
291
312
|
set_transformation(
|
|
@@ -295,8 +316,15 @@ def visium_hd(
|
|
|
295
316
|
)
|
|
296
317
|
|
|
297
318
|
# lowres image
|
|
319
|
+
lowres_image_path = [path for path in all_files if VisiumHDKeys.IMAGE_LOWRES_FILE in str(path)]
|
|
320
|
+
if len(lowres_image_path) == 0:
|
|
321
|
+
warnings.warn(
|
|
322
|
+
f"No image path found containing the lowres image: {VisiumHDKeys.IMAGE_LOWRES_FILE}",
|
|
323
|
+
UserWarning,
|
|
324
|
+
stacklevel=2,
|
|
325
|
+
)
|
|
298
326
|
load_image(
|
|
299
|
-
path=
|
|
327
|
+
path=lowres_image_path[0],
|
|
300
328
|
suffix="_lowres_image",
|
|
301
329
|
)
|
|
302
330
|
set_transformation(
|
|
@@ -306,9 +334,16 @@ def visium_hd(
|
|
|
306
334
|
)
|
|
307
335
|
|
|
308
336
|
# cytassist image
|
|
337
|
+
cytassist_path = [path for path in all_files if VisiumHDKeys.IMAGE_CYTASSIST in str(path)]
|
|
338
|
+
if len(cytassist_path) == 0:
|
|
339
|
+
warnings.warn(
|
|
340
|
+
f"No image path found containing the cytassist image: {VisiumHDKeys.IMAGE_CYTASSIST}",
|
|
341
|
+
UserWarning,
|
|
342
|
+
stacklevel=2,
|
|
343
|
+
)
|
|
309
344
|
if load_all_images:
|
|
310
345
|
load_image(
|
|
311
|
-
path=
|
|
346
|
+
path=cytassist_path[0],
|
|
312
347
|
suffix="_cytassist_image",
|
|
313
348
|
)
|
|
314
349
|
image = images[dataset_id + "_cytassist_image"]
|
|
@@ -352,7 +387,7 @@ def _load_image(
|
|
|
352
387
|
# dask_image doesn't recognize .btf automatically and imageio v3 throws error due to pixel limit -> use imageio v2
|
|
353
388
|
data = imread2(path, **imread_kwargs).squeeze().transpose(2, 0, 1)
|
|
354
389
|
image = DataArray(data, dims=("c", "y", "x"))
|
|
355
|
-
parsed = Image2DModel.parse(image, scale_factors=scale_factors, **image_models_kwargs)
|
|
390
|
+
parsed = Image2DModel.parse(image, scale_factors=scale_factors, rgb=None, **image_models_kwargs)
|
|
356
391
|
images[dataset_id + suffix] = parsed
|
|
357
392
|
else:
|
|
358
393
|
warnings.warn(f"File {path} does not exist, skipping it.", UserWarning, stacklevel=2)
|
|
@@ -22,12 +22,11 @@ import zarr
|
|
|
22
22
|
from anndata import AnnData
|
|
23
23
|
from dask.dataframe import read_parquet
|
|
24
24
|
from dask_image.imread import imread
|
|
25
|
+
from datatree.datatree import DataTree
|
|
25
26
|
from geopandas import GeoDataFrame
|
|
26
27
|
from joblib import Parallel, delayed
|
|
27
|
-
from multiscale_spatial_image.multiscale_spatial_image import MultiscaleSpatialImage
|
|
28
28
|
from pyarrow import Table
|
|
29
29
|
from shapely import Polygon
|
|
30
|
-
from spatial_image import SpatialImage
|
|
31
30
|
from spatialdata import SpatialData
|
|
32
31
|
from spatialdata._core.query.relational_query import get_element_instances
|
|
33
32
|
from spatialdata._types import ArrayLike
|
|
@@ -39,6 +38,7 @@ from spatialdata.models import (
|
|
|
39
38
|
TableModel,
|
|
40
39
|
)
|
|
41
40
|
from spatialdata.transformations.transformations import Affine, Identity, Scale
|
|
41
|
+
from xarray import DataArray
|
|
42
42
|
|
|
43
43
|
from spatialdata_io._constants._constants import XeniumKeys
|
|
44
44
|
from spatialdata_io._docs import inject_docs
|
|
@@ -111,7 +111,9 @@ def xenium(
|
|
|
111
111
|
morphology_focus
|
|
112
112
|
Whether to read the morphology focus image.
|
|
113
113
|
aligned_images
|
|
114
|
-
Whether to also parse, when available, additional H&E or IF aligned images.
|
|
114
|
+
Whether to also parse, when available, additional H&E or IF aligned images. For more control over the aligned
|
|
115
|
+
images being read, in particular, to specify the axes of the aligned images, please set this parameter to
|
|
116
|
+
`False` and use the `xenium_aligned_image` function directly.
|
|
115
117
|
cells_table
|
|
116
118
|
Whether to read the cell annotations in the `AnnData` table.
|
|
117
119
|
n_jobs
|
|
@@ -513,6 +515,7 @@ def _get_points(path: Path, specs: dict[str, Any]) -> Table:
|
|
|
513
515
|
feature_key=XeniumKeys.FEATURE_NAME,
|
|
514
516
|
instance_key=XeniumKeys.CELL_ID,
|
|
515
517
|
transformations={"global": transform},
|
|
518
|
+
sort=True,
|
|
516
519
|
)
|
|
517
520
|
return points
|
|
518
521
|
|
|
@@ -550,7 +553,7 @@ def _get_images(
|
|
|
550
553
|
file: str,
|
|
551
554
|
imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
|
|
552
555
|
image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
|
|
553
|
-
) ->
|
|
556
|
+
) -> DataArray | DataTree:
|
|
554
557
|
image = imread(path / file, **imread_kwargs)
|
|
555
558
|
if "c_coords" in image_models_kwargs and "dummy" in image_models_kwargs["c_coords"]:
|
|
556
559
|
# Napari currently interprets 4 channel images as RGB; a series of PRs to fix this is almost ready but they will
|
|
@@ -559,7 +562,7 @@ def _get_images(
|
|
|
559
562
|
# let's add a dummy channel as a temporary workaround.
|
|
560
563
|
image = da.concatenate([image, da.zeros_like(image[0:1])], axis=0)
|
|
561
564
|
return Image2DModel.parse(
|
|
562
|
-
image, transformations={"global": Identity()}, dims=("c", "y", "x"), **image_models_kwargs
|
|
565
|
+
image, transformations={"global": Identity()}, dims=("c", "y", "x"), rgb=None, **image_models_kwargs
|
|
563
566
|
)
|
|
564
567
|
|
|
565
568
|
|
|
@@ -567,7 +570,7 @@ def _add_aligned_images(
|
|
|
567
570
|
path: Path,
|
|
568
571
|
imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
|
|
569
572
|
image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
|
|
570
|
-
) -> dict[str,
|
|
573
|
+
) -> dict[str, DataTree]:
|
|
571
574
|
"""Discover and parse aligned images."""
|
|
572
575
|
images = {}
|
|
573
576
|
ome_tif_files = list(path.glob("*.ome.tif"))
|
|
@@ -598,7 +601,8 @@ def xenium_aligned_image(
|
|
|
598
601
|
alignment_file: str | Path | None,
|
|
599
602
|
imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
|
|
600
603
|
image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
|
|
601
|
-
|
|
604
|
+
dims: tuple[str, ...] | None = None,
|
|
605
|
+
) -> DataTree:
|
|
602
606
|
"""
|
|
603
607
|
Read an image aligned to a Xenium dataset, with an optional alignment file.
|
|
604
608
|
|
|
@@ -610,6 +614,12 @@ def xenium_aligned_image(
|
|
|
610
614
|
Path to the alignment file, if not passed it is assumed that the image is aligned.
|
|
611
615
|
image_models_kwargs
|
|
612
616
|
Keyword arguments to pass to the image models.
|
|
617
|
+
dims
|
|
618
|
+
Dimensions of the image (tuple of axes names); valid strings are "c", "x" and "y". If not passed, the function
|
|
619
|
+
will try to infer the dimensions from the image shape. Please use this argument when the default behavior fails.
|
|
620
|
+
Example: for an image with shape (1, y, 1, x, 3), use dims=("anystring", "y", "dummy", "x", "c"). Values that
|
|
621
|
+
are not "c", "x" or "y" are considered dummy dimensions and will be squeezed (the data must have len 1 for
|
|
622
|
+
those axes).
|
|
613
623
|
|
|
614
624
|
Returns
|
|
615
625
|
-------
|
|
@@ -627,19 +637,28 @@ def xenium_aligned_image(
|
|
|
627
637
|
# In fact, it could be that the len(image.shape) == 4 has actually dimes (1, x, y, c) and not (1, y, x, c). This is
|
|
628
638
|
# not a problem because the transformation is constructed to be consistent, but if is the case, the data orientation
|
|
629
639
|
# would be transposed compared to the original image, not ideal.
|
|
630
|
-
if
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
640
|
+
if dims is None:
|
|
641
|
+
if len(image.shape) == 4:
|
|
642
|
+
assert image.shape[0] == 1
|
|
643
|
+
assert image.shape[-1] == 3
|
|
644
|
+
image = image.squeeze(0)
|
|
645
|
+
dims = ("y", "x", "c")
|
|
646
|
+
else:
|
|
647
|
+
assert len(image.shape) == 3
|
|
648
|
+
assert image.shape[0] in [3, 4]
|
|
649
|
+
if image.shape[0] == 4:
|
|
650
|
+
# as explained before in _get_images(), we need to add a dummy channel until we support 4-channel images as
|
|
651
|
+
# non-RGBA images in napari
|
|
652
|
+
image = da.concatenate([image, da.zeros_like(image[0:1])], axis=0)
|
|
653
|
+
dims = ("c", "y", "x")
|
|
635
654
|
else:
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
655
|
+
logging.info(f"Image has shape {image.shape}, parsing with dims={dims}.")
|
|
656
|
+
image = DataArray(image, dims=dims)
|
|
657
|
+
# squeeze spurious dimensions away
|
|
658
|
+
to_squeeze = [dim for dim in dims if dim not in ["c", "x", "y"]]
|
|
659
|
+
dims = tuple(dim for dim in dims if dim in ["c", "x", "y"])
|
|
660
|
+
for dim in to_squeeze:
|
|
661
|
+
image = image.squeeze(dim)
|
|
643
662
|
|
|
644
663
|
if alignment_file is None:
|
|
645
664
|
transformation = Identity()
|
|
@@ -762,3 +781,6 @@ def prefix_suffix_uint32_from_cell_id_str(cell_id_str: ArrayLike) -> tuple[Array
|
|
|
762
781
|
cell_id_prefix = [int(x, 16) for x in cell_id_prefix_hex]
|
|
763
782
|
|
|
764
783
|
return np.array(cell_id_prefix, dtype=np.uint32), np.array(dataset_suffix_int)
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
##
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/converters/legacy_anndata.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spatialdata_io-0.1.4 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/_utils/_read_10x_h5.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|