Assemblage d’images - l’approche naïve#

Dans le traitement d’images par tuiles, la première étape consiste à découper l’image en tuiles. Bien que cela puisse être fait avec numpy, nous utiliserons dask car il offre plusieurs fonctionnalités très utiles dans ce contexte.

import dask
import dask.array as da
from skimage.filters import gaussian
from skimage.data import cells3d
from pyclesperanto_prototype import imshow

Dans le premier exemple, nous utiliserons une image montrant des noyaux issus de la microscopie à fluorescence et nous allons simplement débruiter l’image en utilisant un flou gaussien. Nous ferons cela tuile par tuile. Pour cela, nous définissons d’abord la procédure qui doit être appliquée à toutes les tuiles. Nous intégrons une instruction print dans cette fonction pour voir quand elle est exécutée et quelle est la taille de l’image en cours de traitement.

def procedure(image):
    print("proceduring", image.shape)
    return gaussian(image, sigma=5)
image = cells3d()[30,1]
imshow(image)
../_images/e6d2393f7c605c769fb82b772552ade1566917e2a294f57aa9e4f747b0c78793.png

Après avoir chargé l’image, elle peut être découpée en tuiles comme ceci. Dans dask, les tuiles sont également appelées chunks.

tiles = da.from_array(image, chunks=(128, 128))
tiles
Array Chunk
Bytes 128.00 kiB 32.00 kiB
Shape (256, 256) (128, 128)
Count 4 Tasks 4 Chunks
Type uint16 numpy.ndarray
256 256

Ensuite, nous indiquons à dask ce qu’il faut faire avec nos tuiles. Nous voulons mapper la fonction procedure sur toutes les tuiles individuelles. Notez que cela ne traite pas encore l’image entière.

tile_map = da.map_blocks(procedure, tiles)
proceduring (0, 0)
proceduring (1, 1)

Comme nous pouvons le lire, la fonction a été exécutée deux fois avec de très petites images (0x0 et 1x1 pixels). Dask fait cela en principe pour vérifier si la fonction fonctionne. Ensuite, nous allons effectivement exécuter notre procédure sur les tuiles de l’image.

result = tile_map.compute() # Attention : Ceci charge toutes les données de l'image en mémoire
proceduringproceduring (128, 128)
 (128, 128)
proceduring (128, 128)
proceduring (128, 128)

La sortie imprimée semble un peu chaotique car dask a exécuté la procédure sur plusieurs tuiles en parallèle. Si nous examinons le résultat, nous verrons que c’est à nouveau une image.

result.shape
(256, 256)
type(result)
numpy.ndarray

Note : La fonction imshow peut ne pas fonctionner sur de grands ensembles de données. Nous l’utilisons ici à des fins de démonstration.

imshow(result)
../_images/7b0a8ba73291ddde763b05183cfdf2a3d3557bede55f4d5dd6dd35a5022f1c2e.png

Effets de bord#

Lors du traitement d’images tuile par tuile, nous devons toujours supposer que des artefacts apparaissent le long de la bordure, résultant du découpage de l’image en tuiles. Comme notre image d’exemple tient en mémoire, nous pouvons lui appliquer la procédure et la comparer au résultat du traitement d’image par tuiles

untiled_result = procedure(image)
imshow(untiled_result)
proceduring (256, 256)
../_images/b31924a9f3cf423d048676a3c5ccca6648eb37f59f08ecc030e068593386819d.png

Les différences ne sont pas évidentes, mais nous pouvons les visualiser.

difference = result - untiled_result
imshow(difference)
../_images/e4126918c50e4b551767159a05ace9b17cb60db4891f34ce4000369b3cfe9c54.png

Lors de l’application d’un flou gaussien avec un petit sigma, ces effets peuvent être négligeables. Dans le cas où ces effets causent des problèmes sérieux dans notre flux de traitement d’image, nous pourrions vouloir réduire ou même prévenir ces artefacts.