Détermination de la fonction d’étalement du point à partir d’une image de bille par moyenne#

Afin de déconvoluer correctement une image de microscopie, nous devrions déterminer la fonction d’étalement du point (PSF) du microscope.

Voir aussi

import numpy as np
from skimage.io import imread, imsave
from pyclesperanto_prototype import imshow
import pyclesperanto_prototype as cle
import pandas as pd
import matplotlib.pyplot as plt

Les données d’image d’exemple utilisées ici ont été acquises par Bert Nitzsche et Robert Haase (tous deux du MPI-CBG à l’époque) à l’installation de microscopie optique du MPI-CBG. Pour être complet, la taille des voxels est de 0,022x0,022x0,125 µm^3.

bead_image = imread('../../data/Bead_Image1_crop.tif')
bead_image.shape
(41, 150, 150)

Notre image d’exemple montre des billes fluorescentes, idéalement avec un diamètre inférieur à la résolution du dispositif d’imagerie. De plus, les billes devraient émettre de la lumière à la même longueur d’onde que l’échantillon que nous aimerions déconvoluer plus tard. Dans la coupe d’image suivante, nous voyons quatre billes fluorescentes. Il est recommandé d’imager un champ de vision plus large, avec au moins 25 billes. Assurez-vous également que les billes ne se collent pas entre elles et sont réparties de manière éparse.

imshow(cle.maximum_x_projection(bead_image), colorbar=True)
imshow(cle.maximum_y_projection(bead_image), colorbar=True)
imshow(cle.maximum_z_projection(bead_image), colorbar=True)
../_images/bc61c20f19744054b7f1ff81a09c6eef21855bcb937ce7a918c70d19c0b145d6.png ../_images/0901ecfda696afda8465acea84c8b4908017df75f5f6fd78f74fb696935786ba.png ../_images/906ad5084326f9f315bf7726ebdd33b0bca743cc1a4798621e7f7592fea5aac5.png

Pour déterminer une PSF moyenne, techniquement, nous pouvons découper toutes les billes individuelles, les aligner puis faire la moyenne des images. Par conséquent, nous segmentons les objets et déterminons leur centre de masse.

# Segmenter les objets
label_image = cle.voronoi_otsu_labeling(bead_image)
imshow(label_image, labels=True)
../_images/658f1ffe27f81bddfbe03b9a26de9f84b13519e5a1e8af84a198053b1701cdb6.png
# déterminer le centre de masse pour chaque objet
stats = cle.statistics_of_labelled_pixels(bead_image, label_image)

df = pd.DataFrame(stats)
df[["mass_center_x", "mass_center_y", "mass_center_z"]]
mass_center_x mass_center_y mass_center_z
0 30.107895 73.028938 23.327475
1 44.293156 111.633430 23.329062
2 76.092850 82.453033 23.299677
3 125.439606 35.972496 23.390951

Moyenne de la PSF#

Ensuite, nous allons itérer sur les billes et les découper en les transférant dans une image PSF plus petite.

# configurer la taille de la future image PSF
psf_radius = 20
size = psf_radius * 2 + 1

# initialiser la PSF
single_psf_image = cle.create([size, size, size])
avg_psf_image = cle.create([size, size, size])

num_psfs = len(df)
for index, row in df.iterrows():
    x = row["mass_center_x"]
    y = row["mass_center_y"]
    z = row["mass_center_z"]
    
    print("Bille", index, "à la position", x, y, z)
    
    # déplacer la PSF dans la bonne position dans une image plus petite
    cle.translate(bead_image, single_psf_image, 
                  translate_x= -x + psf_radius,
                  translate_y= -y + psf_radius,
                  translate_z= -z + psf_radius)

    # visualiser
    fig, axs = plt.subplots(1,3)    
    imshow(cle.maximum_x_projection(single_psf_image), plot=axs[0])
    imshow(cle.maximum_y_projection(single_psf_image), plot=axs[1])
    imshow(cle.maximum_z_projection(single_psf_image), plot=axs[2])
    
    # moyenne
    avg_psf_image = avg_psf_image + single_psf_image / num_psfs
Bead 0 at position 30.107894897460938 73.02893829345703 23.32747459411621
Bead 1 at position 44.293155670166016 111.63343048095703 23.32906150817871
Bead 2 at position 76.09284973144531 82.45303344726562 23.2996768951416
Bead 3 at position 125.43960571289062 35.972496032714844 23.39095115661621
../_images/dff2d693110f889bd993da0ee4703fedaf6723adf1a5f1ca87ae881d2395f2b4.png ../_images/afbb13b598502c783d87eaa1f928eed1f8736a2d01eb5e07e261015a488abd98.png ../_images/e5ee2ab02997327492647477f3862ac9dc7b55d0bd77b31ddc82226893c1eaf4.png ../_images/4afaf310f68a10d34b771c4d261c1a359c89e9746974e70025269d35ed3c93a7.png

La PSF moyenne ressemble alors à ceci :

fig, axs = plt.subplots(1,3)    
imshow(cle.maximum_x_projection(avg_psf_image), plot=axs[0])
imshow(cle.maximum_y_projection(avg_psf_image), plot=axs[1])
imshow(cle.maximum_z_projection(avg_psf_image), plot=axs[2])
../_images/df910e75d821babaf7d3beb4793305758a9e91879ec249c4815be26d36436ba5.png
avg_psf_image.min(), avg_psf_image.max()
(0.0, 94.5)

Après avoir déterminé une PSF bien centrée, nous pouvons la sauvegarder pour une utilisation ultérieure. Avant cela, nous normalisons la PSF. L’objectif est d’avoir une image où l’intensité totale est 1. Cela garantit qu’une image qui est déconvoluée en utilisant cette PSF plus tard ne modifie pas la gamme d’intensité de l’image.

normalized_psf = avg_psf_image / np.sum(avg_psf_image)

imshow(normalized_psf, colorbar=True)
../_images/c214ea1c85ad9e3868924c84710f0764cf0f21ab583f26430731c889cf5705e6.png
normalized_psf.min(), normalized_psf.max()
(0.0, 0.0006259646)
imsave('../../data/psf.tif', normalized_psf)
C:\Users\rober\AppData\Local\Temp\ipykernel_16716\3265681491.py:1: UserWarning: ../../data/psf.tif is a low contrast image
  imsave('../../data/psf.tif', normalized_psf)