Example notebook

In this notebook we show how to use the main methods of astroalign.

Full documentation available at https://astroalign.readthedocs.io/.

This notebook was created for astroalign version 2.0

In [1]:
import astroalign as aa
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

np.random.seed(seed=12)
aa.__version__
Out[1]:
'2.0'

Create images

We will create two images of the same portion of the sky. Our source image will end up being (400, 400) pixels, have 10 stars randomly distributed. It will have a Gaussian PSF with a sigma of 2 pixels and Gaussian background noise.

The target image will be (200, 200) pixels, will be rotated 30 degrees clockwise with respect to source image. It will have a Gaussian PSF with a sigma of 1.5 pixels and Gaussian background noise.

Notice that source image is made so to have better resolution (and seeing) than the target.

In [2]:
h, w = img_shape = (200, 200)
n_stars = 10
pos_x = np.random.randint(10, w - 10, n_stars)
pos_y = np.random.randint(10, h - 10, n_stars)
fluxes = 200.0 + np.random.rand(n_stars) * 300.0

img = np.zeros(img_shape)
for x, y, f in zip(pos_x, pos_y, fluxes):
    img[x, y] = f

# Let's rotate and make the image twice as big
from scipy.ndimage import rotate, zoom
img_rotated = rotate(img, angle=30.0, reshape=False)
img_rotated = zoom(img_rotated, 1.5, order=2)

# Let's add a Gaussian PSF response with different seeing for both images
from scipy.ndimage.filters import gaussian_filter
img = gaussian_filter(img, sigma=2.0, mode='constant')
img_rotated = gaussian_filter(img_rotated, sigma=1.5, mode='constant')

# Let's add some noise to the images
noise_dc = 5.0
noise_std = np.sqrt(noise_dc)
img += np.random.normal(loc=noise_dc, scale=noise_std, size=img.shape)
img_rotated += np.random.normal(loc=noise_dc, scale=noise_std, size=img_rotated.shape)

Solve registration with astroalign

In [3]:
img_aligned, footprint = aa.register(img, img_rotated)

Plot results

In [4]:
fig, axes = plt.subplots(2, 2, figsize=(10, 10))
axes[0, 0].imshow(img, cmap='gray', interpolation='none', origin='lower')
axes[0, 0].axis('off')
axes[0, 0].set_title("Source Image")

axes[0, 1].imshow(img_rotated, cmap='gray', interpolation='none', origin='lower')
axes[0, 1].axis('off')
axes[0, 1].set_title("Target Image")

axes[1, 1].imshow(img_aligned, cmap='gray', interpolation='none', origin='lower')
axes[1, 1].axis('off')
axes[1, 1].set_title("Source Image aligned with Target")

axes[1, 0].axis('off')

plt.tight_layout()
plt.show()

Inquiry the parameters of the transformation before applying

In some occasions we need to check for the parameters of the transformation before applying it. Or maybe we just need to know the parameters for further analysis. In this case, we can call astroalign.find_transform.

In [5]:
p, (pos_img, pos_img_rot) = aa.find_transform(img, img_rotated)

Parameters of the transformation

In [6]:
print("Rotation: {:.2f} degrees".format(p.rotation * 180.0 / np.pi))
print("\nScale factor: {:.2f}".format(p.scale))
print("\nTranslation: (x, y) = ({:.2f}, {:.2f})".format(*p.translation))
print("\nTranformation matrix:\n{}".format(p.params))
print("\nPoint correspondence:")
for (x1, y1), (x2, y2) in zip(pos_img, pos_img_rot):
    print("({:.2f}, {:.2f}) in source --> ({:.2f}, {:.2f}) in target"
          .format(x1, y1, x2, y2))
Rotation: -30.01 degrees

Scale factor: 1.50

Translation: (x, y) = (-54.37, 94.87)

Tranformation matrix:
[[  1.30047133   0.75104318 -54.36997612]
 [ -0.75104318   1.30047133  94.86982081]
 [  0.           0.           1.        ]]

Point correspondence:
(127.03, 85.98) in source --> (175.13, 111.36) in target
(23.11, 31.87) in source --> (0.58, 119.04) in target
(98.84, 142.99) in source --> (181.55, 206.49) in target
(150.93, 85.02) in source --> (205.60, 91.89) in target
(137.99, 12.88) in source --> (134.61, 7.94) in target
(113.88, 185.59) in source --> (233.35, 251.22) in target
(91.86, 59.05) in source --> (109.34, 102.41) in target
(127.86, 144.12) in source --> (220.18, 185.99) in target
(83.94, 139.89) in source --> (159.76, 213.87) in target
(172.09, 164.64) in source --> (293.24, 180.29) in target

Identify corresponding stars with pos_img and pos_img_rot

In [7]:
fig, axes = plt.subplots(2, 2, figsize=(10, 10))

colors = ['r', 'g', 'b', 'y', 'cyan', 'w', 'm']

axes[0, 0].imshow(img, cmap='gray', interpolation='none', origin='lower')
axes[0, 0].axis('off')
axes[0, 0].set_title("Source Image")
for (xp, yp), c in zip(pos_img[:len(colors)], colors):
    circ = plt.Circle((xp, yp), 4, fill=False, edgecolor=c, linewidth=2)
    axes[0, 0].add_patch(circ)

axes[0, 1].imshow(img_rotated, cmap='gray', interpolation='none', origin='lower')
axes[0, 1].axis('off')
axes[0, 1].set_title("Target Image")
for (xp, yp), c in zip(pos_img_rot[:len(colors)], colors):
    circ = plt.Circle((xp, yp), 4 * p.scale, fill=False, edgecolor=c, linewidth=2)
    axes[0, 1].add_patch(circ)

axes[1, 1].imshow(img_aligned, cmap='gray', interpolation='none', origin='lower')
axes[1, 1].axis('off')
axes[1, 1].set_title("Source Image aligned with Target")
for (xp, yp), c in zip(pos_img_rot[:len(colors)], colors):
    circ = plt.Circle((xp, yp), 4 * p.scale, fill=False, edgecolor=c, linewidth=2)
    axes[1, 1].add_patch(circ)

axes[1, 0].axis('off')

plt.tight_layout()
plt.show()