spatialproteomics 0.7.10__tar.gz → 0.8.1__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.7.10/spatialproteomics.egg-info → spatialproteomics-0.8.1}/PKG-INFO +1 -1
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/pyproject.toml +1 -1
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/pl/plot.py +4 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/pl/utils.py +3 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/pp/preprocessing.py +59 -26
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/pp/utils.py +104 -22
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1/spatialproteomics.egg-info}/PKG-INFO +1 -1
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/LICENSE +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/README.md +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/setup.cfg +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/__init__.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/base_logger.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/constants.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/container.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/image_container/ImageContainer.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/image_container/__init__.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/la/__init__.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/la/label.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/la/utils.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/nh/__init__.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/nh/neighborhood.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/nh/utils.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/pl/__init__.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/pp/__init__.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/pp/intensity.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/sd/__init__.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/sd/utils.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/tl/__init__.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/tl/tool.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics/tl/utils.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics.egg-info/SOURCES.txt +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics.egg-info/dependency_links.txt +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics.egg-info/requires.txt +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics.egg-info/top_level.txt +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/tests/test_container.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/tests/test_integration.py +0 -0
- {spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/tests/test_to_zarr.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spatialproteomics
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
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
|
|
@@ -245,6 +245,10 @@ class PlotAccessor:
|
|
|
245
245
|
min_val = obs_colors["min"]
|
|
246
246
|
max_val = obs_colors["max"]
|
|
247
247
|
|
|
248
|
+
# handling the special case where all values in the segmentation are the same,
|
|
249
|
+
# which would lead to a division by zero in the normalization step
|
|
250
|
+
max_val = max_val if max_val > min_val else min_val + 1
|
|
251
|
+
|
|
248
252
|
# Create the colorbar with dynamic positioning and size
|
|
249
253
|
sm = plt.cm.ScalarMappable(cmap=cmap, norm=plt.Normalize(vmin=min_val, vmax=max_val))
|
|
250
254
|
sm.set_array([])
|
|
@@ -219,6 +219,9 @@ def _render_obs(
|
|
|
219
219
|
vmin: float = None,
|
|
220
220
|
vmax: float = None,
|
|
221
221
|
) -> np.ndarray:
|
|
222
|
+
# handling the special case where all values in the segmentation are the same,
|
|
223
|
+
# which would lead to a division by zero in the normalization step
|
|
224
|
+
vmax = vmax if vmax > vmin else vmin + 1
|
|
222
225
|
# normalize the segmentation to be in the range [0, 1]
|
|
223
226
|
segmentation = (segmentation - vmin) / (vmax - vmin)
|
|
224
227
|
|
|
@@ -313,6 +313,7 @@ def filter_by_obs(
|
|
|
313
313
|
func: Callable,
|
|
314
314
|
segmentation_key: str = SDLayers.SEGMENTATION,
|
|
315
315
|
table_key: str = SDLayers.TABLE,
|
|
316
|
+
reindex: bool = False,
|
|
316
317
|
copy: bool = False,
|
|
317
318
|
):
|
|
318
319
|
"""
|
|
@@ -324,6 +325,7 @@ def filter_by_obs(
|
|
|
324
325
|
func (Callable): A filtering function that takes in the values of the feature and returns a boolean array.
|
|
325
326
|
segmentation_key (str): The key of the segmentation mask in the object. Default is SDLayers.SEGMENTATION.
|
|
326
327
|
table_key (str): The key of the table in the object. Default is SDLayers.TABLE.
|
|
328
|
+
reindex (bool): Whether to reindex the obs after filtering. Default is False.
|
|
327
329
|
copy (bool): If True, a copy of the object is returned. Default is False.
|
|
328
330
|
"""
|
|
329
331
|
import spatialdata
|
|
@@ -347,13 +349,15 @@ def filter_by_obs(
|
|
|
347
349
|
# setting all cells that are not in cells to 0
|
|
348
350
|
segmentation = _remove_unlabeled_cells(segmentation, cells_sel)
|
|
349
351
|
# relabeling cells in the segmentation mask so the IDs go from 1 to n again
|
|
350
|
-
|
|
352
|
+
if reindex:
|
|
353
|
+
segmentation, relabel_dict = _relabel_cells(segmentation)
|
|
351
354
|
|
|
352
355
|
# removing the cells which are not in cells_sel
|
|
353
356
|
adata = adata[adata.obs[SDFeatures.ID].isin(cells_sel), :].copy()
|
|
354
357
|
# updating the cell coords of the object
|
|
355
|
-
|
|
356
|
-
|
|
358
|
+
if reindex:
|
|
359
|
+
adata.obs[SDFeatures.ID] = [relabel_dict[cell] for cell in adata.obs[SDFeatures.ID].values]
|
|
360
|
+
adata.obs.index = [f"Cell_{x}" for x in adata.obs[SDFeatures.ID].values]
|
|
357
361
|
|
|
358
362
|
# overwriting the segmentation mask in the object
|
|
359
363
|
# get transformations
|
|
@@ -822,7 +826,7 @@ class PreprocessingAccessor:
|
|
|
822
826
|
def add_segmentation(
|
|
823
827
|
self,
|
|
824
828
|
segmentation: Union[str, np.ndarray] = None,
|
|
825
|
-
reindex: bool =
|
|
829
|
+
reindex: bool = False,
|
|
826
830
|
keep_labels: bool = True,
|
|
827
831
|
add_obs: bool = True,
|
|
828
832
|
) -> xr.Dataset:
|
|
@@ -1593,7 +1597,9 @@ class PreprocessingAccessor:
|
|
|
1593
1597
|
|
|
1594
1598
|
return xr.merge([obj, new_img, new_seg], join="outer", compat="no_conflicts")
|
|
1595
1599
|
|
|
1596
|
-
def filter_by_obs(
|
|
1600
|
+
def filter_by_obs(
|
|
1601
|
+
self, col: str, func: Callable, segmentation_key: str = Layers.SEGMENTATION, reindex: bool = False
|
|
1602
|
+
):
|
|
1597
1603
|
"""
|
|
1598
1604
|
Filter the object by observations based on a given feature and filtering function.
|
|
1599
1605
|
|
|
@@ -1601,6 +1607,7 @@ class PreprocessingAccessor:
|
|
|
1601
1607
|
col (str): The name of the feature to filter by.
|
|
1602
1608
|
func (Callable): A filtering function that takes in the values of the feature and returns a boolean array.
|
|
1603
1609
|
segmentation_key (str): The key of the segmentation mask in the object. Default is Layers.SEGMENTATION.
|
|
1610
|
+
reindex (bool): Whether to reindex the cell IDs after filtering. Default is False.
|
|
1604
1611
|
|
|
1605
1612
|
Returns:
|
|
1606
1613
|
xr.Dataset: The filtered object with the selected cells and updated segmentation mask.
|
|
@@ -1610,7 +1617,6 @@ class PreprocessingAccessor:
|
|
|
1610
1617
|
|
|
1611
1618
|
Notes:
|
|
1612
1619
|
- This method filters the object by selecting only the cells that satisfy the filtering condition.
|
|
1613
|
-
- It also updates the segmentation mask to remove cells that are not selected and relabels the remaining cells.
|
|
1614
1620
|
|
|
1615
1621
|
Example:
|
|
1616
1622
|
To filter the object by the feature "area" and keep only the cells with an area greater than 70px:
|
|
@@ -1639,10 +1645,11 @@ class PreprocessingAccessor:
|
|
|
1639
1645
|
segmentation = obj[segmentation_key].values
|
|
1640
1646
|
# setting all cells that are not in cells to 0
|
|
1641
1647
|
segmentation = _remove_unlabeled_cells(segmentation, cells_sel)
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1648
|
+
if reindex:
|
|
1649
|
+
# relabeling cells in the segmentation mask so the IDs go from 1 to n again
|
|
1650
|
+
segmentation, relabel_dict = _relabel_cells(segmentation)
|
|
1651
|
+
# updating the cell coords of the object
|
|
1652
|
+
obj.coords[Dims.CELLS] = [relabel_dict[cell] for cell in obj.coords[Dims.CELLS].values]
|
|
1646
1653
|
|
|
1647
1654
|
# creating a data array with the segmentation mask, so that we can merge it to the original
|
|
1648
1655
|
da = xr.DataArray(
|
|
@@ -1659,7 +1666,11 @@ class PreprocessingAccessor:
|
|
|
1659
1666
|
return xr.merge([obj, da], join="outer", compat="no_conflicts")
|
|
1660
1667
|
|
|
1661
1668
|
def remove_outlying_cells(
|
|
1662
|
-
self,
|
|
1669
|
+
self,
|
|
1670
|
+
dilation_size: int = 25,
|
|
1671
|
+
threshold: int = 5,
|
|
1672
|
+
segmentation_key: str = Layers.SEGMENTATION,
|
|
1673
|
+
reindex: bool = False,
|
|
1663
1674
|
):
|
|
1664
1675
|
"""
|
|
1665
1676
|
Removes outlying cells from the image container. It does so by dilating the segmentation mask and removing cells that belong to a connected component with less than 'threshold' cells.
|
|
@@ -1672,6 +1683,8 @@ class PreprocessingAccessor:
|
|
|
1672
1683
|
The minimum number of cells in a connected component required for the cells to be kept. Default is 5.
|
|
1673
1684
|
segmentation_key : str
|
|
1674
1685
|
The key of the segmentation mask in the object. Default is '_segmentation'.
|
|
1686
|
+
reindex : bool
|
|
1687
|
+
Whether to reindex the cell IDs after filtering. Default is False.
|
|
1675
1688
|
|
|
1676
1689
|
Returns
|
|
1677
1690
|
-------
|
|
@@ -1703,10 +1716,11 @@ class PreprocessingAccessor:
|
|
|
1703
1716
|
obj = self._obj.sel(query)
|
|
1704
1717
|
# setting all cells that are not in cells to 0
|
|
1705
1718
|
segmentation = _remove_unlabeled_cells(segmentation, cells_sel)
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1719
|
+
if reindex:
|
|
1720
|
+
# relabeling cells in the segmentation mask so the IDs go from 1 to n again
|
|
1721
|
+
segmentation, relabel_dict = _relabel_cells(segmentation)
|
|
1722
|
+
# updating the cell coords of the object
|
|
1723
|
+
obj.coords[Dims.CELLS] = [relabel_dict[cell] for cell in obj.coords[Dims.CELLS].values]
|
|
1710
1724
|
|
|
1711
1725
|
# creating a data array with the segmentation mask, so that we can merge it to the original
|
|
1712
1726
|
da = xr.DataArray(
|
|
@@ -1785,12 +1799,21 @@ class PreprocessingAccessor:
|
|
|
1785
1799
|
layer_key: str,
|
|
1786
1800
|
key_added: str = "_merged_segmentation",
|
|
1787
1801
|
labels: Optional[List[str]] = None,
|
|
1788
|
-
|
|
1802
|
+
threshold1: float = 0.7,
|
|
1803
|
+
threshold2: float = 0.8,
|
|
1789
1804
|
):
|
|
1790
1805
|
"""
|
|
1791
|
-
Merge segmentation masks.
|
|
1792
|
-
|
|
1793
|
-
|
|
1806
|
+
Merge segmentation masks. Merging works as follows:
|
|
1807
|
+
1. Assume two segmentation masks, S1 (higher priority, e.g., macrophages) and S2 (lower priority, e.g., lymphocytes)
|
|
1808
|
+
2. We start by copying the segmentation regions from S2 into S1, but only for pixels where S1 has background
|
|
1809
|
+
3. Some S2 cells may now be partially overwritten by S1 cells — we drop any S2 cell where less than threshold1
|
|
1810
|
+
fraction of its original area survived (e.g. with threshold1=0.7,
|
|
1811
|
+
an S2 cell that lost more than 30% of its area to S1 is discarded)
|
|
1812
|
+
4. Of the remaining S2 cells, we additionally drop any that overlap with threshold2 or
|
|
1813
|
+
more of any single S1 cell's area — this prevents S2 cells from being retained when they
|
|
1814
|
+
largely encapsulate an S1 cell (e.g. with threshold2=0.8, an S2 cell is discarded if it
|
|
1815
|
+
covers more than 80% of any S1 cell)
|
|
1816
|
+
5. The surviving S1 and S2 regions are combined into a final mask with sequential labels
|
|
1794
1817
|
|
|
1795
1818
|
Parameters
|
|
1796
1819
|
----------
|
|
@@ -1800,7 +1823,9 @@ class PreprocessingAccessor:
|
|
|
1800
1823
|
The name of the new segmentation mask to be added to the xarray object. Default is "_merged_segmentation".
|
|
1801
1824
|
labels : Optional[List[str]]
|
|
1802
1825
|
Optional. Labels corresponding to each segmentation mask. If provided, must match number of arrays.
|
|
1803
|
-
|
|
1826
|
+
threshold1: float
|
|
1827
|
+
Optional. Threshold for merging cells. Default is 0.7.
|
|
1828
|
+
threshold2: float
|
|
1804
1829
|
Optional. Threshold for merging cells. Default is 0.8.
|
|
1805
1830
|
|
|
1806
1831
|
Returns
|
|
@@ -1820,6 +1845,9 @@ class PreprocessingAccessor:
|
|
|
1820
1845
|
- The merging process starts with merging the biggest cells first, then the smaller ones.
|
|
1821
1846
|
- Disconnected cells in the input are handled based on the specified method.
|
|
1822
1847
|
"""
|
|
1848
|
+
assert threshold1 >= 0 and threshold1 <= 1, "threshold1 must be between 0 and 1."
|
|
1849
|
+
assert threshold2 >= 0 and threshold2 <= 1, "threshold2 must be between 0 and 1."
|
|
1850
|
+
|
|
1823
1851
|
# Make sure layer_key is a list internally
|
|
1824
1852
|
if isinstance(layer_key, str):
|
|
1825
1853
|
layer_keys = [layer_key]
|
|
@@ -1899,7 +1927,8 @@ class PreprocessingAccessor:
|
|
|
1899
1927
|
next_segmentation.squeeze(),
|
|
1900
1928
|
label1=label_1,
|
|
1901
1929
|
label2=label_2,
|
|
1902
|
-
|
|
1930
|
+
threshold1=threshold1,
|
|
1931
|
+
threshold2=threshold2,
|
|
1903
1932
|
)
|
|
1904
1933
|
|
|
1905
1934
|
# Update label mapping
|
|
@@ -2082,13 +2111,16 @@ class PreprocessingAccessor:
|
|
|
2082
2111
|
|
|
2083
2112
|
return xr.merge([obj, da], join="outer", compat="no_conflicts")
|
|
2084
2113
|
|
|
2085
|
-
def mask_cells(
|
|
2114
|
+
def mask_cells(
|
|
2115
|
+
self, mask_key: str = Layers.MASK, segmentation_key=Layers.SEGMENTATION, reindex: bool = False
|
|
2116
|
+
) -> xr.Dataset:
|
|
2086
2117
|
"""
|
|
2087
2118
|
Mask cells in the segmentation mask.
|
|
2088
2119
|
|
|
2089
2120
|
Parameters:
|
|
2090
2121
|
mask_key (str): The key of the mask to use for masking.
|
|
2091
2122
|
segmentation_key (str): The key of the segmentation mask in the object. Default is Layers.SEGMENTATION.
|
|
2123
|
+
reindex (bool): Whether to reindex the cell IDs after filtering. Default is False.
|
|
2092
2124
|
|
|
2093
2125
|
Returns:
|
|
2094
2126
|
xr.Dataset: The object with the masked cells in the segmentation mask.
|
|
@@ -2117,10 +2149,11 @@ class PreprocessingAccessor:
|
|
|
2117
2149
|
segmentation = obj[segmentation_key].values
|
|
2118
2150
|
# setting all cells that are not in cells to 0
|
|
2119
2151
|
segmentation = _remove_unlabeled_cells(segmentation, cells_sel)
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2152
|
+
if reindex:
|
|
2153
|
+
# relabeling cells in the segmentation mask so the IDs go from 1 to n again
|
|
2154
|
+
segmentation, relabel_dict = _relabel_cells(segmentation)
|
|
2155
|
+
# updating the cell coords of the object
|
|
2156
|
+
obj.coords[Dims.CELLS] = [relabel_dict[cell] for cell in obj.coords["cells"].values]
|
|
2124
2157
|
|
|
2125
2158
|
# creating a data array with the segmentation mask, so that we can merge it to the original
|
|
2126
2159
|
da = xr.DataArray(
|
|
@@ -95,62 +95,144 @@ def _relabel_cells(segmentation: np.ndarray) -> Tuple[np.ndarray, dict]:
|
|
|
95
95
|
return segmentation_relabeled, value_map
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
def
|
|
98
|
+
def _exceeds_overlap(i2: np.ndarray, s1: np.ndarray, n2: np.ndarray, p1: list, threshold2: float) -> np.ndarray:
|
|
99
|
+
"""
|
|
100
|
+
Return a boolean mask over i2 indicating which s2 regions should be KEPT
|
|
101
|
+
(i.e. do not exceed the overlap threshold with any s1 cell).
|
|
102
|
+
|
|
103
|
+
Instead of looping per s2 label, this builds a joint overlap table in one pass
|
|
104
|
+
over the image, then checks the threshold condition vectorized.
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
i2 : np.ndarray
|
|
109
|
+
Array of candidate s2 labels (already filtered by threshold1).
|
|
110
|
+
s1 : np.ndarray
|
|
111
|
+
First segmentation mask.
|
|
112
|
+
n2 : np.ndarray
|
|
113
|
+
Relabeled second segmentation mask.
|
|
114
|
+
p1 : list
|
|
115
|
+
Region properties of s1 (from skimage.measure.regionprops).
|
|
116
|
+
threshold2 : float
|
|
117
|
+
Maximum fraction of any s1 cell's area that may overlap with an s2 region
|
|
118
|
+
before that s2 region is excluded.
|
|
119
|
+
"""
|
|
120
|
+
# Only consider pixels where both masks are non-zero
|
|
121
|
+
both = (s1 > 0) & (n2 > 0)
|
|
122
|
+
s1_vals = s1[both]
|
|
123
|
+
n2_vals = n2[both]
|
|
124
|
+
|
|
125
|
+
# Precompute s1 cell areas from regionprops
|
|
126
|
+
s1_areas = {p.label: p.area for p in p1}
|
|
127
|
+
|
|
128
|
+
# Count overlapping pixels for each (s1_label, s2_label) pair
|
|
129
|
+
pairs, counts = np.unique(np.stack([s1_vals, n2_vals], axis=1), axis=0, return_counts=True)
|
|
130
|
+
|
|
131
|
+
# Filter to only pairs where the s2 label is a candidate in i2
|
|
132
|
+
i2_set = set(i2.tolist())
|
|
133
|
+
mask = np.array([p[1] in i2_set for p in pairs])
|
|
134
|
+
pairs, counts = pairs[mask], counts[mask]
|
|
135
|
+
|
|
136
|
+
# Compute overlap fraction: intersection / s1 cell area
|
|
137
|
+
s1_areas_arr = np.array([s1_areas[p[0]] for p in pairs])
|
|
138
|
+
fractions = counts / s1_areas_arr
|
|
139
|
+
|
|
140
|
+
# Find s2 labels where any s1 cell exceeds the threshold
|
|
141
|
+
exceeds = pairs[fractions >= threshold2, 1]
|
|
142
|
+
exceeded_set = set(exceeds.tolist())
|
|
143
|
+
|
|
144
|
+
return np.array([label for label in i2 if label not in exceeded_set])
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _merge_segmentation(
|
|
148
|
+
s1: np.ndarray,
|
|
149
|
+
s2: np.ndarray,
|
|
150
|
+
label1: int = 1,
|
|
151
|
+
label2: int = 2,
|
|
152
|
+
threshold1: float = 1.0,
|
|
153
|
+
threshold2: float = 1.0,
|
|
154
|
+
) -> Tuple[np.ndarray, dict]:
|
|
99
155
|
"""
|
|
100
156
|
Merge two segmentation masks based on specified criteria.
|
|
101
157
|
|
|
158
|
+
Regions from s1 are always included (subject to threshold1). Regions from s2 are only
|
|
159
|
+
included if they do not overlap with s1 beyond the threshold1 and threshold2 criteria.
|
|
160
|
+
|
|
102
161
|
Parameters
|
|
103
162
|
----------
|
|
104
163
|
s1 : numpy.ndarray
|
|
105
|
-
First segmentation mask.
|
|
164
|
+
First segmentation mask. Regions here take priority over s2.
|
|
106
165
|
s2 : numpy.ndarray
|
|
107
|
-
Second segmentation mask.
|
|
166
|
+
Second segmentation mask. Regions are only added where they don't conflict with s1.
|
|
108
167
|
label1 : int
|
|
109
|
-
Label
|
|
168
|
+
Label assigned to s1 regions in the output mapping. Default is 1.
|
|
110
169
|
label2 : int
|
|
111
|
-
Label
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
Default is 1.0, meaning
|
|
115
|
-
|
|
170
|
+
Label assigned to s2 regions in the output mapping. Default is 2.
|
|
171
|
+
threshold1 : float, optional
|
|
172
|
+
Minimum fraction of an s2 region's area that must be preserved (i.e. not masked by s1)
|
|
173
|
+
for it to be included. Default is 1.0, meaning only fully non-overlapping s2 regions
|
|
174
|
+
are included.
|
|
175
|
+
threshold2 : float, optional
|
|
176
|
+
Maximum fraction of any s1 cell's area that may overlap with an s2 region before
|
|
177
|
+
that s2 region is excluded. Default is 1.0, meaning an s2 region is only excluded
|
|
178
|
+
if it fully contains an s1 cell.
|
|
116
179
|
Returns
|
|
117
180
|
-------
|
|
118
181
|
numpy.ndarray
|
|
119
|
-
Merged segmentation mask.
|
|
182
|
+
Merged segmentation mask with sequentially relabeled regions.
|
|
120
183
|
dict
|
|
121
|
-
Mapping of labels
|
|
122
|
-
|
|
184
|
+
Mapping of labels in the final mask to their origin: label1 (from s1) or label2 (from s2).
|
|
123
185
|
Notes
|
|
124
186
|
-----
|
|
125
|
-
This function assumes that
|
|
187
|
+
This function assumes that s1 and s2 are 2D segmentation masks with integer labels,
|
|
188
|
+
where 0 represents background.
|
|
126
189
|
"""
|
|
127
190
|
s1 = s1.squeeze()
|
|
128
191
|
s2 = s2.squeeze()
|
|
129
192
|
|
|
130
|
-
|
|
193
|
+
# Relabel s2 so its labels don't collide with s1's labels
|
|
194
|
+
n2, fmap, _ = relabel_sequential(s2, offset=s1.max() + 1)
|
|
195
|
+
|
|
196
|
+
# Build a combined mask: start with s1, then fill background pixels with s2 regions.
|
|
197
|
+
# This means s1 always takes priority — s2 only fills where s1 has no label.
|
|
131
198
|
s3 = s1.copy()
|
|
132
199
|
s3[np.logical_and(n2 > 0, s1 == 0)] = n2[np.logical_and(n2 > 0, s1 == 0)]
|
|
133
200
|
|
|
201
|
+
# Collect region properties and labels for filtering
|
|
134
202
|
p1 = regionprops(s1)
|
|
135
203
|
p2 = regionprops(n2)
|
|
136
204
|
p3 = regionprops(s3)
|
|
137
|
-
|
|
138
205
|
l1 = [p.label for p in p1]
|
|
139
206
|
l2 = [p.label for p in p2]
|
|
140
|
-
l3 = [p.label for p in p3]
|
|
207
|
+
l3 = [p.label for p in p3] # labels still present after combining s1 and s2
|
|
141
208
|
|
|
142
|
-
#
|
|
209
|
+
# i1: s1 regions that survived into the combined mask (should always be all of them)
|
|
210
|
+
# i2: s2 regions that survived into the combined mask (may be partial if they overlapped with s1)
|
|
143
211
|
i1 = list(set(l1) & set(l3))
|
|
144
212
|
i2 = list(set(l2) & set(l3))
|
|
145
213
|
|
|
214
|
+
# threshold1 filter: only keep s2 regions where the surviving area (in s3) is at least
|
|
215
|
+
# threshold1 fraction of the original s2 area. Regions heavily masked by s1 are dropped.
|
|
146
216
|
area_ratio = np.array([p.area for p in p3 if p.label in i2]) / np.array([p.area for p in p2 if p.label in i2])
|
|
147
|
-
i2 = np.array(i2)[area_ratio >=
|
|
148
|
-
|
|
217
|
+
i2 = np.array(i2)[area_ratio >= threshold1]
|
|
218
|
+
|
|
219
|
+
# threshold2 filter: exclude s2 regions that overlap too much with any single s1 cell.
|
|
220
|
+
# If an s2 region covers >= threshold2 of an s1 cell's area, it's considered conflicting
|
|
221
|
+
# and dropped — even if it passed the threshold1 check.
|
|
222
|
+
i2 = _exceeds_overlap(i2, s1, n2, p1, threshold2)
|
|
149
223
|
|
|
150
|
-
#
|
|
224
|
+
# Combine selected s1 and s2 region labels, then clean and relabel the mask
|
|
225
|
+
selected_cells = np.concatenate([np.array(i1), i2])
|
|
151
226
|
clean_mask = _remove_unlabeled_cells(s3, selected_cells)
|
|
152
|
-
final_mask, fmap,
|
|
153
|
-
|
|
227
|
+
final_mask, fmap, _ = relabel_sequential(clean_mask)
|
|
228
|
+
|
|
229
|
+
# Build a mapping from final mask labels back to their origin (label1 or label2)
|
|
230
|
+
mapping = dict(
|
|
231
|
+
zip(
|
|
232
|
+
[fmap[i] for i in selected_cells.astype(int)],
|
|
233
|
+
[label1] * len(i1) + [label2] * len(i2),
|
|
234
|
+
)
|
|
235
|
+
)
|
|
154
236
|
|
|
155
237
|
return final_mask, mapping
|
|
156
238
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spatialproteomics
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
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
|
|
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.7.10 → spatialproteomics-0.8.1}/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
|
|
File without changes
|
{spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics.egg-info/requires.txt
RENAMED
|
File without changes
|
{spatialproteomics-0.7.10 → spatialproteomics-0.8.1}/spatialproteomics.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|