Skip to content

Commit fd31e4d

Browse files
committed
Use control point derivatives for active contours
1 parent 2da3f72 commit fd31e4d

2 files changed

Lines changed: 40 additions & 8 deletions

File tree

‎examples/plot_active_contours.py‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import numpy as np
1313
import scipy
1414
import skimage
15-
1615
import splinebox.basis_functions
1716
import splinebox.spline_curves
1817

@@ -78,6 +77,10 @@
7877
# The energy function consist of two terms:
7978
# (1) Image energy: measures the pixel values below the spline.
8079
# (2) Internal energy: penalises curvy spline
80+
# Both energy terms can be differentiated analytically with respect to the
81+
# control points: the derivative of the spline itself is computed with
82+
# ``derivative_wrt_control_points`` and the gradient of the internal (curvature)
83+
# energy with ``derivative_of_curvature_energy_wrt_control_points``.
8184
# A detailed description of the math can be found in :ref:`theory/active_contour`.
8285

8386
# Store intermediate contours
@@ -97,7 +100,10 @@
97100

98101
image_energy_gradients = partial_derivs.T @ img_gradients
99102

100-
internal_energy_gradients = np.mean(spline.derivative_of_norm_squared_wrt_control_points(t, derivative=2), axis=0)
103+
# The exact gradient of the continuous curvature energy; dividing by M turns
104+
# the integral over the parameter range into an average, matching the scale
105+
# of the image energy term above.
106+
internal_energy_gradients = spline.derivative_of_curvature_energy_wrt_control_points() / spline.M
101107

102108
gradients = -image_energy_gradients + internal_energy_gradients / 2
103109

‎examples/plot_peptides.py‎

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,27 +75,53 @@
7575
# %%
7676
# 5. Refine Spline
7777
# ----------------
78+
# We refine the spline by minimising an energy function that combines the image
79+
# energy (the mean pixel value under the spline) and an internal energy that
80+
# penalises curvy splines. Instead of using a derivative-free optimiser, we
81+
# supply the analytic derivatives of the energy with respect to the control points,
82+
# which splinebox computes with ``derivative_wrt_control_points`` and
83+
# ``derivative_of_norm_squared_wrt_control_points``.
84+
# The image energy term also requires the spatial gradients of the image.
85+
# We precompute them on a lightly Gaussian smoothed version of the image to
86+
# reduce the effect of noise. Note that we interpolate the pixel values on a
87+
# float version of the image because ``map_coordinates`` truncates interpolated
88+
# values to integers for integer input images.
89+
SIGMA = 1
90+
img_gy = scipy.ndimage.gaussian_filter(img.astype(float), SIGMA, order=[1, 0], mode="nearest")
91+
img_gx = scipy.ndimage.gaussian_filter(img.astype(float), SIGMA, order=[0, 1], mode="nearest")
7892

7993

8094
def loss_function(control_points, alpha):
8195
spline.control_points = control_points.reshape((-1, 2))
8296
coords = spline(t)
83-
pixel_values = scipy.ndimage.map_coordinates(img, coords.T)
97+
pixel_values = scipy.ndimage.map_coordinates(img.astype(float), coords.T)
8498
image_energy = np.mean(pixel_values)
85-
internal_energy = np.mean(spline(t, derivative=2) ** 2)
99+
internal_energy = np.mean(np.sum(spline(t, derivative=2) ** 2, axis=1))
86100
energy = -1 * image_energy + alpha * internal_energy
87101
return energy
88102

89103

104+
def jacobian(control_points, alpha):
105+
spline.control_points = control_points.reshape((-1, 2))
106+
coords = spline(t)
107+
dy = scipy.ndimage.map_coordinates(img_gy, coords.T, order=1)
108+
dx = scipy.ndimage.map_coordinates(img_gx, coords.T, order=1)
109+
img_gradients = np.stack([dy, dx], axis=-1)
110+
image_energy_gradients = spline.derivative_wrt_control_points(t).T @ img_gradients / len(t)
111+
internal_energy_gradients = np.mean(spline.derivative_of_norm_squared_wrt_control_points(t, derivative=2), axis=0)
112+
return (-image_energy_gradients + alpha * internal_energy_gradients).flatten()
113+
114+
90115
initial_control_points = initial_spline.control_points
91116
spline = initial_spline.copy()
92-
scipy.optimize.minimize(
117+
result = scipy.optimize.minimize(
93118
loss_function,
94119
initial_control_points.flatten(),
95-
args=(0.3,),
96-
method="Powell",
97-
tol=0.01,
120+
args=(0.04,),
121+
method="L-BFGS-B",
122+
jac=jacobian,
98123
)
124+
spline.control_points = result.x.reshape((-1, 2))
99125

100126
vals = spline(t)
101127
knots = spline.knots

0 commit comments

Comments
 (0)