Optimize segmentation algorithms#

The Napari plugin napari-workflow-optimizer allows optimizing image segmentation workflows in a convenient way.

from napari_workflow_optimizer import JaccardLabelImageOptimizer, Workflow

from skimage.io import imread
import pyclesperanto_prototype as cle
import matplotlib.pyplot as plt

To use the optimizer, we need to define our workflow using a Workflow object. It works like a dictionary with image names as keywords and list of operations and parameters as values. The underlying infrastructure is based on dask graphs.

w = Workflow()
# define background subtraction
w.set("blurred", cle.gaussian_blur, "input", sigma_x=5, sigma_y=5)
# define segmentation
w.set("binarized", cle.threshold_otsu, "blurred")
w.set("labeled", cle.label, "binarized")

These workflows can be explored. For example we can read from it which image parameters are needed to get started.

w.roots()
['input']

We can also determine what the result images of the workflow are.

w.leafs()
['labeled']

After setting the inputs, we can read ask the workflow to compute results.

w.set("input", imread("../../data/blobs.tif"))
result = w.get("labeled")

cle.imshow(result, labels=True)
../_images/cab4e38e044535463178fcf1e62cd36e32965a28fc1ce952db31796b70694b0d.png

For optimization of such a workflow, we need a ground truth annotation image. A sparse annotation of some objects is enough for this.

ground_truth = imread("../../data/blobs_sparse_labels.tif")
cle.imshow(ground_truth, labels=True)
../_images/6d517202339c1dd6f59a9e24fd84fc8fd2f0acd0d2aba8012dd49289052ea53f.png

The JaccardLabelImageOptimizer consumes a workflow and can optimize parameters with respect to a sparse ground truth. Its optimize function returns a set of parameters which corresponds to all numeric parameters of the workflow.

jlio = JaccardLabelImageOptimizer(w)
best_param = jlio.optimize("labeled", ground_truth, maxiter=20)
best_param
array([4.80023582e+00, 4.44562637e+00, 3.84861161e-04])

We can then use the optimizer for setting these parameters to the workflow and afterwards read where in the workflow the paramweters were set.

jlio.set_numeric_parameters(best_param)

# before printing the workflow, we quickly remove the input image
w.remove('input')
print(w)
Workflow:
blurred <- (<function gaussian_blur at 0x0000023723085A60>, 'input', None, 4.8002358210977345, 4.445626372447739, 0.00038486116050511713)
binarized <- (<function threshold_otsu at 0x00000237232EADC0>, 'blurred')
labeled <- (<function connected_components_labeling_box at 0x000002372316AB80>, 'binarized')

After setting the input again, we can also apply the workflow to the image and inspect the result visually.

w.set("input", imread("../../data/blobs.tif"))
cle.imshow(w.get("labeled"), labels=True)
../_images/9670bee784477649f403b50b1b7d253b75fa69d0f8cbcf5b779cd6bd7dba88f7.png

Further insights in optimization#

The JaccardLabelImageOptimizer also allows to take a look at the optimization process applied earlier.

attempt, quality = jlio.get_plot()

plt.plot(attempt, quality)
[<matplotlib.lines.Line2D at 0x23735c22c70>]
../_images/9a651798ca88ed0ba7674d01f32cc6492c4842b881cac216f5d56edf5751055c.png