spatialproteomics 0.8.0__tar.gz → 0.8.2__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.0/spatialproteomics.egg-info → spatialproteomics-0.8.2}/PKG-INFO +1 -1
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/pyproject.toml +1 -1
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/pp/preprocessing.py +21 -6
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/pp/utils.py +104 -22
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2/spatialproteomics.egg-info}/PKG-INFO +1 -1
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/LICENSE +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/README.md +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/setup.cfg +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/__init__.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/base_logger.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/constants.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/container.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/image_container/ImageContainer.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/image_container/__init__.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/la/__init__.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/la/label.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/la/utils.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/nh/__init__.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/nh/neighborhood.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/nh/utils.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/pl/__init__.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/pl/plot.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/pl/utils.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/pp/__init__.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/pp/intensity.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/sd/__init__.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/sd/utils.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/tl/__init__.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/tl/tool.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics/tl/utils.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics.egg-info/SOURCES.txt +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics.egg-info/dependency_links.txt +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics.egg-info/requires.txt +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics.egg-info/top_level.txt +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/tests/test_container.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/tests/test_integration.py +0 -0
- {spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/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.2
|
|
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
|
|
@@ -1799,12 +1799,21 @@ class PreprocessingAccessor:
|
|
|
1799
1799
|
layer_key: str,
|
|
1800
1800
|
key_added: str = "_merged_segmentation",
|
|
1801
1801
|
labels: Optional[List[str]] = None,
|
|
1802
|
-
|
|
1802
|
+
threshold1: float = 0.7,
|
|
1803
|
+
threshold2: float = 0.8,
|
|
1803
1804
|
):
|
|
1804
1805
|
"""
|
|
1805
|
-
Merge segmentation masks.
|
|
1806
|
-
|
|
1807
|
-
|
|
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
|
|
1808
1817
|
|
|
1809
1818
|
Parameters
|
|
1810
1819
|
----------
|
|
@@ -1814,7 +1823,9 @@ class PreprocessingAccessor:
|
|
|
1814
1823
|
The name of the new segmentation mask to be added to the xarray object. Default is "_merged_segmentation".
|
|
1815
1824
|
labels : Optional[List[str]]
|
|
1816
1825
|
Optional. Labels corresponding to each segmentation mask. If provided, must match number of arrays.
|
|
1817
|
-
|
|
1826
|
+
threshold1: float
|
|
1827
|
+
Optional. Threshold for merging cells. Default is 0.7.
|
|
1828
|
+
threshold2: float
|
|
1818
1829
|
Optional. Threshold for merging cells. Default is 0.8.
|
|
1819
1830
|
|
|
1820
1831
|
Returns
|
|
@@ -1834,6 +1845,9 @@ class PreprocessingAccessor:
|
|
|
1834
1845
|
- The merging process starts with merging the biggest cells first, then the smaller ones.
|
|
1835
1846
|
- Disconnected cells in the input are handled based on the specified method.
|
|
1836
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
|
+
|
|
1837
1851
|
# Make sure layer_key is a list internally
|
|
1838
1852
|
if isinstance(layer_key, str):
|
|
1839
1853
|
layer_keys = [layer_key]
|
|
@@ -1913,7 +1927,8 @@ class PreprocessingAccessor:
|
|
|
1913
1927
|
next_segmentation.squeeze(),
|
|
1914
1928
|
label1=label_1,
|
|
1915
1929
|
label2=label_2,
|
|
1916
|
-
|
|
1930
|
+
threshold1=threshold1,
|
|
1931
|
+
threshold2=threshold2,
|
|
1917
1932
|
)
|
|
1918
1933
|
|
|
1919
1934
|
# Update label mapping
|
|
@@ -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], dtype=bool)
|
|
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.8.
|
|
3
|
+
Version: 0.8.2
|
|
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.8.0 → spatialproteomics-0.8.2}/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
|
|
File without changes
|
|
File without changes
|
{spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
{spatialproteomics-0.8.0 → spatialproteomics-0.8.2}/spatialproteomics.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|