diff --git a/pm2d/line_matcher.py b/pm2d/line_matcher.py index c37b7f8..899a746 100644 --- a/pm2d/line_matcher.py +++ b/pm2d/line_matcher.py @@ -241,13 +241,49 @@ class LineShapeMatcher: bins = np.clip(bins, 0, N_BINS - 1) return mag, bins + def _hysteresis_mask(self, mag: np.ndarray) -> np.ndarray: + """Edge mask con hysteresis (Halcon Contrast='auto' two-threshold). + + Procedura: + 1. seed = pixel con mag >= strong_grad (edge nitidi) + 2. weak = pixel con mag >= weak_grad (edge candidati) + 3. Espande seed dentro weak via componenti connesse 8-vicini + + Risultato: edge debole connesso a edge forte viene PROMOSSO a + feature valida; edge debole isolato (rumore) viene SCARTATO. + + Riduce sia falsi-positivi (rumore puro) sia falsi-negativi + (continuita' interrotta su edge sottili a basso contrasto). + """ + weak = (mag >= self.weak_grad).astype(np.uint8) + strong = (mag >= self.strong_grad).astype(np.uint8) + # connectedComponentsWithStats su weak: per ogni componente, + # se contiene almeno un pixel strong → tutto componente accettato + n_lab, labels = cv2.connectedComponents(weak, connectivity=8) + if n_lab <= 1: + return strong.astype(bool) + # Label dei pixel strong: marker per componenti da accettare + strong_labels = np.unique(labels[strong > 0]) + strong_labels = strong_labels[strong_labels > 0] # 0 = bg + if len(strong_labels) == 0: + return strong.astype(bool) + # Mask = appartiene a label di componente "promosso" + keep = np.isin(labels, strong_labels) + return keep + def _extract_features( self, mag: np.ndarray, bins: np.ndarray, mask: np.ndarray | None, ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: if mask is not None: mag = np.where(mask > 0, mag, 0) - strong = mag >= self.strong_grad - ys, xs = np.where(strong) + # Halcon-style edge selection: hysteresis tra weak_grad e strong_grad. + # Edge weak connessi a edge strong sono inclusi (continuita' bordi). + # Se weak_grad >= strong_grad → fallback a soglia singola strong. + if self.weak_grad < self.strong_grad: + edge = self._hysteresis_mask(mag) + else: + edge = mag >= self.strong_grad + ys, xs = np.where(edge) if len(xs) == 0: return (np.zeros(0, np.int32),) * 3 vals = mag[ys, xs]