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.
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')
Thanks Jianxu for your explanation. By performance, do you mean accuracy?
I will get back to you if I have any questions.
yes, i mean accuracy