spatialproteomics 0.8.3__tar.gz → 0.8.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.
Files changed (37) hide show
  1. {spatialproteomics-0.8.3/spatialproteomics.egg-info → spatialproteomics-0.8.5}/PKG-INFO +2 -6
  2. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/README.md +1 -5
  3. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/pyproject.toml +1 -1
  4. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/container.py +0 -3
  5. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/la/label.py +11 -2
  6. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/la/utils.py +9 -1
  7. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/pp/preprocessing.py +3 -3
  8. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/pp/utils.py +5 -3
  9. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/tl/tool.py +22 -2
  10. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5/spatialproteomics.egg-info}/PKG-INFO +2 -6
  11. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/tests/test_to_zarr.py +4 -4
  12. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/LICENSE +0 -0
  13. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/setup.cfg +0 -0
  14. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/__init__.py +0 -0
  15. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/base_logger.py +0 -0
  16. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/constants.py +0 -0
  17. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/image_container/ImageContainer.py +0 -0
  18. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/image_container/__init__.py +0 -0
  19. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/la/__init__.py +0 -0
  20. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/nh/__init__.py +0 -0
  21. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/nh/neighborhood.py +0 -0
  22. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/nh/utils.py +0 -0
  23. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/pl/__init__.py +0 -0
  24. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/pl/plot.py +0 -0
  25. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/pl/utils.py +0 -0
  26. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/pp/__init__.py +0 -0
  27. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/pp/intensity.py +0 -0
  28. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/sd/__init__.py +0 -0
  29. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/sd/utils.py +0 -0
  30. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/tl/__init__.py +0 -0
  31. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics/tl/utils.py +0 -0
  32. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics.egg-info/SOURCES.txt +0 -0
  33. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics.egg-info/dependency_links.txt +0 -0
  34. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics.egg-info/requires.txt +0 -0
  35. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/spatialproteomics.egg-info/top_level.txt +0 -0
  36. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/tests/test_container.py +0 -0
  37. {spatialproteomics-0.8.3 → spatialproteomics-0.8.5}/tests/test_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spatialproteomics
3
- Version: 0.8.3
3
+ Version: 0.8.5
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
@@ -97,8 +97,4 @@ matplotlib
97
97
  ```
98
98
 
99
99
  ## Citation
100
- Spatialproteomics - an interoperable toolbox for analyzing highly multiplexed fluorescence image data
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
100
+ > 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.
@@ -47,8 +47,4 @@ matplotlib
47
47
  ```
48
48
 
49
49
  ## Citation
50
- Spatialproteomics - an interoperable toolbox for analyzing highly multiplexed fluorescence image data
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"
3
+ version = "0.8.5"
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"
@@ -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
 
@@ -76,6 +76,7 @@ def threshold_labels(
76
76
  def predict_cell_types_argmax(
77
77
  sdata,
78
78
  marker_dict: dict,
79
+ min_intensity: float = 0.0,
79
80
  table_key: str = SDLayers.TABLE,
80
81
  label_key: str = SDFeatures.LABELS,
81
82
  copy: bool = False,
@@ -90,6 +91,7 @@ def predict_cell_types_argmax(
90
91
  Args:
91
92
  sdata (spatialdata.SpatialData): The spatialdata object containing the expression matrix.
92
93
  marker_dict (dict): A dictionary containing the marker genes for each cell type.
94
+ min_intensity (float, optional): The minimum intensity threshold for assigning cells to a type. Cells with all marker intensities below this threshold will be assigned to "Unlabeled". Defaults to 0.0.
93
95
  table_key (str, optional): The key under which the expression matrix is stored in the tables attribute of the spatialdata object. Defaults to "table".
94
96
  label_key (str, optional): The key under which the cell type labels are stored in the obs attribute of the spatialdata object. Defaults to "celltype".
95
97
  copy (bool, optional): Whether to create a copy of the spatialdata object. Defaults to False.
@@ -102,7 +104,9 @@ def predict_cell_types_argmax(
102
104
  assert (
103
105
  len(set(marker_dict.keys()) - set(expression_df.columns)) == 0
104
106
  ), f"The following markers were not found in quantification layer: {set(marker_dict.keys()) - set(expression_df.columns)}."
105
- celltypes = _predict_cell_types_argmax(expression_df, list(marker_dict.keys()), list(marker_dict.values()))
107
+ celltypes = _predict_cell_types_argmax(
108
+ expression_df, list(marker_dict.keys()), list(marker_dict.values()), min_intensity=min_intensity
109
+ )
106
110
  adata.obs[label_key] = celltypes
107
111
  adata.obs[label_key] = adata.obs[label_key].astype("category")
108
112
 
@@ -792,6 +796,7 @@ class LabelAccessor:
792
796
  overwrite_existing_labels: bool = False,
793
797
  cell_col: str = "cell",
794
798
  label_col: str = "label",
799
+ min_intensity: float = 0.0,
795
800
  ):
796
801
  """
797
802
  Predicts cell types based on the argmax classification of marker intensities.
@@ -802,6 +807,7 @@ class LabelAccessor:
802
807
  overwrite_existing_labels (bool, optional): Whether to overwrite existing labels. Defaults to False.
803
808
  cell_col (str, optional): The name of the column to store cell IDs in the output dataframe. Defaults to "cell".
804
809
  label_col (str, optional): The name of the column to store predicted cell types in the output dataframe. Defaults to "label".
810
+ min_intensity (float, optional): The minimum intensity threshold for assigning cells to a type. Defaults to 0.0.
805
811
 
806
812
  Returns:
807
813
  xr.Dataset: A new spatialproteomics object with the predicted cell types added as labels.
@@ -825,7 +831,10 @@ class LabelAccessor:
825
831
  obj = self._obj.copy()
826
832
 
827
833
  celltypes_argmax = _predict_cell_types_argmax(
828
- pd.DataFrame(obj.pp[markers][key].values, columns=markers), markers=markers, celltypes=celltypes
834
+ pd.DataFrame(obj.pp[markers][key].values, columns=markers),
835
+ markers=markers,
836
+ celltypes=celltypes,
837
+ min_intensity=min_intensity,
829
838
  )
830
839
 
831
840
  # putting everything into a dataframe
@@ -4,6 +4,8 @@ import numpy as np
4
4
  import pandas as pd
5
5
  from skimage.segmentation import relabel_sequential
6
6
 
7
+ from spatialproteomics.constants import Labels
8
+
7
9
  from ..base_logger import logger
8
10
 
9
11
 
@@ -160,11 +162,17 @@ def _predict_cell_types_argmax(
160
162
  expression_df: pd.DataFrame,
161
163
  markers: List,
162
164
  celltypes: List,
165
+ min_intensity: float = 0.0,
163
166
  ):
164
167
  # getting the argmax for each cell
165
168
  argmax_classification = np.argmax(expression_df[markers], axis=1)
166
169
 
167
170
  # translating the argmax classification into cell types
168
- celltypes_argmax = np.array(celltypes)[argmax_classification]
171
+ celltypes_argmax = np.array(celltypes, dtype=object)[argmax_classification]
172
+
173
+ # if all markers are below the minimum intensity, assign UNASSIGNED
174
+ if min_intensity > 0.0:
175
+ below_threshold = (expression_df[markers].max(axis=1) < min_intensity).values
176
+ celltypes_argmax[below_threshold] = Labels.UNLABELED
169
177
 
170
178
  return celltypes_argmax
@@ -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 dimensional."
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(
@@ -659,11 +659,13 @@ def _threshold(
659
659
  if intensity.size == 1:
660
660
  intensity = np.full(len(all_channels), intensity.item(), dtype=img_dtype)
661
661
  else:
662
- # cast each provided value to image dtype
663
- intensity = np.asarray(intensity, dtype=img_dtype)
662
+ # casting to float here to allow for NAs (important when using the channels argument)
663
+ intensity = np.asarray(intensity, dtype=float)
664
664
 
665
665
  max_val = np.max(img)
666
- assert np.all(intensity <= max_val), "Intensity values must be smaller than the maximum intensity."
666
+ assert np.all(
667
+ intensity[~np.isnan(intensity)] <= max_val
668
+ ), "Intensity values must be smaller than the maximum intensity."
667
669
 
668
670
  filtered = None
669
671
 
@@ -588,6 +588,7 @@ class ToolAccessor:
588
588
  expression_matrix_key: str = Layers.INTENSITY,
589
589
  obs_key: str = Layers.OBS,
590
590
  additional_layers: Optional[dict] = None,
591
+ additional_obsm: Optional[dict] = None,
591
592
  additional_uns: Optional[dict] = None,
592
593
  ):
593
594
  """
@@ -601,6 +602,8 @@ class ToolAccessor:
601
602
  The key of the observation data in the spatialproteomics object. Default is '_obs'.
602
603
  additional_layers : dict, optional
603
604
  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.
605
+ additional_obsm : dict, optional
606
+ 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
607
  additional_uns : dict, optional
605
608
  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
609
 
@@ -612,7 +615,7 @@ class ToolAccessor:
612
615
  Raises
613
616
  ------
614
617
  AssertionError
615
- If the expression matrix key or additional layers are not found in the spatialproteomics object.
618
+ 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
619
 
617
620
  Notes
618
621
  -----
@@ -633,6 +636,11 @@ class ToolAccessor:
633
636
  for key, layer in additional_layers.items():
634
637
  # checking that the additional layer is present in the object
635
638
  assert layer in self._obj, f"Layer {layer} not found in the object."
639
+ # checking that layer has the same shape as the expression matrix
640
+ assert self._obj[layer].shape == expression_matrix.shape, (
641
+ f"Layer {layer} has shape {self._obj[layer].shape}, but AnnData layers must match "
642
+ f"the shape of the expression matrix {expression_matrix.shape}."
643
+ )
636
644
  adata.layers[key] = self._obj[layer].values
637
645
  adata.var_names = self._obj.coords[Dims.CHANNELS].values
638
646
 
@@ -661,9 +669,21 @@ class ToolAccessor:
661
669
  adata.obs[Features.Y] = adata.obs[Features.Y].astype(float)
662
670
  adata.obsm["spatial"] = np.array(adata.obs[[Features.X, Features.Y]])
663
671
 
672
+ if additional_obsm:
673
+ for key, layer in additional_obsm.items():
674
+ # checking that the additional obsm source layer is present in the object
675
+ assert layer in self._obj, f"Layer {layer} not found in the object."
676
+ # checking that the additional obsm layer has the same number of observations as the expression matrix
677
+ assert self._obj[layer].shape[0] == expression_matrix.shape[0], (
678
+ f"Layer {layer} has {self._obj[layer].shape[0]} observations, but the expression "
679
+ f"matrix has {expression_matrix.shape[0]} observations. AnnData obsm entries must match "
680
+ "the number of observations in the expression matrix."
681
+ )
682
+ adata.obsm[key] = self._obj[layer].values
683
+
664
684
  if additional_uns:
665
685
  for key, layer in additional_uns.items():
666
- # checking that the additional layer is present in the object
686
+ # checking that the additional uns source layer is present in the object
667
687
  assert layer in self._obj, f"Layer {layer} not found in the object."
668
688
  adata.uns[key] = self._obj.pp.get_layer_as_df(layer)
669
689
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spatialproteomics
3
- Version: 0.8.3
3
+ Version: 0.8.5
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
@@ -97,8 +97,4 @@ matplotlib
97
97
  ```
98
98
 
99
99
  ## Citation
100
- Spatialproteomics - an interoperable toolbox for analyzing highly multiplexed fluorescence image data
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
100
+ > 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.
@@ -7,7 +7,7 @@ def test_to_zarr_image(ds_image):
7
7
  with tempfile.TemporaryDirectory() as temp_dir:
8
8
  output_path = os.path.join(temp_dir, "zarr_writing_test.zarr")
9
9
  try:
10
- ds_image.drop_encoding().to_zarr(output_path)
10
+ ds_image.drop_encoding().to_zarr(output_path, consolidated=False)
11
11
  assert os.path.exists(output_path) and os.path.isdir(
12
12
  output_path
13
13
  ), f"Directory {output_path} was not created."
@@ -21,7 +21,7 @@ def test_to_zarr_segmentation(ds_segmentation):
21
21
  with tempfile.TemporaryDirectory() as temp_dir:
22
22
  output_path = os.path.join(temp_dir, "zarr_writing_test.zarr")
23
23
  try:
24
- ds_segmentation.drop_encoding().to_zarr(output_path)
24
+ ds_segmentation.drop_encoding().to_zarr(output_path, consolidated=False)
25
25
  assert os.path.exists(output_path) and os.path.isdir(
26
26
  output_path
27
27
  ), f"Directory {output_path} was not created."
@@ -35,7 +35,7 @@ def test_to_zarr_labels(ds_labels):
35
35
  with tempfile.TemporaryDirectory() as temp_dir:
36
36
  output_path = os.path.join(temp_dir, "zarr_writing_test.zarr")
37
37
  try:
38
- ds_labels.drop_encoding().to_zarr(output_path)
38
+ ds_labels.drop_encoding().to_zarr(output_path, consolidated=False)
39
39
  assert os.path.exists(output_path) and os.path.isdir(
40
40
  output_path
41
41
  ), f"Directory {output_path} was not created."
@@ -49,7 +49,7 @@ def test_to_zarr_neighborhoods(ds_neighborhoods):
49
49
  with tempfile.TemporaryDirectory() as temp_dir:
50
50
  output_path = os.path.join(temp_dir, "zarr_writing_test.zarr")
51
51
  try:
52
- ds_neighborhoods.drop_encoding().to_zarr(output_path)
52
+ ds_neighborhoods.drop_encoding().to_zarr(output_path, consolidated=False)
53
53
  assert os.path.exists(output_path) and os.path.isdir(
54
54
  output_path
55
55
  ), f"Directory {output_path} was not created."