spatialproteomics 0.8.4__tar.gz → 0.8.6__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.
- {spatialproteomics-0.8.4/spatialproteomics.egg-info → spatialproteomics-0.8.6}/PKG-INFO +6 -7
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/README.md +2 -6
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/pyproject.toml +3 -1
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/container.py +0 -3
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/la/label.py +3 -1
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/pp/preprocessing.py +3 -3
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/tl/__init__.py +2 -1
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/tl/tool.py +191 -4
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/tl/utils.py +38 -1
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6/spatialproteomics.egg-info}/PKG-INFO +6 -7
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics.egg-info/requires.txt +4 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/LICENSE +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/setup.cfg +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/__init__.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/base_logger.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/constants.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/image_container/ImageContainer.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/image_container/__init__.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/la/__init__.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/la/utils.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/nh/__init__.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/nh/neighborhood.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/nh/utils.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/pl/__init__.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/pl/plot.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/pl/utils.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/pp/__init__.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/pp/intensity.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/pp/utils.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/sd/__init__.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/sd/utils.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics.egg-info/SOURCES.txt +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics.egg-info/dependency_links.txt +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics.egg-info/top_level.txt +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/tests/test_container.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/tests/test_integration.py +0 -0
- {spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/tests/test_to_zarr.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spatialproteomics
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.6
|
|
4
4
|
Summary: Spatialproteomics is an interoperable toolbox for analyzing highly multiplexed fluorescence image data
|
|
5
5
|
Author: Matthias Meyer-Bender, Harald Vohringer
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -33,6 +33,8 @@ Provides-Extra: anndata
|
|
|
33
33
|
Requires-Dist: anndata; extra == "anndata"
|
|
34
34
|
Provides-Extra: astir
|
|
35
35
|
Requires-Dist: astir>=0.1.5; extra == "astir"
|
|
36
|
+
Provides-Extra: spotiflow
|
|
37
|
+
Requires-Dist: spotiflow; extra == "spotiflow"
|
|
36
38
|
Provides-Extra: dev
|
|
37
39
|
Requires-Dist: pytest>=7.1.2; extra == "dev"
|
|
38
40
|
Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
|
|
@@ -41,6 +43,7 @@ Requires-Dist: isort>=5.10.1; extra == "dev"
|
|
|
41
43
|
Requires-Dist: flake8>=5.0.4; extra == "dev"
|
|
42
44
|
Requires-Dist: bandit>=1.7.4; extra == "dev"
|
|
43
45
|
Requires-Dist: safety>=2.1.1; extra == "dev"
|
|
46
|
+
Requires-Dist: typing_extensions>=4.16; extra == "dev"
|
|
44
47
|
Provides-Extra: all
|
|
45
48
|
Requires-Dist: spatialdata; extra == "all"
|
|
46
49
|
Requires-Dist: cellpose; extra == "all"
|
|
@@ -60,7 +63,7 @@ Dynamic: license-file
|
|
|
60
63
|
|
|
61
64
|
## Principles
|
|
62
65
|
|
|
63
|
-
Multiplexed imaging data comprises at least 3 dimensions (i.e. `channels`, `x`, and `y`) and
|
|
66
|
+
Multiplexed imaging data comprises at least 3 dimensions (i.e. `channels`, `x`, and `y`) and often contains additional data such as segmentation masks or cell type annotations associated with it. In `spatialproteomics`, we use `xarray` to create a data structure that keeps all of these data dimension in sync. This data structure can then be used to apply all sorts of operations to the data. Users can segment cells, perform different image processing steps, quantify protein expression, predict cell types, and plot their data in various ways. By providing researchers with those tools, `spatialproteomics` can be used to quickly explore highly multiplexed spatial proteomics data directly within jupyter notebooks.
|
|
64
67
|
|
|
65
68
|
<p align="center" width="100%">
|
|
66
69
|
<img src="docs/_static/img/supplementary_figure_1.png" alt="The spatialproteomics data structure enables synchronized subsetting across shared dimensions." style="width:70%;">
|
|
@@ -97,8 +100,4 @@ matplotlib
|
|
|
97
100
|
```
|
|
98
101
|
|
|
99
102
|
## Citation
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
Matthias Fabian Meyer-Bender, Harald Sager Voehringer, Christina Schniederjohann, Sarah Patricia Koziel, Erin Kim Chung, Ekaterina Popova, Alexander Brobeil, Lisa-Maria Held, Aamir Munir, Scverse Community, Sascha Dietrich, Peter-Martin Bruch, Wolfgang Huber
|
|
103
|
-
|
|
104
|
-
bioRxiv 2025.04.29.651202; doi: https://doi.org/10.1101/2025.04.29.651202
|
|
103
|
+
> Meyer-Bender, M., Voehringer, H., Schniederjohann, C., Koziel, S., Chung, E., Popova, E., ... & Huber, W. (2026). Spatialproteomics: an interoperable toolbox for analyzing highly multiplexed fluorescence image data. Nature Methods, 1-10.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
## Principles
|
|
12
12
|
|
|
13
|
-
Multiplexed imaging data comprises at least 3 dimensions (i.e. `channels`, `x`, and `y`) and
|
|
13
|
+
Multiplexed imaging data comprises at least 3 dimensions (i.e. `channels`, `x`, and `y`) and often contains additional data such as segmentation masks or cell type annotations associated with it. In `spatialproteomics`, we use `xarray` to create a data structure that keeps all of these data dimension in sync. This data structure can then be used to apply all sorts of operations to the data. Users can segment cells, perform different image processing steps, quantify protein expression, predict cell types, and plot their data in various ways. By providing researchers with those tools, `spatialproteomics` can be used to quickly explore highly multiplexed spatial proteomics data directly within jupyter notebooks.
|
|
14
14
|
|
|
15
15
|
<p align="center" width="100%">
|
|
16
16
|
<img src="docs/_static/img/supplementary_figure_1.png" alt="The spatialproteomics data structure enables synchronized subsetting across shared dimensions." style="width:70%;">
|
|
@@ -47,8 +47,4 @@ matplotlib
|
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
## Citation
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
Matthias Fabian Meyer-Bender, Harald Sager Voehringer, Christina Schniederjohann, Sarah Patricia Koziel, Erin Kim Chung, Ekaterina Popova, Alexander Brobeil, Lisa-Maria Held, Aamir Munir, Scverse Community, Sascha Dietrich, Peter-Martin Bruch, Wolfgang Huber
|
|
53
|
-
|
|
54
|
-
bioRxiv 2025.04.29.651202; doi: https://doi.org/10.1101/2025.04.29.651202
|
|
50
|
+
> Meyer-Bender, M., Voehringer, H., Schniederjohann, C., Koziel, S., Chung, E., Popova, E., ... & Huber, W. (2026). Spatialproteomics: an interoperable toolbox for analyzing highly multiplexed fluorescence image data. Nature Methods, 1-10.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "spatialproteomics"
|
|
3
|
-
version = "0.8.
|
|
3
|
+
version = "0.8.6"
|
|
4
4
|
description = "Spatialproteomics is an interoperable toolbox for analyzing highly multiplexed fluorescence image data"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -40,6 +40,7 @@ cellpose = ["cellpose>=3.1.0"]
|
|
|
40
40
|
stardist = ["stardist>=0.9.1"]
|
|
41
41
|
anndata = ["anndata"]
|
|
42
42
|
astir = ["astir>=0.1.5"]
|
|
43
|
+
spotiflow = ["spotiflow"]
|
|
43
44
|
dev = [
|
|
44
45
|
"pytest>=7.1.2",
|
|
45
46
|
"pytest-cov>=3.0.0",
|
|
@@ -48,6 +49,7 @@ dev = [
|
|
|
48
49
|
"flake8>=5.0.4",
|
|
49
50
|
"bandit>=1.7.4",
|
|
50
51
|
"safety>=2.1.1",
|
|
52
|
+
"typing_extensions>=4.16", # to avoid anndata issues
|
|
51
53
|
]
|
|
52
54
|
all = ["spatialdata", "cellpose", "stardist", "astir"]
|
|
53
55
|
|
|
@@ -106,9 +106,6 @@ def load_image_data(
|
|
|
106
106
|
if neighborhood is not None:
|
|
107
107
|
dataset = dataset.nh.add_neighborhoods_from_dataframe(neighborhood, neighborhood_col=neighborhood_col)
|
|
108
108
|
|
|
109
|
-
else:
|
|
110
|
-
dataset = xr.Dataset(data_vars={Layers.IMAGE: im})
|
|
111
|
-
|
|
112
109
|
return dataset
|
|
113
110
|
|
|
114
111
|
|
|
@@ -797,6 +797,7 @@ class LabelAccessor:
|
|
|
797
797
|
cell_col: str = "cell",
|
|
798
798
|
label_col: str = "label",
|
|
799
799
|
min_intensity: float = 0.0,
|
|
800
|
+
ignore_neighborhoods: bool = False,
|
|
800
801
|
):
|
|
801
802
|
"""
|
|
802
803
|
Predicts cell types based on the argmax classification of marker intensities.
|
|
@@ -808,6 +809,7 @@ class LabelAccessor:
|
|
|
808
809
|
cell_col (str, optional): The name of the column to store cell IDs in the output dataframe. Defaults to "cell".
|
|
809
810
|
label_col (str, optional): The name of the column to store predicted cell types in the output dataframe. Defaults to "label".
|
|
810
811
|
min_intensity (float, optional): The minimum intensity threshold for assigning cells to a type. Defaults to 0.0.
|
|
812
|
+
ignore_neighborhoods (bool, optional): Whether to remove existing neighborhoods when predicting cell types. Defaults to False.
|
|
811
813
|
|
|
812
814
|
Returns:
|
|
813
815
|
xr.Dataset: A new spatialproteomics object with the predicted cell types added as labels.
|
|
@@ -869,7 +871,7 @@ class LabelAccessor:
|
|
|
869
871
|
obj = obj.pp.drop_layers(Layers.LA_PROPERTIES)
|
|
870
872
|
|
|
871
873
|
# adding the new labels
|
|
872
|
-
return obj.la.add_labels_from_dataframe(celltype_prediction_df)
|
|
874
|
+
return obj.la.add_labels_from_dataframe(celltype_prediction_df, ignore_neighborhoods=ignore_neighborhoods)
|
|
873
875
|
|
|
874
876
|
def _threshold_label(
|
|
875
877
|
self, channel: str, threshold: float, layer_key: str = Layers.INTENSITY, label: Optional[str] = None
|
|
@@ -866,7 +866,7 @@ class PreprocessingAccessor:
|
|
|
866
866
|
from_layer = segmentation
|
|
867
867
|
segmentation = self._obj[segmentation].values.squeeze()
|
|
868
868
|
|
|
869
|
-
assert segmentation.ndim == 2, "A segmentation mask must 2
|
|
869
|
+
assert segmentation.ndim == 2, "A segmentation mask must be 2-dimensional."
|
|
870
870
|
assert ~np.any(segmentation < 0), "A segmentation mask may not contain negative numbers."
|
|
871
871
|
|
|
872
872
|
y_dim, x_dim = segmentation.shape
|
|
@@ -939,14 +939,14 @@ class PreprocessingAccessor:
|
|
|
939
939
|
"""
|
|
940
940
|
# checking that the layer does not exist yet
|
|
941
941
|
assert key_added not in self._obj, f"Layer {key_added} already exists."
|
|
942
|
-
assert array.ndim in [2, 3], "The array to add mask must 2 or 3-dimensional."
|
|
942
|
+
assert array.ndim in [2, 3], "The array to add mask must be 2- or 3-dimensional."
|
|
943
943
|
|
|
944
944
|
if array.ndim == 2:
|
|
945
945
|
# in the case of a 2D array
|
|
946
946
|
y_dim, x_dim = array.shape
|
|
947
947
|
assert (x_dim == self._obj.sizes[Dims.X]) & (
|
|
948
948
|
y_dim == self._obj.sizes[Dims.Y]
|
|
949
|
-
), f"The shape of array does not match that of the image. Image has shape ({self._obj.sizes[Dims.Y]}, {self._obj.sizes[Dims.X]}), array has shape {array.shape}."
|
|
949
|
+
), f"The shape of the array does not match that of the image. Image has shape ({self._obj.sizes[Dims.Y]}, {self._obj.sizes[Dims.X]}), array has shape {array.shape}."
|
|
950
950
|
|
|
951
951
|
# create a data array with the new layer
|
|
952
952
|
da = xr.DataArray(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .tool import ToolAccessor, astir, cellpose, mesmer, stardist
|
|
1
|
+
from .tool import ToolAccessor, astir, cellpose, mesmer, spotiflow, stardist
|
|
2
2
|
|
|
3
3
|
__all__ = [
|
|
4
4
|
"ToolAccessor",
|
|
@@ -6,4 +6,5 @@ __all__ = [
|
|
|
6
6
|
"mesmer",
|
|
7
7
|
"stardist",
|
|
8
8
|
"astir",
|
|
9
|
+
"spotiflow",
|
|
9
10
|
]
|
|
@@ -7,7 +7,12 @@ import xarray as xr
|
|
|
7
7
|
|
|
8
8
|
from ..base_logger import logger
|
|
9
9
|
from ..constants import Dims, Features, Layers, Props, SDLayers
|
|
10
|
-
from ..sd.utils import
|
|
10
|
+
from ..sd.utils import (
|
|
11
|
+
_get_channels_spatialdata,
|
|
12
|
+
_process_adata,
|
|
13
|
+
_process_image,
|
|
14
|
+
_process_segmentation,
|
|
15
|
+
)
|
|
11
16
|
from .utils import (
|
|
12
17
|
_astir,
|
|
13
18
|
_cellpose,
|
|
@@ -15,6 +20,7 @@ from .utils import (
|
|
|
15
20
|
_convert_masks_to_data_array,
|
|
16
21
|
_get_channels,
|
|
17
22
|
_mesmer,
|
|
23
|
+
_spotiflow,
|
|
18
24
|
_stardist,
|
|
19
25
|
)
|
|
20
26
|
|
|
@@ -262,6 +268,96 @@ def astir(
|
|
|
262
268
|
return sdata
|
|
263
269
|
|
|
264
270
|
|
|
271
|
+
def spotiflow(
|
|
272
|
+
sdata,
|
|
273
|
+
channel: Optional[str] = None,
|
|
274
|
+
image_key: str = SDLayers.IMAGE,
|
|
275
|
+
segmentation_key: str = SDLayers.SEGMENTATION,
|
|
276
|
+
table_key: str = SDLayers.TABLE,
|
|
277
|
+
key_added: str = "spotiflow",
|
|
278
|
+
key_added_suffix: str = "_spots",
|
|
279
|
+
pretrained_name: str = "general",
|
|
280
|
+
model_kwargs: Optional[dict] = {"verbose": False},
|
|
281
|
+
predict_kwargs: Optional[dict] = {"verbose": False},
|
|
282
|
+
copy: bool = False,
|
|
283
|
+
):
|
|
284
|
+
"""
|
|
285
|
+
This function applies the Spotiflow algorithm to detect spots in the provided image data.
|
|
286
|
+
It extracts the image data from the spatialdata object, applies the Spotiflow algorithm,
|
|
287
|
+
and adds the detected spots to the spatialdata object.
|
|
288
|
+
The detected spots are stored in the points attribute of the spatialdata object.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
sdata (spatialdata.SpatialData): The spatialdata object containing the image data.
|
|
292
|
+
channel (Optional[str]): The channel(s) to be used for spot detection. If None, all channels will be used.
|
|
293
|
+
image_key (str, optional): The key for the image data in the spatialdata object. Defaults to "image".
|
|
294
|
+
segmentation_key (str, optional): The key for the segmentation mask in the spatialdata object. Defaults to "segmentation".
|
|
295
|
+
table_key (str, optional): The key for the table in the spatialdata object. Defaults to "table".
|
|
296
|
+
key_added (str, optional): The key under which the detected spots will be stored in the points attribute of the spatialdata object. Defaults to "spotiflow".
|
|
297
|
+
key_added_suffix (str, optional): The suffix to be added to the channel names in the detected spots. Defaults to "_spots".
|
|
298
|
+
pretrained_name (str, optional): The name of the pretrained model to be used for spot detection. Defaults to "general".
|
|
299
|
+
model_kwargs (Optional[dict], optional): Additional keyword arguments to be passed to the Spotiflow model. Defaults to {'verbose': False}.
|
|
300
|
+
predict_kwargs (Optional[dict], optional): Additional keyword arguments to be passed to the Spotiflow prediction method. Defaults to {'verbose': False}.
|
|
301
|
+
copy (bool, optional): Whether to create a copy of the spatialdata object. Defaults to False.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
spatialdata.SpatialData: The spatialdata object with the detected spots added to the points attribute.
|
|
305
|
+
"""
|
|
306
|
+
import spatialdata as sd
|
|
307
|
+
from spatialdata.transformations import get_transformation, set_transformation
|
|
308
|
+
|
|
309
|
+
assert image_key in sdata, f"Image key {image_key} not found in the object."
|
|
310
|
+
assert segmentation_key in sdata, f"Segmentation key {segmentation_key} not found in the object."
|
|
311
|
+
assert table_key in sdata, f"Table key {table_key} not found in the object."
|
|
312
|
+
|
|
313
|
+
if copy:
|
|
314
|
+
sdata = cp.deepcopy(sdata)
|
|
315
|
+
|
|
316
|
+
channels = _get_channels_spatialdata(channel)
|
|
317
|
+
img = _process_image(sdata, channels=channel, image_key=image_key, key_added=None)
|
|
318
|
+
adata = _process_adata(sdata, table_key=table_key)
|
|
319
|
+
|
|
320
|
+
points_df = _spotiflow(
|
|
321
|
+
img,
|
|
322
|
+
channel_names=channels,
|
|
323
|
+
pretrained_name=pretrained_name,
|
|
324
|
+
model_kwargs=model_kwargs,
|
|
325
|
+
predict_kwargs=predict_kwargs,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
seg = _process_segmentation(sdata, segmentation_key=segmentation_key)
|
|
329
|
+
yy = points_df["y"].round().astype(int).clip(0, seg.shape[0] - 1)
|
|
330
|
+
xx = points_df["x"].round().astype(int).clip(0, seg.shape[1] - 1)
|
|
331
|
+
points_df["cell_id"] = seg[yy, xx]
|
|
332
|
+
points_df = points_df[points_df["cell_id"] > 0] # drop background detections
|
|
333
|
+
|
|
334
|
+
# --- counts per cell, per channel -------------------------------------
|
|
335
|
+
counts = points_df.groupby(["cell_id", "channel"]).size().unstack(fill_value=0)
|
|
336
|
+
counts.columns = [f"{ch}{key_added_suffix}" for ch in counts.columns]
|
|
337
|
+
|
|
338
|
+
# --- counts per cell, per channel -------------------------------------
|
|
339
|
+
counts = points_df.groupby(["cell_id", "channel"]).size().unstack(fill_value=0)
|
|
340
|
+
counts.columns = [f"{ch}{key_added_suffix}" for ch in counts.columns]
|
|
341
|
+
new_cols = counts.columns.tolist() # remember which columns are new
|
|
342
|
+
|
|
343
|
+
# get transformations
|
|
344
|
+
transformation = get_transformation(sdata[image_key])
|
|
345
|
+
|
|
346
|
+
# write points to spatialdata
|
|
347
|
+
sdata.points[key_added] = sd.models.PointsModel.parse(points_df, coordinates={"x": "x", "y": "y"})
|
|
348
|
+
set_transformation(sdata.points[key_added], transformation)
|
|
349
|
+
|
|
350
|
+
# merge counts to existing obs
|
|
351
|
+
obs = adata.obs
|
|
352
|
+
obs = obs.merge(counts, left_on="id", right_index=True, how="left")
|
|
353
|
+
obs[new_cols] = obs[new_cols].fillna(0).astype(int) # only fill the newly merged columns
|
|
354
|
+
|
|
355
|
+
sdata.tables[table_key].obs = obs
|
|
356
|
+
|
|
357
|
+
if copy:
|
|
358
|
+
return sdata
|
|
359
|
+
|
|
360
|
+
|
|
265
361
|
# === SPATIALPROTEOMICS ACCESSOR ===
|
|
266
362
|
@xr.register_dataset_accessor("tl")
|
|
267
363
|
class ToolAccessor:
|
|
@@ -418,7 +514,7 @@ class ToolAccessor:
|
|
|
418
514
|
self._obj.pp[channels]._image.values,
|
|
419
515
|
scale=scale,
|
|
420
516
|
n_tiles=n_tiles,
|
|
421
|
-
normalize=
|
|
517
|
+
normalize=normalize,
|
|
422
518
|
predict_big=predict_big,
|
|
423
519
|
postprocess_func=postprocess_func,
|
|
424
520
|
**kwargs,
|
|
@@ -583,11 +679,83 @@ class ToolAccessor:
|
|
|
583
679
|
assigned_cell_types, cell_col=cell_id_col, label_col=cell_type_col
|
|
584
680
|
)
|
|
585
681
|
|
|
682
|
+
def spotiflow(
|
|
683
|
+
self,
|
|
684
|
+
channel: Optional[str] = None,
|
|
685
|
+
image_key: str = Layers.IMAGE,
|
|
686
|
+
segmentation_key: str = Layers.SEGMENTATION,
|
|
687
|
+
key_added_suffix: str = "_spots",
|
|
688
|
+
pretrained_name: str = "general",
|
|
689
|
+
return_spots: bool = False,
|
|
690
|
+
model_kwargs: Optional[dict] = {"verbose": False},
|
|
691
|
+
predict_kwargs: Optional[dict] = {"verbose": False},
|
|
692
|
+
):
|
|
693
|
+
"""
|
|
694
|
+
This method detects spots in the image using the Spotiflow algorithm and counts the number of spots per cell, per channel.
|
|
695
|
+
|
|
696
|
+
Parameters
|
|
697
|
+
----------
|
|
698
|
+
channel : str, optional
|
|
699
|
+
Channel to use for spot detection. If None, all channels are used.
|
|
700
|
+
image_key : str, optional
|
|
701
|
+
Key to the image layer in the spatialproteomics object. Default is 'image'.
|
|
702
|
+
segmentation_key : str, optional
|
|
703
|
+
Key to the segmentation layer in the spatialproteomics object. Default is 'segmentation'.
|
|
704
|
+
key_added_suffix : str, optional
|
|
705
|
+
Suffix to add to the channel names in the resulting counts. Default is '_spots'.
|
|
706
|
+
pretrained_name : str, optional
|
|
707
|
+
Name of the pretrained model to use for spot detection. Default is 'general'.
|
|
708
|
+
return_spots : bool, optional
|
|
709
|
+
Whether to return the detected spots as a DataFrame. Default is False.
|
|
710
|
+
model_kwargs : dict, optional
|
|
711
|
+
Additional keyword arguments to pass to the Spotiflow model. Default is {'verbose': False}.
|
|
712
|
+
predict_kwargs : dict, optional
|
|
713
|
+
Additional keyword arguments to pass to the Spotiflow prediction method. Default is {'verbose': False}.
|
|
714
|
+
|
|
715
|
+
Returns
|
|
716
|
+
-------
|
|
717
|
+
xr.Dataset
|
|
718
|
+
Dataset containing the counts of spots per cell, per channel. If return_spots is True, also returns a DataFrame of the detected spots with their coordinates and assigned cell IDs.
|
|
719
|
+
"""
|
|
720
|
+
assert image_key in self._obj, f"Image key {image_key} not found in the object."
|
|
721
|
+
assert segmentation_key in self._obj, f"Segmentation key {segmentation_key} not found in the object."
|
|
722
|
+
channels = _get_channels(self._obj, key_added=None, channel=channel)
|
|
723
|
+
img = self._obj.pp[channels][image_key].values
|
|
724
|
+
points_df = _spotiflow(
|
|
725
|
+
img,
|
|
726
|
+
channel_names=channels,
|
|
727
|
+
pretrained_name=pretrained_name,
|
|
728
|
+
model_kwargs=model_kwargs,
|
|
729
|
+
predict_kwargs=predict_kwargs,
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
# assign each point to a cell via the segmentation mask
|
|
733
|
+
seg = self._obj[segmentation_key].values
|
|
734
|
+
yy = points_df["y"].round().astype(int).clip(0, seg.shape[0] - 1)
|
|
735
|
+
xx = points_df["x"].round().astype(int).clip(0, seg.shape[1] - 1)
|
|
736
|
+
points_df["cell_id"] = seg[yy, xx]
|
|
737
|
+
points_df = points_df[points_df["cell_id"] > 0] # drop background detections
|
|
738
|
+
|
|
739
|
+
# count points per cell, per channel
|
|
740
|
+
counts = (
|
|
741
|
+
points_df.groupby(["cell_id", "channel"])
|
|
742
|
+
.size()
|
|
743
|
+
.unstack(fill_value=0)
|
|
744
|
+
.reindex(index=self._obj.cells.values, columns=channels, fill_value=0)
|
|
745
|
+
)
|
|
746
|
+
counts.columns = [f"{ch}{key_added_suffix}" for ch in counts.columns]
|
|
747
|
+
|
|
748
|
+
if return_spots:
|
|
749
|
+
return self._obj.pp.add_obs_from_dataframe(counts.reset_index(drop=True)), points_df
|
|
750
|
+
|
|
751
|
+
return self._obj.pp.add_obs_from_dataframe(counts.reset_index(drop=True))
|
|
752
|
+
|
|
586
753
|
def convert_to_anndata(
|
|
587
754
|
self,
|
|
588
755
|
expression_matrix_key: str = Layers.INTENSITY,
|
|
589
756
|
obs_key: str = Layers.OBS,
|
|
590
757
|
additional_layers: Optional[dict] = None,
|
|
758
|
+
additional_obsm: Optional[dict] = None,
|
|
591
759
|
additional_uns: Optional[dict] = None,
|
|
592
760
|
):
|
|
593
761
|
"""
|
|
@@ -601,6 +769,8 @@ class ToolAccessor:
|
|
|
601
769
|
The key of the observation data in the spatialproteomics object. Default is '_obs'.
|
|
602
770
|
additional_layers : dict, optional
|
|
603
771
|
Additional layers to include in the anndata.AnnData object. The keys are the names of the layers and the values are the corresponding keys in the spatialproteomics object.
|
|
772
|
+
additional_obsm : dict, optional
|
|
773
|
+
Additional obsm data to include in the anndata.AnnData object. The keys are the names of the obsm data and the values are the corresponding keys in the spatialproteomics object.
|
|
604
774
|
additional_uns : dict, optional
|
|
605
775
|
Additional uns data to include in the anndata.AnnData object. The keys are the names of the uns data and the values are the corresponding keys in the spatialproteomics object.
|
|
606
776
|
|
|
@@ -612,7 +782,7 @@ class ToolAccessor:
|
|
|
612
782
|
Raises
|
|
613
783
|
------
|
|
614
784
|
AssertionError
|
|
615
|
-
If
|
|
785
|
+
If additional layers, obsm entries, or uns entries are not found in the spatialproteomics object, or if additional layers or obsm entries have incompatible shapes.
|
|
616
786
|
|
|
617
787
|
Notes
|
|
618
788
|
-----
|
|
@@ -633,6 +803,11 @@ class ToolAccessor:
|
|
|
633
803
|
for key, layer in additional_layers.items():
|
|
634
804
|
# checking that the additional layer is present in the object
|
|
635
805
|
assert layer in self._obj, f"Layer {layer} not found in the object."
|
|
806
|
+
# checking that layer has the same shape as the expression matrix
|
|
807
|
+
assert self._obj[layer].shape == expression_matrix.shape, (
|
|
808
|
+
f"Layer {layer} has shape {self._obj[layer].shape}, but AnnData layers must match "
|
|
809
|
+
f"the shape of the expression matrix {expression_matrix.shape}."
|
|
810
|
+
)
|
|
636
811
|
adata.layers[key] = self._obj[layer].values
|
|
637
812
|
adata.var_names = self._obj.coords[Dims.CHANNELS].values
|
|
638
813
|
|
|
@@ -661,9 +836,21 @@ class ToolAccessor:
|
|
|
661
836
|
adata.obs[Features.Y] = adata.obs[Features.Y].astype(float)
|
|
662
837
|
adata.obsm["spatial"] = np.array(adata.obs[[Features.X, Features.Y]])
|
|
663
838
|
|
|
839
|
+
if additional_obsm:
|
|
840
|
+
for key, layer in additional_obsm.items():
|
|
841
|
+
# checking that the additional obsm source layer is present in the object
|
|
842
|
+
assert layer in self._obj, f"Layer {layer} not found in the object."
|
|
843
|
+
# checking that the additional obsm layer has the same number of observations as the expression matrix
|
|
844
|
+
assert self._obj[layer].shape[0] == expression_matrix.shape[0], (
|
|
845
|
+
f"Layer {layer} has {self._obj[layer].shape[0]} observations, but the expression "
|
|
846
|
+
f"matrix has {expression_matrix.shape[0]} observations. AnnData obsm entries must match "
|
|
847
|
+
"the number of observations in the expression matrix."
|
|
848
|
+
)
|
|
849
|
+
adata.obsm[key] = self._obj[layer].values
|
|
850
|
+
|
|
664
851
|
if additional_uns:
|
|
665
852
|
for key, layer in additional_uns.items():
|
|
666
|
-
# checking that the additional layer is present in the object
|
|
853
|
+
# checking that the additional uns source layer is present in the object
|
|
667
854
|
assert layer in self._obj, f"Layer {layer} not found in the object."
|
|
668
855
|
adata.uns[key] = self._obj.pp.get_layer_as_df(layer)
|
|
669
856
|
|
|
@@ -12,7 +12,7 @@ from ..pp.utils import _normalize
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def _get_channels(obj, key_added, channel):
|
|
15
|
-
if key_added in obj:
|
|
15
|
+
if key_added is not None and key_added in obj:
|
|
16
16
|
raise KeyError(f'The key "{key_added}" already exists. Please choose another key.')
|
|
17
17
|
|
|
18
18
|
if channel is not None:
|
|
@@ -248,6 +248,43 @@ def _astir(
|
|
|
248
248
|
return assigned_cell_types
|
|
249
249
|
|
|
250
250
|
|
|
251
|
+
def _spotiflow(
|
|
252
|
+
img,
|
|
253
|
+
channel_names,
|
|
254
|
+
pretrained_name: str = "general",
|
|
255
|
+
model_kwargs: dict = None,
|
|
256
|
+
predict_kwargs: dict = None,
|
|
257
|
+
):
|
|
258
|
+
model_kwargs = model_kwargs or {}
|
|
259
|
+
predict_kwargs = predict_kwargs or {}
|
|
260
|
+
|
|
261
|
+
from spotiflow.model import Spotiflow
|
|
262
|
+
|
|
263
|
+
if img.ndim not in [2, 3]:
|
|
264
|
+
raise ValueError(f"Input image must be 2D or 3D, got {img.ndim}.")
|
|
265
|
+
|
|
266
|
+
if img.ndim == 2:
|
|
267
|
+
img = img[np.newaxis, :, :]
|
|
268
|
+
channel_names = [channel_names] if np.isscalar(channel_names) else channel_names
|
|
269
|
+
|
|
270
|
+
assert img.shape[0] == len(channel_names), "Number of channels in image does not match number of channel names."
|
|
271
|
+
|
|
272
|
+
model = Spotiflow.from_pretrained(pretrained_name, **model_kwargs)
|
|
273
|
+
|
|
274
|
+
dfs = []
|
|
275
|
+
for ch_idx, ch_name in enumerate(channel_names):
|
|
276
|
+
single_image = img[ch_idx]
|
|
277
|
+
points, _ = model.predict(single_image, **predict_kwargs)
|
|
278
|
+
# spotiflow returns points as (row, col) == (y, x)
|
|
279
|
+
df = pd.DataFrame(points, columns=["y", "x"])
|
|
280
|
+
df["channel"] = ch_name
|
|
281
|
+
dfs.append(df)
|
|
282
|
+
|
|
283
|
+
df = pd.concat(dfs, ignore_index=True)
|
|
284
|
+
df.index.name = "points"
|
|
285
|
+
return df
|
|
286
|
+
|
|
287
|
+
|
|
251
288
|
def _compute_transformation(x_coords, y_coords):
|
|
252
289
|
from spatialdata.transformations import Affine
|
|
253
290
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spatialproteomics
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.6
|
|
4
4
|
Summary: Spatialproteomics is an interoperable toolbox for analyzing highly multiplexed fluorescence image data
|
|
5
5
|
Author: Matthias Meyer-Bender, Harald Vohringer
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -33,6 +33,8 @@ Provides-Extra: anndata
|
|
|
33
33
|
Requires-Dist: anndata; extra == "anndata"
|
|
34
34
|
Provides-Extra: astir
|
|
35
35
|
Requires-Dist: astir>=0.1.5; extra == "astir"
|
|
36
|
+
Provides-Extra: spotiflow
|
|
37
|
+
Requires-Dist: spotiflow; extra == "spotiflow"
|
|
36
38
|
Provides-Extra: dev
|
|
37
39
|
Requires-Dist: pytest>=7.1.2; extra == "dev"
|
|
38
40
|
Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
|
|
@@ -41,6 +43,7 @@ Requires-Dist: isort>=5.10.1; extra == "dev"
|
|
|
41
43
|
Requires-Dist: flake8>=5.0.4; extra == "dev"
|
|
42
44
|
Requires-Dist: bandit>=1.7.4; extra == "dev"
|
|
43
45
|
Requires-Dist: safety>=2.1.1; extra == "dev"
|
|
46
|
+
Requires-Dist: typing_extensions>=4.16; extra == "dev"
|
|
44
47
|
Provides-Extra: all
|
|
45
48
|
Requires-Dist: spatialdata; extra == "all"
|
|
46
49
|
Requires-Dist: cellpose; extra == "all"
|
|
@@ -60,7 +63,7 @@ Dynamic: license-file
|
|
|
60
63
|
|
|
61
64
|
## Principles
|
|
62
65
|
|
|
63
|
-
Multiplexed imaging data comprises at least 3 dimensions (i.e. `channels`, `x`, and `y`) and
|
|
66
|
+
Multiplexed imaging data comprises at least 3 dimensions (i.e. `channels`, `x`, and `y`) and often contains additional data such as segmentation masks or cell type annotations associated with it. In `spatialproteomics`, we use `xarray` to create a data structure that keeps all of these data dimension in sync. This data structure can then be used to apply all sorts of operations to the data. Users can segment cells, perform different image processing steps, quantify protein expression, predict cell types, and plot their data in various ways. By providing researchers with those tools, `spatialproteomics` can be used to quickly explore highly multiplexed spatial proteomics data directly within jupyter notebooks.
|
|
64
67
|
|
|
65
68
|
<p align="center" width="100%">
|
|
66
69
|
<img src="docs/_static/img/supplementary_figure_1.png" alt="The spatialproteomics data structure enables synchronized subsetting across shared dimensions." style="width:70%;">
|
|
@@ -97,8 +100,4 @@ matplotlib
|
|
|
97
100
|
```
|
|
98
101
|
|
|
99
102
|
## Citation
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
Matthias Fabian Meyer-Bender, Harald Sager Voehringer, Christina Schniederjohann, Sarah Patricia Koziel, Erin Kim Chung, Ekaterina Popova, Alexander Brobeil, Lisa-Maria Held, Aamir Munir, Scverse Community, Sascha Dietrich, Peter-Martin Bruch, Wolfgang Huber
|
|
103
|
-
|
|
104
|
-
bioRxiv 2025.04.29.651202; doi: https://doi.org/10.1101/2025.04.29.651202
|
|
103
|
+
> Meyer-Bender, M., Voehringer, H., Schniederjohann, C., Koziel, S., Chung, E., Popova, E., ... & Huber, W. (2026). Spatialproteomics: an interoperable toolbox for analyzing highly multiplexed fluorescence image data. Nature Methods, 1-10.
|
|
@@ -32,6 +32,7 @@ isort>=5.10.1
|
|
|
32
32
|
flake8>=5.0.4
|
|
33
33
|
bandit>=1.7.4
|
|
34
34
|
safety>=2.1.1
|
|
35
|
+
typing_extensions>=4.16
|
|
35
36
|
|
|
36
37
|
[docs]
|
|
37
38
|
Sphinx>=7.0.0
|
|
@@ -44,5 +45,8 @@ IPython>=8.0.0
|
|
|
44
45
|
[spatialdata]
|
|
45
46
|
spatialdata>=0.4.0
|
|
46
47
|
|
|
48
|
+
[spotiflow]
|
|
49
|
+
spotiflow
|
|
50
|
+
|
|
47
51
|
[stardist]
|
|
48
52
|
stardist>=0.9.1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics/image_container/__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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{spatialproteomics-0.8.4 → spatialproteomics-0.8.6}/spatialproteomics.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|