Allen Cell Discussion Forum

Dilation function speed

I noticed that the dilation function used in segmenting particles appears to take ~2 hours even though it says 2-3 minutes in the notebook. I have about 2000- 5000 ‘particles’ in my zstack. I wasn’t able to test the tutorial notebook directly since the data file and path does not exist. Also can you elaborate - or point me in the right direction - on what the purpose of the dilation function is? Thanks!

If I understand correctly, you are referring this step:

Seed = dilation(peak_local_max(struct_img,labels=label(Mask), min_distance=2, indices=False), selem=ball(1))

The actual time consuming function is “peak_local_max”.

The goal of this step is to separate tightly touching particles. For example, when you have two particles very close to each other, the S2 or S3 filter may segment the two particles as one. In this situation, if you care about accurately counting the number of particles, we want to separate them properly. To do this, we can take advantage of the bright center of each particle (this is what “peak_local_max” does) and use watershed to cut the objects based on the detected seeds (i.e., local max).

The current code is the jupyter notebook is what we used in production, which achieved best performance on our data in our test. However, we do have two alternative ways to cut the touching particles in a much faster way. I will ask our engineer to put it up. For those, you have to take a close look at the results and make sure it achieves your desired accuracy.


1 Like


If splitting falsely merged particles (due to close distance) step is needed and generating seed takes a long time, there is a little trick that you can try.

First, find out approximate size( or volume) of merged particles, then you can filter out the correctly segmented particles with size filter. After, generate the seeds using filtered image which will give you the seeds for merged particles only. Use this seeds to perform watershed for splitting falsely merged particles.

Quick demo code is like this:

# indexing each particles
labels = label(seg_img, connectivity=seg_img.ndim) # seg_img is segmentation after dot filter

small_dot_th = small_thresh # small_thresh is an approximate volume for merged segmentation. use lower bound I would try with 60

dot_bins = np.bincount(labels.flat)[1:]

# target is merged particle segmentatons
target = np.zeros(seg_img.shape)
for idx, dot in enumerate(dot_bins):
    if dot >= small_dot_th:
        target[labels == idx+1] = 1 

hold_out = seg_img - target

# getting mask for local max
target = binary_opening(target) # break weak boundary
img_for_lmax =  np.multiply(struct_img, target) # use target to get ROI 

# breaks falsely merged segmentation ###############################
l_max = peak_local_max(img_for_lmax, min_distance=3, indices=False).astype("int8")
watershed_map = -1*distance_transform_edt(l_max)
im_watershed = watershed(watershed_map, mask=target, watershed_line=True)
watershed_bin = im_watershed > 0

# combine separated particles with size filtered segmentations from previous step
combined = np.logical_xor(hold_out>0, watershed_bin).astype('int8')
1 Like

Thanks Jianxu for your explanation. By performance, do you mean accuracy?

Thanks Hyeonwoo,
I will get back to you if I have any questions.

yes, i mean accuracy