Active_contours

Hi,

I’m in the process of trying to process some CT scans of bone and I’m needing to use scikit-image’s “active_contour” function. I have the function working (in terms of actually producing a result) but the result isn’t quite what I’m looking for.

This is the “original” image I’d like to apply the snake to:
trab_1.tiff (207.2 KB)

Here is the plotted inital ellipse and the snake produced:
image

This is the active_contour and the parameters I used:

snake = active_contour(gaussian_filter(trab, 3), ellipse, alpha=0.01, beta=0.01, w_line=1, w_edge=-10, gamma=0.001, max_iterations=2500, coordinates='rc')

I’m looking for something closer to:
trab_1.tiff (164.3 KB)
(except closer in, I drew this by hand and couldnt see under my finger

Any ideas what the best parameters should be? I feel like I’ve tried everything but clearly I’m missing something.
Thanks in advance for any help

Hey @steersteer it’s True that the result of the active contours is not what you would expect. I can try to see if changing the parameters improves the result. But just to be sure, is trab_1.tiff the real image on which you want to find the contour? Because on an image which is already binarized (or if not could be easily binarized using for example threshold_otsu), you can just call skimage.segmentation.find_boundaries to get the boundary https://scikit-image.org/docs/stable/api/skimage.segmentation.html#find-boundaries

Trab_1.tiff isn’t the original image, no. It actually started off as the trabecular region of a cow rib: SS_Bovine Rib #002_TIFF_1000.tif (3.2 MB) It has been segmented with thresholding etc. I don’t really want the actual outline of trab_1.tiff, I feel that would be too jagged. I’d quite like something like the red line I drew. Ultimately I need to make a 3d model from many slices of the bone sample so would like a nice “neat” outline to represent the boundary between this region and the surrounding bone if this makes sense?

Hi
@steersteer

I use image J.
I invite you to watch this topic.


``

Hi,
Thanks for the link, it doesn’t seem to work though. It says " Oops! That page doesn’t exist or is private."
I’d quite like to keep the progress within my code an not use imagej though

Hi @steersteer

Sorry for the broken link : I changed it and it should work now
I found this

scikit-image also offers convex hull.

https://scikit-image.org/docs/dev/auto_examples/edges/plot_convex_hull.html

I hope it helps.

Yes, I’ve tried using convex hull but that also isn’t really what I’m looking for. The convex nature of it means I lose a lot of the concave features, a lot of the detail. I’d really like to aim for the active contour method within my python code but it seems to “clip” past the outline of my image whereas I wanted it to create a soft-ish outline. Sort of like a partially vacuum sealed item.
Here is the less adulturated image I’m trying to do this with. I’d just like a sort of “average” outline of the shape:
trab_1.tiff (207.2 KB)
Using the active contour module in my code means I can perform this on all of the slices of my sample automatically.

Hi,

I think using active contours to do this is a bit of an overkill as active contours can be tough to adjust, are relatively slow, and tricky to use with binary images. You can probably get something approaching what you need with much simpler tools. You are essentially trying to get a continuous and smooth contour of your segmentation. You can do that e.g. by using a large Gaussian filtering, and then thresholding your image. Here’s what I managed to do:

import skimage.io
import skimage.filters
import skimage.morphology
import numpy as np
import matplotlib.pyplot as plt

#import image
image = skimage.io.imread('trab_1.tiff')

#dilate to avoid loosing small elements on edges
image_dil = skimage.morphology.binary_dilation(image, np.ones((5,5)))
#make the image smooth
image_gauss = skimage.filters.gaussian(image_dil, sigma=30)
#automatic threshold
image_th = skimage.filters.threshold_otsu(image_gauss)
#find contour
contour = skimage.measure.find_contours(image_gauss>image_th, level = 0.5)

#plot
fig, ax = plt.subplots(2,1, figsize = (10,10))
ax[0].imshow(image, cmap = 'gray')
ax[0].imshow(image_gauss>image_th, alpha = 0.5, cmap = 'Reds')
ax[1].imshow(image, cmap = 'gray')
ax[1].plot(contour[0][:,1],contour[0][:,0],'-r', linewidth = 5);

If you really want to enclose all parts including the smallest ones at the edge, you can then maybe further dilate that shape.

I hope this helps, and gives you some alternative ideas. If you still want to use active contours, the contour obtained with what I show here could be used as an initial starting point (instead of the ellipse) if you dilate it a bit to enclose all elements.

Guillaume

1 Like

Thanks, I’ll give that a try and consider it. Definitely seems like a better option right now. Regarding what you said about possibly using the contour as the starting point instead of the ellipse, I actually tried something similar. I tried to use the convex hull as the starting point but it never seemed to work, it just seemed to result in this:

. Even with one iteration the line would break up and move in random directions. This is 30 iterations:
I used this code to get the convex hull and the coordinates of the boundary:

 #getting hull
 hull = convex_hull_image(trab)

 #getting boundary of enlarged hull
 hull_boundary = find_boundaries(hull, connectivity=1, mode='outer', background=False)

 #Finds the shape (boundary) and lists the coordinates of the "True" pixels and lists in coordinate (N 2) array like (row column)
 hull_boundary_location = (np.argwhere(hull_boundary != False))

Just wanted to come back and say thanks for everyone’s help, I eventually went with Guillaume’s solution. I modified it a bit and got a much better result than I was hoping for!

2 Likes