Simulation der Bildentstehung + Bildwiederherstellung#

In diesem Notebook stellen wir künstlich ein Mikroskopbild aus simulierten Zellkernen, Rauschen und Hintergrund zusammen. Anschließend verwenden wir klassische Bildverarbeitungstechniken, um Rauschen und Hintergrund zu entfernen.

import pyclesperanto_prototype as cle
import numpy as np
image_size = (100, 100)

# noise configuration
noise_level = 2

# background configuration
camera_offset = 100
background_sigma = 25
background_intensity = 5

# nuclei configuration
nuclei_radius = 5
nuclei_blur_sigma = 1
nuclei_number = 10
nuclei_intensity = 5
# by pinning the random seed, we can make the code repeatable
np.random.seed(42)

Rauschen#

Hier nehmen wir an, dass das Rauschen im Bild Poisson-verteilt ist, eine häufige Annahme in der Mikroskopie.

noise_image = np.random.poisson(noise_level, image_size)

cle.imshow(noise_image, colorbar=True)
../_images/a0be8d81e2a7b002e7841e89c7ac2ef43be4a5c58d9062e56886b3be8b5c308b.png

Hintergrund#

Die Hintergrundintensität in Fluoreszenzmikroskopiebildern stammt typischerweise von unfokussiertem Licht. Wir können dies simulieren, indem wir Lichtquellen als einzelne Pixel platzieren und sie mit einem Gaußschen Filter verwischen. Darüber hinaus haben viele Mikroskopkameras einen sogenannten Kamera-Offset. Kein Pixel wird jemals eine Intensität unter diesem Wert haben.

# create empty image
background = np.zeros(image_size)

# place light sources
background[20, 10] += 1
background[50, 80] += 1
background[60, 50] += 1

# blur them massively
background = cle.gaussian_blur(background, sigma_x=background_sigma, sigma_y=background_sigma)

# normalize the image so that the maximum intensity has a defined value
background = background / background.max() * background_intensity

# add camera offsert
background = background + camera_offset

background
cle._ image
shape(100, 100)
dtypefloat32
size39.1 kB
min100.14104
max105.0

Zellkerne#

Als nächstes platzieren wir Zellkerne in einem Bild an zufälligen Positionen. Wir verwischen sie ein wenig, um die Punktspreizfunktion des Mikroskops zu simulieren.

# retrieve a defined number of random positions
nuclei_positions = np.random.random((nuclei_number, 2)) * image_size

# write 1 at these locations
nuclei_image = cle.pointlist_to_labelled_spots(nuclei_positions.T, np.zeros(image_size))
nuclei_image = (nuclei_image > 0) * nuclei_intensity

# enlarge the nuclei by a define radius
nuclei_image = cle.maximum_sphere(nuclei_image, radius_x=nuclei_radius, radius_y=nuclei_radius)

# blur the image to make it look more realistic
nuclei_image = cle.gaussian_blur(nuclei_image, sigma_x=nuclei_blur_sigma, sigma_y=nuclei_blur_sigma)

nuclei_image
cle._ image
shape(100, 100)
dtypefloat32
size39.1 kB
min0.0
max5.0

Bildentstehung#

Ein Mikroskopbild ist die Summe der Szene und der oben beschriebenen Effekte.

sum_image = np.asarray(noise_image + background + nuclei_image)

cle.imshow(sum_image, colorbar=True)
../_images/731583ea9532d7236719d77e5d2767170a6c819272fc513fb07ef289008685dd.png

Bildsegmentierung#

Wenn wir jetzt einen Segmentierungsalgorithmus auf dieses Bild anwenden würden, wie es ist, könnte dies zu einem falschen Ergebnis führen.

binary = cle.threshold_otsu(sum_image.astype(np.float32))

binary
cle._ image
shape(100, 100)
dtypeuint8
size9.8 kB
min0.0
max1.0

Hintergrundentfernung#

Um dieses Problem zu beheben, müssen wir zuerst die Hintergrundintensität entfernen.

background_removed = cle.top_hat_box(sum_image, radius_x=10, radius_y=10)

background_removed
cle._ image
shape(100, 100)
dtypefloat32
size39.1 kB
min0.0
max12.833778

Rauschentfernung#

Wir können auch das Rauschen aus dem Bild entfernen.

noise_removed1 = cle.mean_sphere(sum_image, radius_x=3, radius_y=3)

noise_removed1
cle._ image
shape(100, 100)
dtypefloat32
size39.1 kB
min101.35629
max111.36778

Und dies kann auch auf dem hintergrundsubtrahierten Bild durchgeführt werden.

noise_removed = cle.mean_sphere(background_removed, radius_x=3, radius_y=3)

noise_removed
cle._ image
shape(100, 100)
dtypefloat32
size39.1 kB
min0.7578272
max7.5516324

Bildsegmentierung II#

Nach der Korrektur des Bildes können wir die Segmentierung erneut versuchen.

binary2 = cle.threshold_otsu(noise_removed.astype(np.float32))

binary2
cle._ image
shape(100, 100)
dtypeuint8
size9.8 kB
min0.0
max1.0
# sneak preview: watershed
import napari_segment_blobs_and_things_with_membranes as nsbatwm
binary3 = nsbatwm.split_touching_objects(binary2)

binary3
<__array_function__ internals>:180: RuntimeWarning: Converting input from bool to <class 'numpy.uint8'> for compatibility.
nsbatwm made image
shape(100, 100)
dtypebool
size9.8 kB
minFalse
maxTrue