Détection de contours#
DataLab fournit une fonctionnalté de « Détection de contours » qui est basée sur l’algorithme des marching cubes

Paramètres de détection de contours.#
- La fonctionnalité s’utilise de la façon suivante :
Créer ou ouvrir une image dans l’espace de travail de DataLab
Créer éventuellement une ROI autour de la zone cible de l’image
Sélectionner « Détection de contours » dans le menu Analyse »
Saisir le paramètre « Forme » (« Ellipse », « Cercle » ou « Polygone »)

Résultats de la détection de contours (cf. test « contour_app.py »)#
- Les résultats sont affichés dans un tableau :
Chaque ligne est associée à un contour
La première colonne contient l’indice de la ROI (0 si aucune ROI n’est définie)
Les colonnes suivantes présentent les coordonnées des contours : 4 colonnes pour les cercles (coordonnées du diamètre) et 8 colonnes pour les ellipses (coordonnées des diamètres)

Exemple de détection de contours.#
- L’algorithme de détection de contours fonctionne de la manière suivante :
Tout d’abord, les isocontours sont calculés (l’implémentation est basée sur skimage.measure.find_contours.find_contours)
Ensuite, chaque contour est ajusté à une ellipse (ou à un cercle)
La fonctionnalité est basée sur la fonction get_contour_shapes
du module cdl.computation
:
def get_contour_shapes( data: np.ndarray | ma.MaskedArray, shape: Literal["circle", "ellipse", "polygon"] = "ellipse", level: float = 0.5, ) -> np.ndarray: """Find iso-valued contours in a 2D array, above relative level (.5 means FWHM), then fit contours with shape ('ellipse' or 'circle') Args: data: Input data shape: Shape to fit. Default is 'ellipse' level: Relative level (default: 0.5) Returns: Coordinates of shapes """ # pylint: disable=too-many-locals assert shape in ("circle", "ellipse", "polygon") contours = measure.find_contours(data, level=get_absolute_level(data, level)) coords = [] for contour in contours: # `contour` is a (N, 2) array (rows, cols): we need to check if all those # coordinates are masked: if so, we skip this contour if isinstance(data, ma.MaskedArray) and np.all( data.mask[contour[:, 0].astype(int), contour[:, 1].astype(int)] ): continue if shape == "circle": model = measure.CircleModel() if model.estimate(contour): yc, xc, r = model.params if r <= 1.0: continue coords.append([xc, yc, r]) elif shape == "ellipse": model = measure.EllipseModel() if model.estimate(contour): yc, xc, b, a, theta = model.params if a <= 1.0 or b <= 1.0: continue coords.append([xc, yc, a, b, theta]) elif shape == "polygon": # `contour` is a (N, 2) array (rows, cols): we need to convert it # to a list of x, y coordinates flattened in a single list coords.append(contour[:, ::-1].flatten()) else: raise NotImplementedError(f"Invalid contour model {model}") if shape == "polygon": # `coords` is a list of arrays of shape (N, 2) where N is the number of points # that can vary from one array to another, so we need to padd with NaNs each # array to get a regular array: max_len = max(coord.shape[0] for coord in coords) arr = np.full((len(coords), max_len), np.nan) for i_row, coord in enumerate(coords): arr[i_row, : coord.shape[0]] = coord return arr return np.array(coords)