Sélection des caractéristiques#

Lors de la sélection des bonnes caractéristiques, il existe quelques règles empiriques à prendre en compte. Par exemple, si l’on souhaite segmenter des objets très précisément, il faut utiliser de petites valeurs de rayon / sigma. Si une esquisse approximative est suffisante, ou si l’on souhaite éliminer des pixels individuels isolés sur les bordures des objets, il est logique d’utiliser des valeurs de rayon et de sigma plus grandes. Cependant, ce sujet peut également être abordé en utilisant des statistiques.

from skimage.io import imread, imsave
import pyclesperanto_prototype as cle
import numpy as np
import apoc
import matplotlib.pyplot as plt
import pandas as pd
image = imread('../../data/blobs.tif')

manual_annotation = imread('../../data/blobs_annotations.tif')

fig, axs = plt.subplots(1,2)

cle.imshow(image, plot=axs[0])
cle.imshow(manual_annotation, labels=True, plot=axs[1])
../_images/7919f0a3e13e370fb4d27e3ec9ab32b4d3d6f5880e76bccf3fabe54ea6581148.png

Entraînement - avec trop de caractéristiques#

Nous allons maintenant entraîner un segmenteur d’objets en fournissant de nombreuses caractéristiques. Nous devons également fournir des paramètres pour configurer des arbres de décision profonds et de nombreux arbres. Cela est nécessaire pour que les prochaines étapes, dérivant les statistiques, aient suffisamment de puissance statistique. Ensuite, nous examinons le résultat pour une vérification rapide de cohérence.

# define features
features = apoc.PredefinedFeatureSet.small_dog_log.value + " " + \
           apoc.PredefinedFeatureSet.medium_dog_log.value + " " + \
           apoc.PredefinedFeatureSet.large_dog_log.value

# this is where the model will be saved
cl_filename = '../../data/blobs_object_segmenter2.cl'

apoc.erase_classifier(cl_filename)
classifier = apoc.ObjectSegmenter(opencl_filename=cl_filename, 
                           positive_class_identifier=2, 
                           max_depth=5,
                           num_ensembles=1000)
classifier.train(features, manual_annotation, image)

segmentation_result = classifier.predict(features=features, image=image)
cle.imshow(segmentation_result, labels=True)
../_images/9367a3fc115b1f3124871139af914475562a8956611ab0b9ad5ead0bc7023369.png

Statistiques du classificateur#

Après l’entraînement, nous pouvons imprimer quelques statistiques du classificateur. Cela nous donne un tableau des caractéristiques utilisées et de leur importance dans la décision de classification des pixels.

shares, counts = classifier.statistics()

def colorize(styler):
    styler.background_gradient(axis=None, cmap="rainbow")
    return styler

df = pd.DataFrame(shares).T
df.style.pipe(colorize)
  0 1 2 3 4
original 0.138000 0.046423 0.042312 0.037281 0.062112
gaussian_blur=1 0.228000 0.092846 0.074303 0.105263 0.055901
difference_of_gaussian=1 0.000000 0.108828 0.095975 0.074561 0.086957
laplace_box_of_gaussian_blur=1 0.000000 0.105784 0.089783 0.081140 0.099379
gaussian_blur=5 0.096000 0.064688 0.118679 0.096491 0.130435
difference_of_gaussian=5 0.254000 0.182648 0.112487 0.120614 0.118012
laplace_box_of_gaussian_blur=5 0.209000 0.194064 0.121775 0.118421 0.124224
gaussian_blur=25 0.004000 0.061644 0.113519 0.127193 0.080745
difference_of_gaussian=25 0.032000 0.072298 0.122807 0.127193 0.130435
laplace_box_of_gaussian_blur=25 0.039000 0.070776 0.108359 0.111842 0.111801

Dans cette visualisation, vous pouvez voir que les caractéristiques gaussian_blur=1, difference_of_gaussian=5 et laplace_box_of_gaussian_blur=5 représentent environ 65% de la décision. Au premier niveau (niveau 0). Si ces trois caractéristiques sont cruciales, nous pouvons entraîner un autre classificateur qui ne prend en compte que ces caractéristiques. De plus, nous voyons que la part d’utilisation des caractéristiques aux trois niveaux de profondeur supérieurs est plus uniformément répartie. Ces niveaux ne font peut-être pas une grande différence lors de la classification des pixels. Pour le prochain classificateur que nous entraînerons, nous pouvons le faire avec un max_depth plus faible.

# define features
features = "gaussian_blur=1 difference_of_gaussian=5 laplace_box_of_gaussian_blur=5"

# this is where the model will be saved
cl_filename = '../../data/blobs_object_segmenter3.cl'

apoc.erase_classifier(cl_filename)
classifier = apoc.ObjectSegmenter(opencl_filename=cl_filename, 
                           positive_class_identifier=2, 
                           max_depth=3,
                           num_ensembles=1000)
classifier.train(features, manual_annotation, image)

segmentation_result = classifier.predict(features=features, image=image)
cle.imshow(segmentation_result, labels=True)
../_images/8fdbda78354f283b74387c3b1f6520063c3dc9b212210925736d563365713eaa.png

Le nouveau classificateur produit toujours un résultat très similaire. Il prend en compte moins de caractéristiques, ce qui le rend plus rapide, mais potentiellement aussi moins robuste face aux différences entre les images et les conditions d’imagerie. Nous allons jeter un autre coup d’œil aux statistiques du classificateur :

shares, counts = classifier.statistics()
df = pd.DataFrame(shares).T
df.style.pipe(colorize)
  0 1 2
gaussian_blur=1 0.331000 0.349194 0.344620
difference_of_gaussian=5 0.356000 0.329839 0.337096
laplace_box_of_gaussian_blur=5 0.313000 0.320968 0.318284

À des fins de démonstration, nous allons maintenant entraîner un autre classificateur avec des caractéristiques très similaires.

# define features
features = "gaussian_blur=1 difference_of_gaussian=2 difference_of_gaussian=3 difference_of_gaussian=4 difference_of_gaussian=5 difference_of_gaussian=6 laplace_box_of_gaussian_blur=5"

# this is where the model will be saved
cl_filename = '../../data/blobs_object_segmenter3.cl'

apoc.erase_classifier(cl_filename)
classifier = apoc.ObjectSegmenter(opencl_filename=cl_filename, 
                           positive_class_identifier=2, 
                           max_depth=3,
                           num_ensembles=1000)
classifier.train(features, manual_annotation, image)

segmentation_result = classifier.predict(features=features, image=image)
cle.imshow(segmentation_result, labels=True)
../_images/818aa0099f0c4174441c547fab71d7b7a09525ec526a14c64d4bf785a4f24bf4.png

Encore une fois, le résultat de la segmentation semble très similaire, mais la statistique du classificateur est différente.

shares, counts = classifier.statistics()
df = pd.DataFrame(shares).T
df.style.pipe(colorize)
  0 1 2
gaussian_blur=1 0.200000 0.093750 0.162829
difference_of_gaussian=2 0.000000 0.120888 0.148026
difference_of_gaussian=3 0.053000 0.064967 0.125000
difference_of_gaussian=4 0.236000 0.162829 0.097039
difference_of_gaussian=5 0.178000 0.222862 0.125822
difference_of_gaussian=6 0.134000 0.120066 0.194901
laplace_box_of_gaussian_blur=5 0.199000 0.214638 0.146382

De cette manière, on peut également affiner les paramètres de rayon et de sigma qu’il faut utiliser pour les caractéristiques spécifiées.

Les conseils donnés dans cette section ne sont pas des règles solides pour sélectionner les bonnes caractéristiques. Les outils fournis peuvent cependant aider à regarder un peu derrière les caractéristiques et à mesurer l’influence qu’ont les listes de caractéristiques et les paramètres fournis.