Skip to content

Commit 5d11e1e

Browse files
committed
u-segment3D python version synced with GitLab version as of 6/4/2025.
1 parent 094591a commit 5d11e1e

File tree

8 files changed

+240
-39
lines changed

8 files changed

+240
-39
lines changed

README.md

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,20 @@
1212
+ [Windows](#windows)
1313
+ [MacOS](#macos)
1414
* [Getting Started](#getting-started)
15+
+ [Direct Method (cell gradients and probability maps as input) in a nutshell](#direct-method-cell-gradients-and-probability-maps-as-input-in-a-nutshell)
16+
+ [Indirect Method (instance segmentation masks as input) in a nutshell](#indirect-method-instance-segmentation-masks-as-input-in-a-nutshell)
17+
+ [Choosing an Indirect Method Distance Transform](#choosing-an-indirect-method-distance-transform)
1518
+ [Example Data](#example-data)
1619
+ [Example Scripts](#example-scripts)
1720
* [Overview of Library Features](#overview-of-library-features)
1821
* [Questions and Issues](#questions-and-issues)
1922
* [Danuser Lab Links](#danuser-lab-links)
2023
<!-- TOC end -->
2124

25+
#### June 2025
26+
- we have updated API to add compatibility with Cellpose4 changes. u-Segment3D should work with cellpose>=4.0.5. If this is not available from pip, you will need to install the github version of cellpose: `pip install git+https://www.github.com/mouseland/cellpose.git`
27+
- Note: In Cellpose4, the cellpose-SAM model ('cpsam') is the default. As of writing it seems 'cyto3', 'cyto2', 'cyto' etc. are no longer built-in. Therefore currently u-Segment3D will install as dependency **Cellpose3**. This will allow most flexibility for users.
28+
2229
#### :star2: Mar 2025 :star2:
2330
- u-Segment3D is available in PyPI and can be directly installed with `pip install u-Segment3D`
2431

@@ -28,7 +35,7 @@ Despite the name, the postprocessing and segmentation functions in u-Segment3D h
2835

2936
The primary motivation in developing u-Segment3D is to take advantage and leverage existing state-of-the-art 2D segmentation models for 3D segmentation without further data training.
3037

31-
It is associated with the BioArxiv paper, [**A general algorithm for consensus 3D cell segmentation from 2D segmented stacks**](https://doi.org/10.1101/2024.05.03.592249), *bioRxiv*, 2024, written by Felix Y. Zhou, Clarence Yapp, Zhiguo Shang, Md Torikul Islam, Benjamin Nanes, Edward Jenkins, Gabriel M. Gihana, Bo-Jui Chang, Andrew Weems, Michael Dustin, Sean Morrison, Reto Fiolka, Kevin Dean, Andrew Jamieson, Peter K. Sorger and [Gaudenz Danuser](https://www.danuserlab-utsw.org/).
38+
It is associated with the BioArxiv paper, [**Universal consensus 3D segmentation of cells from 2D segmented stacks**](https://www.biorxiv.org/content/10.1101/2024.05.03.592249v3), *bioRxiv*, 2025, written by Felix Y. Zhou, Zach Marin, Clarence Yapp, Qiongjing Zhou, Benjamin A. Nanes, Stephan Daetwyler, Andrew R. Jamieson, Md Torikul Islam, Edward Jenkins, Gabriel M. Gihana, Jinlong Lin, Hazel M.Borges, Bo-Jui Chang, Andrew Weems, Sean J. Morrison, Peter K. Sorger, Reto Fiolka, Kevin Dean, and [Gaudenz Danuser](https://www.danuserlab-utsw.org/).
3239

3340
The current library is still a work-in-progress. It is fully functional but documentation still require work and more tutorials are upcoming.
3441

@@ -157,6 +164,55 @@ segmentation3D, (probability3D, gradients3D) = uSegment3D.aggregate_2D_to_3D_seg
157164

158165
**NOTE: for both direct and indirect method above, you can use the empty list `[]` if you do not have segmentation or gradients in one or more orthoviews. This is how u-Segment3D permits 3D translation from any combination of orthoviews**
159166

167+
#### Choosing an Indirect Method Distance Transform
168+
Choosing a distance transform is a tradeoff for computational speed and accuracy. u-Segment3D provides distance transforms which specify a medial-axis skeleton implicitly and explicitly. If you have tubular, skeletal data, the implicit Euclidean Distance Transform works well. If you have convex cells, the explicit point-source transform, primarily the heat diffusion used also by Cellpose is good. For mixture of arbitrary morphologies, the explicit skeletal point-set transform is best, where u-Segment3D first estimates the medial-axis skeleton with binary morphological operations, then solves the heat diffusion equation using the skeletal points as sources.
169+
170+
The distance transform types available in u-Segmented are summarized visually in the figure.
171+
<p align="center">
172+
<img src="docs/imgs/different_available_transforms.JPG" width="1000"/>
173+
</p>
174+
<p align="center">
175+
<img src="docs/imgs/transforms_and_shape_suitability.JPG" width="1000"/>
176+
</p>
177+
178+
The distance transforms are specified by modifying the default parameters imported from parameters.py
179+
```
180+
import segment3D.parameters as uSegment3D_params
181+
182+
# Get the default parameters.
183+
indirect_aggregation_params = uSegment3D_params.get_2D_to_3D_aggregation_params()
184+
185+
"""
186+
# a) using Explicit Point Source Distance transforms (either heat diffusion or geodesic)
187+
"""
188+
indirect_aggregation_params['indirect_method']['dtform_method'] = 'cellpose_improve' # exact version of Cellpose's iterative heat diffusion
189+
indirect_aggregation_params['indirect_method']['dtform_method'] = 'fmm' # Fast marching solved geodesic variant
190+
indirect_aggregation_params['indirect_method']['edt_fixed_point_percentile'] = 0.01 # adjustment of where to place the medial centroid point. The higher this is, the more the centroid is sampled from only the ridges of the Euclidean Distance Transform.
191+
192+
"""
193+
# b)
194+
"""
195+
# 1. uses heat diffusion skeleton
196+
indirect_aggregation_params['indirect_method']['dtform_method'] = 'cellpose_skel'
197+
# 2. uses Fast marching method solved geodesic skeleton
198+
indirect_aggregation_params['indirect_method']['dtform_method'] = 'fmm_skel'
199+
200+
# For both, medial-axis skeleton is found by skimage.morphology.skeletonize. This can result in skeletons with erroneous or too much bifurcations. The following parameters control the smoothing of the skeleton by Gaussian filtering then rebinarizing with a threshold
201+
indirect_aggregation_params['indirect_method']['smooth_skel_sigma'] = 1
202+
indirect_aggregation_params['gradient_descent']['gradient_decay'] = 0.25
203+
204+
"""
205+
# c) Implicit Skeletal Distance Transform (Euclidean Distance Transform)
206+
"""
207+
indirect_aggregation_params['indirect_method']['dtform_method'] = 'edt' # Uses edt package (https://pypi.org/project/edt/) for fast evaluation.
208+
209+
"""
210+
The distance transforms can be further adjusted by modifying gradient decay rate.
211+
"""
212+
indirect_aggregation_params['gradient_descent']['gradient_decay'] = 0.25
213+
214+
```
215+
160216
### Example Data
161217
Please download the zipped folder containing example data from the [link](https://www.dropbox.com/scl/fo/le8rjbrohg9p29kebq25f/ANp7T7Z7bh4GsaphRmp7Qc0?rlkey=prgj9mxlluy8cl7x68ygtrigz&st=x89yerip&dl=0). The following examples assume you have unzipped the data to the `example_data/` directory of this repository, and is running the examples after installation from their current location in the repository. Please adjust filepaths accordingly, if otherwise.
162218

145 KB
Loading
106 KB
Loading

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ description="Generate consensus 3D segmentation from 2D segmented stacks"
2020
authors = [{name='Felix Y. Zhou', email='[email protected]'}]
2121
readme = "README.md"
2222
license={file = "LICENSE"}
23-
version = "0.1.0"
23+
version = "0.1.1"
2424
classifiers = [
2525
"Intended Audience :: Developers",
2626
"Operating System :: POSIX :: Linux",
@@ -32,7 +32,7 @@ classifiers = [
3232

3333
requires-python = ">=3.9"
3434
dependencies = [
35-
'cellpose',
35+
'cellpose<4.0',
3636
'cupy-cuda11x; sys_platform!="darwin"',
3737
'dask[complete]',
3838
'scikit-image',
@@ -41,6 +41,7 @@ dependencies = [
4141
'opencv-python-headless',
4242
'scipy',
4343
'numpy<2.0',
44+
'numba',
4445
'matplotlib',
4546
'seaborn',
4647
'trimesh',

segment3D/filters.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,47 @@ def filter_2d_label_slices(labelled,bg_label=0, minsize=10):
292292
return labelled_
293293

294294

295+
def detect_and_relabel_multi_component_labels(labels, min_size=100):
296+
297+
import skimage.measure as skmeasure
298+
import skimage.morphology as skmorph
299+
300+
labels_filt = np.zeros_like(labels)
301+
302+
max_id = 1
303+
304+
regionprops = skmeasure.regionprops(labels)
305+
306+
for cc, re in enumerate(regionprops[:]):
307+
308+
bbox = re.bbox
309+
coords = re.coords
310+
311+
x1,y1,z1 = bbox[:3]
312+
bbox_size = np.hstack(bbox[3:]) - np.hstack(bbox[:3])
313+
314+
mask = np.zeros(bbox_size, dtype=bool)
315+
mask[coords[:,0]-x1,
316+
coords[:,1]-y1,
317+
coords[:,2]-z1] = 1
318+
319+
mask = skmorph.remove_small_objects(mask, min_size=min_size)
320+
label_mask = skmeasure.label(mask>0)
321+
322+
label_ids = np.setdiff1d(np.unique(label_mask),0)
323+
324+
for ll in label_ids:
325+
coords_ll = np.argwhere(label_mask==ll)
326+
coords_ll = coords_ll + np.hstack(bbox[:3])
327+
328+
labels_filt[coords_ll[:,0],
329+
coords_ll[:,1],
330+
coords_ll[:,2]] = max_id
331+
max_id += 1
332+
333+
return labels_filt
334+
335+
295336
def filter_segmentations_axis(labels, window=3, min_count=1):
296337
""" according to the persistence along 1 direction. - this works and is relatively fast.
297338
"""
@@ -486,6 +527,48 @@ def remove_small_labels(labels, min_size=64):
486527
return labels
487528

488529

530+
def intensity_filter_labels(labels, intensity_img, threshold=None, auto_thresh_method='Otsu'):
531+
532+
import skimage.measure as skmeasure
533+
import skimage.filters as skfilters
534+
535+
labels_filt = labels.copy()
536+
regionprops = skmeasure.regionprops(labels)
537+
538+
all_region_I = []
539+
all_region_I_inds = []
540+
541+
for ind, reg in enumerate(regionprops):
542+
coords = reg.coords
543+
coords_I = np.nanmean(intensity_img[coords[:,0],
544+
coords[:,1],
545+
coords[:,2]])
546+
all_region_I.append(coords_I)
547+
all_region_I_inds.append(ind)
548+
549+
if len(all_region_I) > 0:
550+
all_region_I = np.hstack(all_region_I)
551+
all_region_I_inds = np.hstack(all_region_I_inds)
552+
553+
if threshold is None:
554+
if auto_thresh_method == 'Otsu':
555+
threshold = skfilters.threshold_otsu(all_region_I)
556+
elif auto_thresh_method == 'Mean':
557+
threshold = np.nanmean(all_region_I)
558+
elif auto_thresh_method == 'Median':
559+
threshold = np.nanmedian(all_region_I)
560+
else:
561+
threshold = skfilters.threshold_otsu(all_region_I)
562+
563+
remove_inds = all_region_I_inds[all_region_I<threshold]
564+
565+
for rr in remove_inds:
566+
coords = regionprops[rr].coords
567+
labels_filt[coords[:,0], coords[:,1], coords[:,2]] = 0 # zero out
568+
569+
return labels_filt
570+
571+
489572
def expand_masks(label_seeds, binary, dist_tform=None):
490573

491574
import skimage.measure as skmeasure

0 commit comments

Comments
 (0)