arcadia-microscopy-tools 0.2.4__py3-none-any.whl → 0.2.5__py3-none-any.whl
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.
- arcadia_microscopy_tools/__init__.py +1 -1
- arcadia_microscopy_tools/masks.py +51 -49
- {arcadia_microscopy_tools-0.2.4.dist-info → arcadia_microscopy_tools-0.2.5.dist-info}/METADATA +1 -1
- {arcadia_microscopy_tools-0.2.4.dist-info → arcadia_microscopy_tools-0.2.5.dist-info}/RECORD +6 -6
- {arcadia_microscopy_tools-0.2.4.dist-info → arcadia_microscopy_tools-0.2.5.dist-info}/WHEEL +0 -0
- {arcadia_microscopy_tools-0.2.4.dist-info → arcadia_microscopy_tools-0.2.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,7 +3,7 @@ from arcadia_microscopy_tools.channels import Channel
|
|
|
3
3
|
from arcadia_microscopy_tools.microscopy import MicroscopyImage
|
|
4
4
|
from arcadia_microscopy_tools.pipeline import ImageOperation, Pipeline, PipelineParallelized
|
|
5
5
|
|
|
6
|
-
__version__ = "0.2.
|
|
6
|
+
__version__ = "0.2.5"
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
9
9
|
"Channel",
|
|
@@ -34,8 +34,6 @@ DEFAULT_INTENSITY_PROPERTY_NAMES = [
|
|
|
34
34
|
"intensity_std",
|
|
35
35
|
]
|
|
36
36
|
|
|
37
|
-
OutlineExtractorMethod = Literal["cellpose", "skimage"]
|
|
38
|
-
|
|
39
37
|
|
|
40
38
|
def _process_mask(
|
|
41
39
|
mask_image: BoolArray | Int64Array,
|
|
@@ -68,7 +66,9 @@ def _extract_outlines_cellpose(label_image: Int64Array) -> list[Float64Array]:
|
|
|
68
66
|
Returns:
|
|
69
67
|
List of arrays, one per cell, containing outline coordinates in (y, x) format.
|
|
70
68
|
"""
|
|
71
|
-
|
|
69
|
+
outlines = outlines_list(label_image, multiprocessing=False)
|
|
70
|
+
# Cellpose returns (x, y) coordinates, flip to (y, x) to match standard (row, col) format
|
|
71
|
+
return [outline[:, [1, 0]] if len(outline) > 0 else outline for outline in outlines]
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
def _extract_outlines_skimage(label_image: Int64Array) -> list[Float64Array]:
|
|
@@ -91,8 +91,6 @@ def _extract_outlines_skimage(label_image: Int64Array) -> list[Float64Array]:
|
|
|
91
91
|
contours = ski.measure.find_contours(cell_mask, level=0.5)
|
|
92
92
|
if contours:
|
|
93
93
|
main_contour = max(contours, key=len)
|
|
94
|
-
# Flip from (x, y) to (y, x) to match cellpose format
|
|
95
|
-
main_contour = main_contour[:, [1, 0]]
|
|
96
94
|
outlines.append(main_contour)
|
|
97
95
|
else:
|
|
98
96
|
# Include empty array to maintain alignment with cell labels
|
|
@@ -112,7 +110,8 @@ class SegmentationMask:
|
|
|
112
110
|
{DAPI: array, FITC: array}
|
|
113
111
|
remove_edge_cells: Whether to remove cells touching image borders. Defaults to True.
|
|
114
112
|
outline_extractor: Outline extraction method ("cellpose" or "skimage").
|
|
115
|
-
Defaults to "cellpose".
|
|
113
|
+
Defaults to "cellpose". In practice, cellpose is ~2x faster but skimage handles
|
|
114
|
+
vertically oriented cell outlines better.
|
|
116
115
|
property_names: List of property names to compute. If None, uses
|
|
117
116
|
DEFAULT_CELL_PROPERTY_NAMES.
|
|
118
117
|
intensity_property_names: List of intensity property names to compute.
|
|
@@ -122,7 +121,7 @@ class SegmentationMask:
|
|
|
122
121
|
mask_image: BoolArray | Int64Array
|
|
123
122
|
intensity_image_dict: Mapping[Channel, UInt16Array] | None = None
|
|
124
123
|
remove_edge_cells: bool = True
|
|
125
|
-
outline_extractor:
|
|
124
|
+
outline_extractor: Literal["cellpose", "skimage"] = "cellpose"
|
|
126
125
|
property_names: list[str] | None = field(default=None)
|
|
127
126
|
intensity_property_names: list[str] | None = field(default=None)
|
|
128
127
|
|
|
@@ -133,8 +132,10 @@ class SegmentationMask:
|
|
|
133
132
|
raise TypeError("mask_image must be a numpy array")
|
|
134
133
|
if self.mask_image.ndim != 2:
|
|
135
134
|
raise ValueError("mask_image must be a 2D array")
|
|
136
|
-
if self.mask_image
|
|
135
|
+
if np.any(self.mask_image < 0):
|
|
137
136
|
raise ValueError("mask_image must have non-negative values")
|
|
137
|
+
if self.mask_image.max() == 0:
|
|
138
|
+
raise ValueError("mask_image contains no cells (all values are 0)")
|
|
138
139
|
|
|
139
140
|
# Validate intensity_image dict if provided
|
|
140
141
|
if self.intensity_image_dict is not None:
|
|
@@ -186,14 +187,20 @@ class SegmentationMask:
|
|
|
186
187
|
|
|
187
188
|
Returns:
|
|
188
189
|
List of arrays, one per cell, containing outline coordinates in (y, x) format.
|
|
189
|
-
|
|
190
|
+
|
|
191
|
+
Raises:
|
|
192
|
+
ValueError: If no cells are found in the mask.
|
|
193
|
+
|
|
194
|
+
Note:
|
|
195
|
+
The cellpose method is ~2x faster in general but skimage handles
|
|
196
|
+
vertically oriented cells/outlines better.
|
|
190
197
|
"""
|
|
191
198
|
if self.num_cells == 0:
|
|
192
|
-
|
|
199
|
+
raise ValueError("No cells found in mask. Cannot extract cell outlines.")
|
|
193
200
|
|
|
194
201
|
if self.outline_extractor == "cellpose":
|
|
195
202
|
return _extract_outlines_cellpose(self.label_image)
|
|
196
|
-
else: #
|
|
203
|
+
else: # must be "skimage" due to Literal type
|
|
197
204
|
return _extract_outlines_skimage(self.label_image)
|
|
198
205
|
|
|
199
206
|
@cached_property
|
|
@@ -205,24 +212,16 @@ class SegmentationMask:
|
|
|
205
212
|
|
|
206
213
|
For multichannel intensity images, property names are suffixed with the channel name:
|
|
207
214
|
- DAPI: "intensity_mean_DAPI"
|
|
208
|
-
- FITC: "
|
|
215
|
+
- FITC: "intensity_max_FITC"
|
|
209
216
|
|
|
210
217
|
Returns:
|
|
211
218
|
Dictionary mapping property names to arrays of values (one per cell).
|
|
212
|
-
|
|
219
|
+
|
|
220
|
+
Raises:
|
|
221
|
+
ValueError: If no cells are found in the mask.
|
|
213
222
|
"""
|
|
214
223
|
if self.num_cells == 0:
|
|
215
|
-
|
|
216
|
-
{property_name: np.array([]) for property_name in self.property_names}
|
|
217
|
-
if self.property_names
|
|
218
|
-
else {}
|
|
219
|
-
)
|
|
220
|
-
# Add empty intensity properties if requested
|
|
221
|
-
if self.intensity_image_dict and self.intensity_property_names:
|
|
222
|
-
for channel in self.intensity_image_dict:
|
|
223
|
-
for prop_name in self.intensity_property_names:
|
|
224
|
-
empty_props[f"{prop_name}_{channel.name}"] = np.array([])
|
|
225
|
-
return empty_props
|
|
224
|
+
raise ValueError("No cells found in mask. Cannot extract cell properties.")
|
|
226
225
|
|
|
227
226
|
# Extract morphological properties (no intensity image needed)
|
|
228
227
|
# Only compute extra properties if explicitly requested
|
|
@@ -232,12 +231,18 @@ class SegmentationMask:
|
|
|
232
231
|
if self.property_names and "volume" in self.property_names:
|
|
233
232
|
extra_props.append(volume)
|
|
234
233
|
|
|
234
|
+
# Compute cell properties
|
|
235
235
|
properties = ski.measure.regionprops_table(
|
|
236
236
|
self.label_image,
|
|
237
237
|
properties=self.property_names,
|
|
238
238
|
extra_properties=extra_props,
|
|
239
239
|
)
|
|
240
240
|
|
|
241
|
+
if "centroid-0" in properties:
|
|
242
|
+
properties["centroid_y"] = properties.pop("centroid-0")
|
|
243
|
+
if "centroid-1" in properties:
|
|
244
|
+
properties["centroid_x"] = properties.pop("centroid-1")
|
|
245
|
+
|
|
241
246
|
# Extract intensity properties for each channel
|
|
242
247
|
if self.intensity_image_dict and self.intensity_property_names:
|
|
243
248
|
for channel, intensities in self.intensity_image_dict.items():
|
|
@@ -248,7 +253,7 @@ class SegmentationMask:
|
|
|
248
253
|
)
|
|
249
254
|
# Add channel suffix to property names
|
|
250
255
|
for prop_name, prop_values in channel_props.items():
|
|
251
|
-
properties[f"{prop_name}_{channel.name}"] = prop_values
|
|
256
|
+
properties[f"{prop_name}_{channel.name.lower()}"] = prop_values
|
|
252
257
|
|
|
253
258
|
return properties
|
|
254
259
|
|
|
@@ -260,6 +265,9 @@ class SegmentationMask:
|
|
|
260
265
|
Array of shape (num_cells, 2) with centroid coordinates.
|
|
261
266
|
Each row is [y_coordinate, x_coordinate] for one cell.
|
|
262
267
|
Returns empty (0, 2) array with warning if "centroid" not in property_names.
|
|
268
|
+
|
|
269
|
+
Raises:
|
|
270
|
+
ValueError: If no cells are found in the mask.
|
|
263
271
|
"""
|
|
264
272
|
if self.property_names and "centroid" not in self.property_names:
|
|
265
273
|
warnings.warn(
|
|
@@ -270,8 +278,8 @@ class SegmentationMask:
|
|
|
270
278
|
)
|
|
271
279
|
return np.array([]).reshape(0, 2)
|
|
272
280
|
|
|
273
|
-
yc = self.cell_properties["
|
|
274
|
-
xc = self.cell_properties["
|
|
281
|
+
yc = self.cell_properties["centroid_y"]
|
|
282
|
+
xc = self.cell_properties["centroid_x"]
|
|
275
283
|
return np.array([yc, xc], dtype=float).T
|
|
276
284
|
|
|
277
285
|
def convert_properties_to_microns(
|
|
@@ -281,34 +289,28 @@ class SegmentationMask:
|
|
|
281
289
|
"""Convert cell properties from pixels to microns.
|
|
282
290
|
|
|
283
291
|
Applies appropriate scaling factors based on the dimensionality of each property:
|
|
284
|
-
- Linear measurements (1D): multiplied by pixel_size_um
|
|
285
|
-
- Area measurements (2D): multiplied by pixel_size_um
|
|
286
|
-
- Volume measurements (3D): multiplied by pixel_size_um
|
|
287
|
-
- Dimensionless properties: unchanged
|
|
292
|
+
- Linear measurements (1D): multiplied by pixel_size_um, keys suffixed with "_um"
|
|
293
|
+
- Area measurements (2D): multiplied by pixel_size_um², keys suffixed with "_um2"
|
|
294
|
+
- Volume measurements (3D): multiplied by pixel_size_um³, keys suffixed with "_um3"
|
|
295
|
+
- Dimensionless properties: unchanged, keys unchanged
|
|
288
296
|
|
|
289
297
|
Args:
|
|
290
298
|
pixel_size_um: Pixel size in microns.
|
|
291
299
|
|
|
292
300
|
Returns:
|
|
293
|
-
Dictionary with
|
|
301
|
+
Dictionary with keys renamed to include units and values
|
|
294
302
|
converted to micron units where applicable.
|
|
295
303
|
|
|
296
304
|
Note:
|
|
297
|
-
Properties like 'label', 'circularity', 'eccentricity', 'solidity',
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
Tensor properties (inertia_tensor, inertia_tensor_eigvals) are scaled
|
|
302
|
-
|
|
305
|
+
Properties like 'label', 'circularity', 'eccentricity', 'solidity', and 'orientation'
|
|
306
|
+
are dimensionless and remain unchanged. Intensity properties (intensity_mean,
|
|
307
|
+
intensity_max, etc.) are also dimensionless and remain unchanged. Centroid coordinates
|
|
308
|
+
(centroid_y, centroid_x) remain in pixel coordinates as they represent image positions.
|
|
309
|
+
Tensor properties (inertia_tensor, inertia_tensor_eigvals) are scaled as 2D quantities
|
|
310
|
+
(pixel_size_um²) and suffixed with "_um2".
|
|
303
311
|
"""
|
|
304
312
|
# Define which properties need which scaling
|
|
305
|
-
linear_properties = {
|
|
306
|
-
"perimeter",
|
|
307
|
-
"axis_major_length",
|
|
308
|
-
"axis_minor_length",
|
|
309
|
-
"centroid-0",
|
|
310
|
-
"centroid-1",
|
|
311
|
-
}
|
|
313
|
+
linear_properties = {"perimeter", "axis_major_length", "axis_minor_length"}
|
|
312
314
|
area_properties = {"area", "area_convex"}
|
|
313
315
|
volume_properties = {"volume"}
|
|
314
316
|
tensor_properties = {"inertia_tensor", "inertia_tensor_eigvals"}
|
|
@@ -316,13 +318,13 @@ class SegmentationMask:
|
|
|
316
318
|
converted = {}
|
|
317
319
|
for prop_name, prop_values in self.cell_properties.items():
|
|
318
320
|
if prop_name in linear_properties:
|
|
319
|
-
converted[prop_name] = prop_values * pixel_size_um
|
|
321
|
+
converted[f"{prop_name}_um"] = prop_values * pixel_size_um
|
|
320
322
|
elif prop_name in area_properties:
|
|
321
|
-
converted[prop_name] = prop_values * (pixel_size_um**2)
|
|
323
|
+
converted[f"{prop_name}_um2"] = prop_values * (pixel_size_um**2)
|
|
322
324
|
elif prop_name in volume_properties:
|
|
323
|
-
converted[prop_name] = prop_values * (pixel_size_um**3)
|
|
325
|
+
converted[f"{prop_name}_um3"] = prop_values * (pixel_size_um**3)
|
|
324
326
|
elif prop_name in tensor_properties:
|
|
325
|
-
converted[prop_name] = prop_values * (pixel_size_um**2)
|
|
327
|
+
converted[f"{prop_name}_um2"] = prop_values * (pixel_size_um**2)
|
|
326
328
|
else:
|
|
327
329
|
# Intensity-related, dimensionless, or label - no conversion
|
|
328
330
|
converted[prop_name] = prop_values
|
{arcadia_microscopy_tools-0.2.4.dist-info → arcadia_microscopy_tools-0.2.5.dist-info}/RECORD
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
arcadia_microscopy_tools/__init__.py,sha256=
|
|
1
|
+
arcadia_microscopy_tools/__init__.py,sha256=HmMHWrlDh8pAB6f_an39kuDiujpPl4UBAvL0eBd1Bdo,440
|
|
2
2
|
arcadia_microscopy_tools/blending.py,sha256=Y5xuius1tHRKIEfueUjZ7qGlBK02arEYqrzRRhEdSTI,7112
|
|
3
3
|
arcadia_microscopy_tools/channels.py,sha256=sE54mJoJnFIMowO_qRG4lx-s_LOaVO10tuxpuVadJg8,6854
|
|
4
|
-
arcadia_microscopy_tools/masks.py,sha256=
|
|
4
|
+
arcadia_microscopy_tools/masks.py,sha256=112486kqgMwNxLvTk9FBj8Rdo-cMOaOpToFD1_RwUQ0,15800
|
|
5
5
|
arcadia_microscopy_tools/metadata_structures.py,sha256=Bb4UXgiNuJcOITNGV_4hGR09HaN8Wt7heET4bXmNcw0,3481
|
|
6
6
|
arcadia_microscopy_tools/microplate.py,sha256=df6HTeQdYQRD7rYKubx8_FWOZ1BbJVoyg7lYySHJQOU,8298
|
|
7
7
|
arcadia_microscopy_tools/microscopy.py,sha256=gPvMVKukGkBY74Ajy71JOd7DfZOoCEfiE40qkBW-C-I,11313
|
|
@@ -25,7 +25,7 @@ arcadia_microscopy_tools/tests/data/example-pbmc.nd2,sha256=gqVP7cGePBJk45xRpyXa
|
|
|
25
25
|
arcadia_microscopy_tools/tests/data/example-timelapse.nd2,sha256=KHCubkVWmkRRmJabhfINx_aTwNi5nVUl-IiYNKQsJ9Y,827392
|
|
26
26
|
arcadia_microscopy_tools/tests/data/example-zstack.nd2,sha256=j70DrFhRTwRgzAAJivVM3mCho05YVgsqJwTmPBobRYo,606208
|
|
27
27
|
arcadia_microscopy_tools/tests/data/known-metadata.yml,sha256=_ZIE04MnoLpZtG-6e8ZytYnmAkGh0Q7-2AwSP3v6rQk,1886
|
|
28
|
-
arcadia_microscopy_tools-0.2.
|
|
29
|
-
arcadia_microscopy_tools-0.2.
|
|
30
|
-
arcadia_microscopy_tools-0.2.
|
|
31
|
-
arcadia_microscopy_tools-0.2.
|
|
28
|
+
arcadia_microscopy_tools-0.2.5.dist-info/METADATA,sha256=y2JLq5xdW2Anen9vmLV0LN4nOOrPSacQLvAmj3Q6IzA,5007
|
|
29
|
+
arcadia_microscopy_tools-0.2.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
30
|
+
arcadia_microscopy_tools-0.2.5.dist-info/licenses/LICENSE,sha256=5pPae5U0NNXysjBv3vjoquhhoCqTTi1Zh0SehM_IXHI,1072
|
|
31
|
+
arcadia_microscopy_tools-0.2.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|