Я хочу разместить несколько точек на изображениях, чтобы найти их истинную интенсивность в многолюдной области, где локальная коррекция фона не удалась.
Мой подход состоит в том, чтобы просто выделить локальную область изображения и подогнать ее. Проблема в том, что подгонка не дает каких-либо полезных результатов, а только по умолчанию с начальными параметрамиДобавление границ, чтобы помочь в этом, приводит к тому, что подгонка вообще не сходится.
Что я делаю не так?
Код:
import scipy.optimize as opt
import numpy as np
import matplotlib.pyplot as plt
import skimage.feature
from collections import namedtuple
import skimage.io
def gaussian_2d(
xy_array, amplitude, pos_x, pos_y, sigma_x, sigma_y, rotation, offset
):
"""
Expression for a 2D gaussian function with variance in both x and y
"""
x, y = xy_array
a = (np.cos(rotation) ** 2) / (2 * sigma_x ** 2) + (
np.sin(rotation) ** 2
) / (2 * sigma_y ** 2)
b = -(np.sin(2 * rotation)) / (4 * sigma_x ** 2) + (
np.sin(2 * rotation)
) / (4 * sigma_y ** 2)
c = (np.sin(rotation) ** 2) / (2 * sigma_x ** 2) + (
np.cos(rotation) ** 2
) / (2 * sigma_y ** 2)
g = amplitude * np.exp(
-(
a * ((x - pos_x) ** 2)
+ 2 * b * (x - pos_x) * (y - pos_y)
+ c * ((y - pos_y) ** 2)
)
)
g += offset
return g.ravel()
def fit_gaussian_spots(x_guess, y_guess, array):
Params = namedtuple(
"Parameters", "amp, x, y, sigma_x, sigma_y, rotation, offset"
)
eps = 1e-8
initial_guess = Params(
amp=1, x=x_guess, y=y_guess, sigma_x=1, sigma_y=1, rotation=0, offset=0
)
# Bounds makes it even harder to converge
min_bounds = Params(
amp=eps,
x=x_guess * 0.5,
y=y_guess * 0.5,
sigma_x=eps,
sigma_y=eps,
rotation=-np.inf,
offset=eps,
)
max_bounds = Params(
amp=np.max(array),
x=x_guess * 1.5,
y=y_guess * 1.5,
sigma_x=np.inf,
sigma_y=np.inf,
rotation=2 * np.pi,
offset=np.max(array),
)
try:
X, Y = create_grid(*array.shape)
popt, pcov = opt.curve_fit(
f=gaussian_2d,
xdata=(X, Y),
ydata=array.ravel(),
p0=initial_guess,
# bounds=(min_bounds, max_bounds),
)
popt = Params(*np.round(popt))
except ValueError:
print("fit didn't converge!")
popt, pcov = None, None
return popt, pcov
def create_grid(h, w):
"""
Creates a grid of x and y points to fit and evaluate over
"""
x = np.arange(0, w, 1)
y = np.arange(0, h, 1)
x, y = np.meshgrid(x, y)
return x, y
def evaluate_gaussian(x, y, popt):
"""
Evaluates gaussian in coordinate positions.
NOTE: this is not necessary for extracting intensity,
as the pure signal is fitted as the amplitude.
"""
z = gaussian_2d((x, y), *popt)
return z
if __name__ == "__main__":
# Create x and y indices
np.random.seed(4)
h, w = 200, 200
x, y = create_grid(h=h, w=w)
# create data
img = []
for _ in range(10):
randx = np.random.randint(10, w - 10)
randy = np.random.randint(10, h - 10)
amp = 100
d = gaussian_2d(
xy_array=(x, y),
amplitude=amp,
pos_x=randx,
pos_y=randy,
sigma_x=9,
sigma_y=3,
rotation=3,
offset=0,
)
# d = d + np.random.normal(0, 5, d.shape) # add noise
# d += 100 # add offset
img.append(d)
img = np.sum(img, axis=0)
img = img.reshape(h, w)
print("max intensity: {:.2f}".format(img.max()))
# Detect soem possible spots first
spots = skimage.feature.peak_local_max(img, num_peaks=20, min_distance=10)
fig, ax = plt.subplots(ncols=2)
h, w = img.shape
local_area = 20
fit = []
# skimage returns rows, columns (y,x) while matplotlib operates in (x,y)
for idx, (pre_y, pre_x) in enumerate(spots):
# Fit gaussian in local area
popt, pcov = fit_gaussian_spots(
x_guess=pre_x,
y_guess=pre_y,
# Avoid falling off the edge of the image
array=img[
max(pre_y - local_area, 0) : pre_y + local_area,
max(pre_x - local_area, 0) : pre_x + local_area,
],
)
if popt is None:
continue
print(popt)
ax[0].add_patch(
plt.Circle(
(pre_x, pre_y), 5, linewidth=0.5, fill=False, color="red"
)
)
ax[1].add_patch(
plt.Rectangle(
(pre_x - local_area, pre_y - local_area),
width=local_area * 2,
height=local_area * 2,
fill=False,
color="yellow",
)
)
fit.append(evaluate_gaussian(x, y, popt))
fit = np.sum(fit, axis=0)
ax[0].set_title("true")
ax[0].imshow(
img, origin="bottom", extent=(x.min(), x.max(), y.min(), y.max())
)
ax[1].set_title("predicted")
ax[1].imshow(
fit.reshape(img.shape),
origin="bottom",
extent=(x.min(), x.max(), y.min(), y.max()),
)
plt.show()