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 »)
Optionnellement, activer « Créer des régions d’intérêt » pour créer automatiquement des ROIs autour de chaque contour détecté :
Choisir la géométrie de la ROI : « Rectangle » ou « Cercle »
La taille de la ROI est automatiquement calculée en fonction de la distance minimale entre les contours détectés (pour éviter le chevauchement)
Cette fonctionnalité nécessite au moins 2 contours détectés
Les ROIs créées peuvent être utiles pour le traitement ultérieur de chaque zone de contour (par exemple, détection de pics, mesures, etc.)
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 sigima.proc :
def get_contour_shapes( data: np.ndarray | ma.MaskedArray, shape: ContourShape = ContourShape.ELLIPSE, level: float = 0.5, ) -> np.ndarray: """Find iso-valued contours in a 2D array, above relative level (.5 means FWHM). Args: data: Input data shape: Shape to fit. Default is ELLIPSE level: Relative level (default: 0.5) Returns: Coordinates of shapes fitted to contours """ # pylint: disable=too-many-locals 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 == ContourShape.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 == ContourShape.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 == ContourShape.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 == ContourShape.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)