spatialdata-io 0.1.3.post0__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.3.post0 → spatialdata_io-0.1.5}/.pre-commit-config.yaml +2 -2
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/CHANGELOG.md +20 -1
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/PKG-INFO +21 -13
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/README.md +18 -11
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/_version.py +2 -2
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/pyproject.toml +2 -1
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/_constants/_constants.py +3 -3
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/converters/legacy_anndata.py +2 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/_utils/_utils.py +2 -2
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/codex.py +1 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/cosmx.py +13 -11
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/mcmicro.py +6 -4
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/merscope.py +2 -1
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/steinbock.py +1 -1
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/visium.py +16 -9
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/visium_hd.py +74 -26
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/xenium.py +78 -29
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/tests/test_xenium.py +2 -2
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/.bumpversion.cfg +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/.codecov.yaml +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/.editorconfig +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/.flake8 +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/.github/codecov.yml +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/.github/workflows/build.yaml +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/.github/workflows/prepare_test_data.yaml +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/.github/workflows/release.yaml +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/.github/workflows/test.yaml +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/.gitignore +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/.mypy.ini +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/.readthedocs.yaml +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/LICENSE +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/docs/Makefile +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/docs/_static/.gitkeep +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/docs/_templates/.gitkeep +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/docs/_templates/autosummary/class.rst +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/docs/api.md +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/docs/changelog.md +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/docs/conf.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/docs/contributing.md +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/docs/extensions/.gitkeep +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/docs/extensions/typed_returns.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/docs/index.md +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/docs/make.bat +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/docs/references.bib +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/docs/references.md +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/docs/template_usage.md +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/__init__.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/_constants/__init__.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/_constants/_enum.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/_docs.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/_utils.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/converters/__init__.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/experimental/__init__.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/__init__.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/_utils/__init__.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/_utils/_read_10x_h5.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/curio.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/dbit.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/iss.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/seqfish.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/stereoseq.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/tests/__init__.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/tests/converters/__init__.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/tests/converters/test_legacy_anndata.py +0 -0
- {spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/tests/test_basic.py +0 -0
|
@@ -23,7 +23,7 @@ repos:
|
|
|
23
23
|
hooks:
|
|
24
24
|
- id: isort
|
|
25
25
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
26
|
-
rev: v1.
|
|
26
|
+
rev: v1.11.0
|
|
27
27
|
hooks:
|
|
28
28
|
- id: mypy
|
|
29
29
|
additional_dependencies: [numpy, types-PyYAML]
|
|
@@ -70,7 +70,7 @@ repos:
|
|
|
70
70
|
- flake8-bugbear
|
|
71
71
|
- flake8-blind-except
|
|
72
72
|
- repo: https://github.com/asottile/pyupgrade
|
|
73
|
-
rev: v3.
|
|
73
|
+
rev: v3.17.0
|
|
74
74
|
hooks:
|
|
75
75
|
- id: pyupgrade
|
|
76
76
|
args: [--py3-plus, --py39-plus, --keep-runtime-typing]
|
|
@@ -8,7 +8,26 @@ 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
|
|
23
|
+
|
|
24
|
+
## [0.1.4] - 2024-08-07
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- (Xenium) changed default target of table to labels; radii of circles computed from cells, not nuclei #179
|
|
29
|
+
- (Visium HD) changed default geometry to squares from circles for the bins; added parameter to choose #183
|
|
30
|
+
- (CosMx) dropping points element with zero-length from the cosmx reader #191
|
|
12
31
|
|
|
13
32
|
## [0.1.3] - 2024-07-03
|
|
14
33
|
|
|
@@ -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,8 @@ 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
|
+
Requires-Dist: tifffile>=2023.8.12
|
|
52
53
|
Provides-Extra: dev
|
|
53
54
|
Requires-Dist: bump2version; extra == 'dev'
|
|
54
55
|
Requires-Dist: pre-commit; extra == 'dev'
|
|
@@ -82,17 +83,20 @@ Description-Content-Type: text/markdown
|
|
|
82
83
|
|
|
83
84
|
This package contains reader functions to load common spatial omics formats into SpatialData. Currently, we provide support for:
|
|
84
85
|
|
|
85
|
-
- 10x Genomics Visium
|
|
86
|
-
- 10x Genomics Visium HD
|
|
87
|
-
- 10x Genomics Xenium
|
|
88
|
-
- Akoya PhenoCycler (formerly CODEX)
|
|
89
|
-
- Curio Seeker
|
|
86
|
+
- 10x Genomics Visium®
|
|
87
|
+
- 10x Genomics Visium HD®
|
|
88
|
+
- 10x Genomics Xenium®
|
|
89
|
+
- Akoya PhenoCycler® (formerly CODEX®)
|
|
90
|
+
- Curio Seeker®
|
|
90
91
|
- DBiT-seq
|
|
91
92
|
- MCMICRO (output data)
|
|
92
|
-
- NanoString CosMx
|
|
93
|
+
- NanoString CosMx®
|
|
94
|
+
- Spatial Genomics GenePS® (seqFISH)
|
|
93
95
|
- Steinbock (output data)
|
|
94
|
-
- STOmics Stereo-seq
|
|
95
|
-
- Vizgen MERSCOPE (MERFISH)
|
|
96
|
+
- STOmics Stereo-seq®
|
|
97
|
+
- Vizgen MERSCOPE® (MERFISH)
|
|
98
|
+
|
|
99
|
+
Note: all mentioned technologies are registered trademarks of their respective companies.
|
|
96
100
|
|
|
97
101
|
## Getting started
|
|
98
102
|
|
|
@@ -129,9 +133,13 @@ If you found a bug, please use the [issue tracker][issue-tracker].
|
|
|
129
133
|
Technologies that can be read into `SpatialData` objects using third-party libraries:
|
|
130
134
|
|
|
131
135
|
- METASPACE (MALDI, ...): [metaspace-converter](https://github.com/metaspace2020/metaspace-converter)
|
|
132
|
-
- PhenoCycler
|
|
133
|
-
- MACSima
|
|
134
|
-
- Hyperion (Imaging Mass Cytometry): [SOPA](https://github.com/gustaveroussy/sopa)
|
|
136
|
+
- PhenoCycler®: [SOPA](https://github.com/gustaveroussy/sopa)
|
|
137
|
+
- MACSima®: [SOPA](https://github.com/gustaveroussy/sopa)
|
|
138
|
+
- Hyperion® (Imaging Mass Cytometry): [SOPA](https://github.com/gustaveroussy/sopa)
|
|
139
|
+
|
|
140
|
+
## Disclaimer
|
|
141
|
+
|
|
142
|
+
This library is community maintained and is not officially endorsed by the aforementioned spatial technology companies. As such, we cannot offer any warranty of the correctness of the representation. Furthermore, we cannot ensure the correctness of the readers for every data version as the technologies evolve and update their formats. If you find a bug or notice a misrepresentation of the data please report it via our [Bug Tracking System](https://github.com/scverse/spatialdata-io/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen) so that it can be addressed either by the maintainers of this library or by the community.
|
|
135
143
|
|
|
136
144
|
## Citation
|
|
137
145
|
|
|
@@ -12,17 +12,20 @@
|
|
|
12
12
|
|
|
13
13
|
This package contains reader functions to load common spatial omics formats into SpatialData. Currently, we provide support for:
|
|
14
14
|
|
|
15
|
-
- 10x Genomics Visium
|
|
16
|
-
- 10x Genomics Visium HD
|
|
17
|
-
- 10x Genomics Xenium
|
|
18
|
-
- Akoya PhenoCycler (formerly CODEX)
|
|
19
|
-
- Curio Seeker
|
|
15
|
+
- 10x Genomics Visium®
|
|
16
|
+
- 10x Genomics Visium HD®
|
|
17
|
+
- 10x Genomics Xenium®
|
|
18
|
+
- Akoya PhenoCycler® (formerly CODEX®)
|
|
19
|
+
- Curio Seeker®
|
|
20
20
|
- DBiT-seq
|
|
21
21
|
- MCMICRO (output data)
|
|
22
|
-
- NanoString CosMx
|
|
22
|
+
- NanoString CosMx®
|
|
23
|
+
- Spatial Genomics GenePS® (seqFISH)
|
|
23
24
|
- Steinbock (output data)
|
|
24
|
-
- STOmics Stereo-seq
|
|
25
|
-
- Vizgen MERSCOPE (MERFISH)
|
|
25
|
+
- STOmics Stereo-seq®
|
|
26
|
+
- Vizgen MERSCOPE® (MERFISH)
|
|
27
|
+
|
|
28
|
+
Note: all mentioned technologies are registered trademarks of their respective companies.
|
|
26
29
|
|
|
27
30
|
## Getting started
|
|
28
31
|
|
|
@@ -59,9 +62,13 @@ If you found a bug, please use the [issue tracker][issue-tracker].
|
|
|
59
62
|
Technologies that can be read into `SpatialData` objects using third-party libraries:
|
|
60
63
|
|
|
61
64
|
- METASPACE (MALDI, ...): [metaspace-converter](https://github.com/metaspace2020/metaspace-converter)
|
|
62
|
-
- PhenoCycler
|
|
63
|
-
- MACSima
|
|
64
|
-
- Hyperion (Imaging Mass Cytometry): [SOPA](https://github.com/gustaveroussy/sopa)
|
|
65
|
+
- PhenoCycler®: [SOPA](https://github.com/gustaveroussy/sopa)
|
|
66
|
+
- MACSima®: [SOPA](https://github.com/gustaveroussy/sopa)
|
|
67
|
+
- Hyperion® (Imaging Mass Cytometry): [SOPA](https://github.com/gustaveroussy/sopa)
|
|
68
|
+
|
|
69
|
+
## Disclaimer
|
|
70
|
+
|
|
71
|
+
This library is community maintained and is not officially endorsed by the aforementioned spatial technology companies. As such, we cannot offer any warranty of the correctness of the representation. Furthermore, we cannot ensure the correctness of the readers for every data version as the technologies evolve and update their formats. If you find a bug or notice a misrepresentation of the data please report it via our [Bug Tracking System](https://github.com/scverse/spatialdata-io/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen) so that it can be addressed either by the maintainers of this library or by the community.
|
|
65
72
|
|
|
66
73
|
## Citation
|
|
67
74
|
|
|
@@ -25,7 +25,7 @@ dependencies = [
|
|
|
25
25
|
"anndata",
|
|
26
26
|
"numpy",
|
|
27
27
|
"scanpy",
|
|
28
|
-
"spatialdata>=0.2.
|
|
28
|
+
"spatialdata>=0.2.2",
|
|
29
29
|
"scikit-image",
|
|
30
30
|
"h5py",
|
|
31
31
|
"joblib",
|
|
@@ -33,6 +33,7 @@ dependencies = [
|
|
|
33
33
|
"dask-image",
|
|
34
34
|
"pyarrow",
|
|
35
35
|
"readfcs",
|
|
36
|
+
"tifffile>=2023.8.12",
|
|
36
37
|
]
|
|
37
38
|
|
|
38
39
|
[project.optional-dependencies]
|
{spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/_constants/_constants.py
RENAMED
|
@@ -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"
|
{spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/converters/legacy_anndata.py
RENAMED
|
@@ -268,6 +268,8 @@ def from_legacy_anndata(adata: AnnData) -> SpatialData:
|
|
|
268
268
|
keys = set(adata.uns[SPATIAL][dataset_id].keys())
|
|
269
269
|
tissue_hires_scalef = None
|
|
270
270
|
tissue_lowres_scalef = None
|
|
271
|
+
hires = None
|
|
272
|
+
lowres = None
|
|
271
273
|
if SCALEFACTORS in keys:
|
|
272
274
|
scalefactors = adata.uns[SPATIAL][dataset_id][SCALEFACTORS]
|
|
273
275
|
if TISSUE_HIRES_SCALEF in scalefactors:
|
|
@@ -200,6 +200,7 @@ def cosmx(
|
|
|
200
200
|
"global_only_image": aff,
|
|
201
201
|
},
|
|
202
202
|
dims=("y", "x", "c"),
|
|
203
|
+
rgb=None,
|
|
203
204
|
**image_models_kwargs,
|
|
204
205
|
)
|
|
205
206
|
images[f"{fov}_image"] = parsed_im
|
|
@@ -271,17 +272,18 @@ def cosmx(
|
|
|
271
272
|
sub_table[CosmxKeys.INSTANCE_KEY] = sub_table[CosmxKeys.INSTANCE_KEY].astype("category")
|
|
272
273
|
# we rename z because we want to treat the data as 2d
|
|
273
274
|
sub_table.rename(columns={"z": "z_raw"}, inplace=True)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
275
|
+
if len(sub_table) > 0:
|
|
276
|
+
points[f"{fov}_points"] = PointsModel.parse(
|
|
277
|
+
sub_table,
|
|
278
|
+
coordinates={"x": CosmxKeys.X_LOCAL_TRANSCRIPT, "y": CosmxKeys.Y_LOCAL_TRANSCRIPT},
|
|
279
|
+
feature_key=CosmxKeys.TARGET_OF_TRANSCRIPT,
|
|
280
|
+
instance_key=CosmxKeys.INSTANCE_KEY,
|
|
281
|
+
transformations={
|
|
282
|
+
fov: Identity(),
|
|
283
|
+
"global": aff,
|
|
284
|
+
"global_only_labels": aff,
|
|
285
|
+
},
|
|
286
|
+
)
|
|
285
287
|
|
|
286
288
|
# TODO: what to do with fov file?
|
|
287
289
|
# if fov_file is not None:
|
|
@@ -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)
|
|
@@ -14,6 +14,7 @@ import numpy as np
|
|
|
14
14
|
import pandas as pd
|
|
15
15
|
import scanpy as sc
|
|
16
16
|
from dask_image.imread import imread
|
|
17
|
+
from geopandas import GeoDataFrame
|
|
17
18
|
from imageio import imread as imread2
|
|
18
19
|
from multiscale_spatial_image import MultiscaleSpatialImage
|
|
19
20
|
from spatial_image import SpatialImage
|
|
@@ -38,6 +39,7 @@ def visium_hd(
|
|
|
38
39
|
dataset_id: str | None = None,
|
|
39
40
|
filtered_counts_file: bool = True,
|
|
40
41
|
bin_size: int | list[int] | None = None,
|
|
42
|
+
bins_as_squares: bool = True,
|
|
41
43
|
fullres_image_file: str | Path | None = None,
|
|
42
44
|
load_all_images: bool = False,
|
|
43
45
|
imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
|
|
@@ -63,6 +65,9 @@ def visium_hd(
|
|
|
63
65
|
bin_size
|
|
64
66
|
When specified, load the data of a specific bin size, or a list of bin sizes. By default, it loads all the
|
|
65
67
|
available bin sizes.
|
|
68
|
+
bins_as_squares
|
|
69
|
+
If `True`, the bins are represented as squares. If `False`, the bins are represented as circles. For a correct
|
|
70
|
+
visualization one should use squares.
|
|
66
71
|
fullres_image_file
|
|
67
72
|
Path to the full-resolution image. By default the image is searched in the ``{vx.MICROSCOPE_IMAGE!r}``
|
|
68
73
|
directory.
|
|
@@ -81,15 +86,14 @@ def visium_hd(
|
|
|
81
86
|
SpatialData object for the Visium HD data.
|
|
82
87
|
"""
|
|
83
88
|
path = Path(path)
|
|
89
|
+
all_files = [file for file in path.rglob("*") if file.is_file()]
|
|
84
90
|
tables = {}
|
|
85
91
|
shapes = {}
|
|
86
92
|
images: dict[str, Any] = {}
|
|
87
93
|
|
|
88
94
|
if dataset_id is None:
|
|
89
95
|
dataset_id = _infer_dataset_id(path)
|
|
90
|
-
|
|
91
|
-
else:
|
|
92
|
-
filename_prefix = ""
|
|
96
|
+
filename_prefix = f"{dataset_id}_"
|
|
93
97
|
|
|
94
98
|
def load_image(path: Path, suffix: str, scale_factors: list[int] | None = None) -> None:
|
|
95
99
|
_load_image(
|
|
@@ -122,8 +126,12 @@ def visium_hd(
|
|
|
122
126
|
]
|
|
123
127
|
)
|
|
124
128
|
|
|
125
|
-
if VisiumHDKeys.BINNED_OUTPUTS in
|
|
126
|
-
|
|
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])
|
|
127
135
|
else:
|
|
128
136
|
path_bins = path
|
|
129
137
|
all_bin_sizes = _get_bins(path_bins)
|
|
@@ -219,19 +227,27 @@ def visium_hd(
|
|
|
219
227
|
axes=("x", "y"),
|
|
220
228
|
)
|
|
221
229
|
# parse shapes
|
|
230
|
+
shapes_name = dataset_id + "_" + bin_size_str
|
|
231
|
+
radius = scalefactors[VisiumHDKeys.SCALEFACTORS_SPOT_DIAMETER_FULLRES] / 2.0
|
|
232
|
+
transformations = {
|
|
233
|
+
"global": transform_original,
|
|
234
|
+
"downscaled_hires": transform_hires,
|
|
235
|
+
"downscaled_lowres": transform_lowres,
|
|
236
|
+
}
|
|
222
237
|
circles = ShapesModel.parse(
|
|
223
238
|
adata.obsm["spatial"],
|
|
224
239
|
geometry=0,
|
|
225
|
-
radius=
|
|
240
|
+
radius=radius,
|
|
226
241
|
index=adata.obs[VisiumHDKeys.INSTANCE_KEY].copy(),
|
|
227
|
-
transformations=
|
|
228
|
-
"global": transform_original,
|
|
229
|
-
"downscaled_hires": transform_hires,
|
|
230
|
-
"downscaled_lowres": transform_lowres,
|
|
231
|
-
},
|
|
242
|
+
transformations=transformations,
|
|
232
243
|
)
|
|
233
|
-
|
|
234
|
-
|
|
244
|
+
if not bins_as_squares:
|
|
245
|
+
shapes[shapes_name] = circles
|
|
246
|
+
else:
|
|
247
|
+
squares_series = circles.buffer(radius, cap_style=3)
|
|
248
|
+
shapes[shapes_name] = ShapesModel.parse(
|
|
249
|
+
GeoDataFrame(geometry=squares_series), transformations=transformations
|
|
250
|
+
)
|
|
235
251
|
|
|
236
252
|
# parse table
|
|
237
253
|
adata.obs[VisiumHDKeys.REGION_KEY] = shapes_name
|
|
@@ -253,15 +269,26 @@ def visium_hd(
|
|
|
253
269
|
fullres_image_filenames = [
|
|
254
270
|
f for f in os.listdir(path_fullres) if os.path.isfile(os.path.join(path_fullres, f))
|
|
255
271
|
]
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
|
265
292
|
|
|
266
293
|
if fullres_image_file is not None:
|
|
267
294
|
load_image(
|
|
@@ -271,8 +298,15 @@ def visium_hd(
|
|
|
271
298
|
)
|
|
272
299
|
|
|
273
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
|
+
)
|
|
274
308
|
load_image(
|
|
275
|
-
path=
|
|
309
|
+
path=hires_image_path[0],
|
|
276
310
|
suffix="_hires_image",
|
|
277
311
|
)
|
|
278
312
|
set_transformation(
|
|
@@ -282,8 +316,15 @@ def visium_hd(
|
|
|
282
316
|
)
|
|
283
317
|
|
|
284
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
|
+
)
|
|
285
326
|
load_image(
|
|
286
|
-
path=
|
|
327
|
+
path=lowres_image_path[0],
|
|
287
328
|
suffix="_lowres_image",
|
|
288
329
|
)
|
|
289
330
|
set_transformation(
|
|
@@ -293,9 +334,16 @@ def visium_hd(
|
|
|
293
334
|
)
|
|
294
335
|
|
|
295
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
|
+
)
|
|
296
344
|
if load_all_images:
|
|
297
345
|
load_image(
|
|
298
|
-
path=
|
|
346
|
+
path=cytassist_path[0],
|
|
299
347
|
suffix="_cytassist_image",
|
|
300
348
|
)
|
|
301
349
|
image = images[dataset_id + "_cytassist_image"]
|
|
@@ -339,7 +387,7 @@ def _load_image(
|
|
|
339
387
|
# dask_image doesn't recognize .btf automatically and imageio v3 throws error due to pixel limit -> use imageio v2
|
|
340
388
|
data = imread2(path, **imread_kwargs).squeeze().transpose(2, 0, 1)
|
|
341
389
|
image = DataArray(data, dims=("c", "y", "x"))
|
|
342
|
-
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)
|
|
343
391
|
images[dataset_id + suffix] = parsed
|
|
344
392
|
else:
|
|
345
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
|
|
@@ -56,7 +56,7 @@ def xenium(
|
|
|
56
56
|
*,
|
|
57
57
|
cells_boundaries: bool = True,
|
|
58
58
|
nucleus_boundaries: bool = True,
|
|
59
|
-
cells_as_circles: bool =
|
|
59
|
+
cells_as_circles: bool | None = None,
|
|
60
60
|
cells_labels: bool = True,
|
|
61
61
|
nucleus_labels: bool = True,
|
|
62
62
|
transcripts: bool = True,
|
|
@@ -96,10 +96,8 @@ def xenium(
|
|
|
96
96
|
nucleus_boundaries
|
|
97
97
|
Whether to read nucleus boundaries (polygons).
|
|
98
98
|
cells_as_circles
|
|
99
|
-
Whether to read cells also as circles
|
|
100
|
-
|
|
101
|
-
(the cell boundaries are computed as a maximum expansion of the nuclei location and therefore the
|
|
102
|
-
corresponding circles would show considerable overlap).
|
|
99
|
+
Whether to read cells also as circles (the center and the radius of each circle is computed from the
|
|
100
|
+
corresponding labels cell).
|
|
103
101
|
cells_labels
|
|
104
102
|
Whether to read cell labels (raster). The polygonal version of the cell labels are simplified
|
|
105
103
|
for visualization purposes, and using the raster version is recommended for analysis.
|
|
@@ -113,7 +111,9 @@ def xenium(
|
|
|
113
111
|
morphology_focus
|
|
114
112
|
Whether to read the morphology focus image.
|
|
115
113
|
aligned_images
|
|
116
|
-
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.
|
|
117
117
|
cells_table
|
|
118
118
|
Whether to read the cell annotations in the `AnnData` table.
|
|
119
119
|
n_jobs
|
|
@@ -128,7 +128,34 @@ def xenium(
|
|
|
128
128
|
Returns
|
|
129
129
|
-------
|
|
130
130
|
:class:`spatialdata.SpatialData`
|
|
131
|
+
|
|
132
|
+
Notes
|
|
133
|
+
-----
|
|
134
|
+
Old versions. Until spatialdata-io v0.1.3post0: previously, `cells_as_circles` was `True` by default; the table was associated to the
|
|
135
|
+
circles when `cells_as_circles` was `True`, and the table was associated to the polygons when `cells_as_circles`
|
|
136
|
+
was `False`; the radii of the circles were computed form the nuclei instead of the cells.
|
|
137
|
+
|
|
138
|
+
Performance. You can improve visualization performance (at the cost of accuracy) by setting `cells_as_circles` to `True`.
|
|
139
|
+
|
|
140
|
+
Examples
|
|
141
|
+
--------
|
|
142
|
+
This code shows how to change the annotation target of the table from the cell circles to the cell labels.
|
|
143
|
+
>>> from spatialdata_io import xenium
|
|
144
|
+
>>> sdata = xenium("path/to/raw/data", cells_as_circles=True)
|
|
145
|
+
>>> sdata["table"].obs["region"] = "cell_labels"
|
|
146
|
+
>>> sdata.set_table_annotates_spatialelement(
|
|
147
|
+
... table_name="table", region="cell_labels", region_key="region", instance_key="cell_labels"
|
|
148
|
+
... )
|
|
149
|
+
>>> sdata.write("path/to/data.zarr")
|
|
131
150
|
"""
|
|
151
|
+
if cells_as_circles is None:
|
|
152
|
+
cells_as_circles = True
|
|
153
|
+
warnings.warn(
|
|
154
|
+
"The default value of `cells_as_circles` will change to `False` in the next release. "
|
|
155
|
+
"Please pass `True` explicitly to maintain the current behavior.",
|
|
156
|
+
DeprecationWarning,
|
|
157
|
+
stacklevel=3,
|
|
158
|
+
)
|
|
132
159
|
image_models_kwargs, labels_models_kwargs = _initialize_raster_models_kwargs(
|
|
133
160
|
image_models_kwargs, labels_models_kwargs
|
|
134
161
|
)
|
|
@@ -138,7 +165,7 @@ def xenium(
|
|
|
138
165
|
# to trigger the warning if the version cannot be parsed
|
|
139
166
|
version = _parse_version_of_xenium_analyzer(specs, hide_warning=False)
|
|
140
167
|
|
|
141
|
-
specs["region"] = "cell_circles" if cells_as_circles else "
|
|
168
|
+
specs["region"] = "cell_circles" if cells_as_circles else "cell_labels"
|
|
142
169
|
|
|
143
170
|
# the table is required in some cases
|
|
144
171
|
if not cells_table:
|
|
@@ -210,14 +237,16 @@ def xenium(
|
|
|
210
237
|
if cell_labels_indices_mapping is not None and table is not None:
|
|
211
238
|
if not pd.DataFrame.equals(cell_labels_indices_mapping["cell_id"], table.obs[str(XeniumKeys.CELL_ID)]):
|
|
212
239
|
warnings.warn(
|
|
213
|
-
"The cell_id column in the cell_labels_table does not match the cell_id column derived from the
|
|
214
|
-
"labels data. This could be due to trying to read a new version that is not supported yet.
|
|
215
|
-
"report this issue.",
|
|
240
|
+
"The cell_id column in the cell_labels_table does not match the cell_id column derived from the "
|
|
241
|
+
"cell labels data. This could be due to trying to read a new version that is not supported yet. "
|
|
242
|
+
"Please report this issue.",
|
|
216
243
|
UserWarning,
|
|
217
244
|
stacklevel=2,
|
|
218
245
|
)
|
|
219
246
|
else:
|
|
220
247
|
table.obs["cell_labels"] = cell_labels_indices_mapping["label_index"]
|
|
248
|
+
if not cells_as_circles:
|
|
249
|
+
table.uns[TableModel.ATTRS_KEY][TableModel.INSTANCE_KEY] = "cell_labels"
|
|
221
250
|
|
|
222
251
|
if nucleus_boundaries:
|
|
223
252
|
polygons["nucleus_boundaries"] = _get_polygons(
|
|
@@ -486,6 +515,7 @@ def _get_points(path: Path, specs: dict[str, Any]) -> Table:
|
|
|
486
515
|
feature_key=XeniumKeys.FEATURE_NAME,
|
|
487
516
|
instance_key=XeniumKeys.CELL_ID,
|
|
488
517
|
transformations={"global": transform},
|
|
518
|
+
sort=True,
|
|
489
519
|
)
|
|
490
520
|
return points
|
|
491
521
|
|
|
@@ -506,7 +536,7 @@ def _get_tables_and_circles(
|
|
|
506
536
|
table = TableModel.parse(adata, region=specs["region"], region_key="region", instance_key=str(XeniumKeys.CELL_ID))
|
|
507
537
|
if cells_as_circles:
|
|
508
538
|
transform = Scale([1.0 / specs["pixel_size"], 1.0 / specs["pixel_size"]], axes=("x", "y"))
|
|
509
|
-
radii = np.sqrt(adata.obs[XeniumKeys.
|
|
539
|
+
radii = np.sqrt(adata.obs[XeniumKeys.CELL_AREA].to_numpy() / np.pi)
|
|
510
540
|
circles = ShapesModel.parse(
|
|
511
541
|
circ,
|
|
512
542
|
geometry=0,
|
|
@@ -523,7 +553,7 @@ def _get_images(
|
|
|
523
553
|
file: str,
|
|
524
554
|
imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
|
|
525
555
|
image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
|
|
526
|
-
) ->
|
|
556
|
+
) -> DataArray | DataTree:
|
|
527
557
|
image = imread(path / file, **imread_kwargs)
|
|
528
558
|
if "c_coords" in image_models_kwargs and "dummy" in image_models_kwargs["c_coords"]:
|
|
529
559
|
# Napari currently interprets 4 channel images as RGB; a series of PRs to fix this is almost ready but they will
|
|
@@ -532,7 +562,7 @@ def _get_images(
|
|
|
532
562
|
# let's add a dummy channel as a temporary workaround.
|
|
533
563
|
image = da.concatenate([image, da.zeros_like(image[0:1])], axis=0)
|
|
534
564
|
return Image2DModel.parse(
|
|
535
|
-
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
|
|
536
566
|
)
|
|
537
567
|
|
|
538
568
|
|
|
@@ -540,7 +570,7 @@ def _add_aligned_images(
|
|
|
540
570
|
path: Path,
|
|
541
571
|
imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
|
|
542
572
|
image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
|
|
543
|
-
) -> dict[str,
|
|
573
|
+
) -> dict[str, DataTree]:
|
|
544
574
|
"""Discover and parse aligned images."""
|
|
545
575
|
images = {}
|
|
546
576
|
ome_tif_files = list(path.glob("*.ome.tif"))
|
|
@@ -571,7 +601,8 @@ def xenium_aligned_image(
|
|
|
571
601
|
alignment_file: str | Path | None,
|
|
572
602
|
imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
|
|
573
603
|
image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
|
|
574
|
-
|
|
604
|
+
dims: tuple[str, ...] | None = None,
|
|
605
|
+
) -> DataTree:
|
|
575
606
|
"""
|
|
576
607
|
Read an image aligned to a Xenium dataset, with an optional alignment file.
|
|
577
608
|
|
|
@@ -583,6 +614,12 @@ def xenium_aligned_image(
|
|
|
583
614
|
Path to the alignment file, if not passed it is assumed that the image is aligned.
|
|
584
615
|
image_models_kwargs
|
|
585
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).
|
|
586
623
|
|
|
587
624
|
Returns
|
|
588
625
|
-------
|
|
@@ -600,19 +637,28 @@ def xenium_aligned_image(
|
|
|
600
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
|
|
601
638
|
# not a problem because the transformation is constructed to be consistent, but if is the case, the data orientation
|
|
602
639
|
# would be transposed compared to the original image, not ideal.
|
|
603
|
-
if
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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")
|
|
608
654
|
else:
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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)
|
|
616
662
|
|
|
617
663
|
if alignment_file is None:
|
|
618
664
|
transformation = Identity()
|
|
@@ -735,3 +781,6 @@ def prefix_suffix_uint32_from_cell_id_str(cell_id_str: ArrayLike) -> tuple[Array
|
|
|
735
781
|
cell_id_prefix = [int(x, 16) for x in cell_id_prefix_hex]
|
|
736
782
|
|
|
737
783
|
return np.array(cell_id_prefix, dtype=np.uint32), np.array(dataset_suffix_int)
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
##
|
|
@@ -51,13 +51,13 @@ def test_roundtrip_with_data_limits() -> None:
|
|
|
51
51
|
"dataset,expected",
|
|
52
52
|
[
|
|
53
53
|
("Xenium_V1_human_Breast_2fov_outs", "{'y': (0, 3529), 'x': (0, 5792), 'z': (10, 25)}"),
|
|
54
|
-
("Xenium_V1_human_Lung_2fov_outs", "{'y': (
|
|
54
|
+
("Xenium_V1_human_Lung_2fov_outs", "{'y': (0, 3553), 'x': (0, 5793), 'z': (7, 32)}"),
|
|
55
55
|
],
|
|
56
56
|
)
|
|
57
57
|
def test_example_data(dataset: str, expected: str) -> None:
|
|
58
58
|
f = Path("./data") / dataset
|
|
59
59
|
assert f.is_dir()
|
|
60
|
-
sdata = xenium(f)
|
|
60
|
+
sdata = xenium(f, cells_as_circles=False)
|
|
61
61
|
from spatialdata import get_extent
|
|
62
62
|
|
|
63
63
|
extent = get_extent(sdata, exact=False)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/.github/workflows/prepare_test_data.yaml
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
|
|
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.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/_constants/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/converters/__init__.py
RENAMED
|
File without changes
|
{spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/experimental/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{spatialdata_io-0.1.3.post0 → spatialdata_io-0.1.5}/src/spatialdata_io/readers/_utils/__init__.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
|