File size: 3,368 Bytes
4b80d99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import cv2
import numpy as np
import math

def _leer_mask(mask):
    """Lee una máscara que puede venir como ruta o como array."""
    if isinstance(mask, str):  # si es ruta
        mask = cv2.imread(mask, cv2.IMREAD_GRAYSCALE)
    elif mask.ndim == 3:  # si es RGB/BGR
        mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)

    if mask is None:
        raise ValueError("No se pudo leer la máscara.")
    return mask


def calcular_area(mask):
    mask = _leer_mask(mask)
    _, mask_bin = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY)
    contours, _ = cv2.findContours(mask_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if not contours:
        return 0.0
    cnt = max(contours, key=cv2.contourArea)
    area = cv2.contourArea(cnt)
    if not np.isfinite(area):
        area = 0.0
    return round(float(area), 2)


def calcular_perimetro(mask):
    mask = _leer_mask(mask)
    _, mask_bin = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY)
    contours, _ = cv2.findContours(mask_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if not contours:
        return 0.0
    cnt = max(contours, key=cv2.contourArea)
    perimetro = cv2.arcLength(cnt, True)
    if not np.isfinite(perimetro):
        perimetro = 0.0
    return round(float(perimetro), 2)


def calcular_circularidad(mask):
    mask = _leer_mask(mask)
    _, mask_bin = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY)
    contours, _ = cv2.findContours(mask_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    if not contours:
        return 0.0

    cnt = max(contours, key=cv2.contourArea)
    area = cv2.contourArea(cnt)
    perimetro = cv2.arcLength(cnt, True)

    if perimetro == 0 or area == 0:
        return 0.0

    circ = (4 * math.pi * area) / (perimetro ** 2)

    if not np.isfinite(circ):
        circ = 0.0

    circ = np.clip(circ, 0, 1)
    return round(float(circ), 4)


def calcular_simetria(mask):
    mask = _leer_mask(mask)
    _, mask_bin = cv2.threshold(mask, 127, 1, cv2.THRESH_BINARY)

    if np.sum(mask_bin) == 0:
        return 0.0, 0.0

    y, x = np.where(mask_bin > 0)
    y_min, y_max = y.min(), y.max()
    x_min, x_max = x.min(), x.max()
    roi = mask_bin[y_min:y_max+1, x_min:x_max+1]

    h, w = roi.shape
    size = max(h, w)
    canvas = np.zeros((size, size), dtype=np.uint8)
    y_off, x_off = (size - h)//2, (size - w)//2
    canvas[y_off:y_off+h, x_off:x_off+w] = roi
    mask_centered = canvas

    cy, cx = np.mean(np.column_stack(np.where(mask_centered > 0)), axis=0).astype(int)
    area_total = np.sum(mask_centered)

    if area_total == 0:
        return 0.0, 0.0

    # --- simetría vertical ---
    left = mask_centered[:, :cx]
    right = mask_centered[:, cx:]
    right_flipped = np.fliplr(right)
    min_width = min(left.shape[1], right_flipped.shape[1])
    xor_v = np.logical_xor(left[:, :min_width], right_flipped[:, :min_width])
    sim_v = 1 - (np.sum(xor_v) / area_total)

    # --- simetría horizontal ---
    top = mask_centered[:cy, :]
    bottom = mask_centered[cy:, :]
    bottom_flipped = np.flipud(bottom)
    min_height = min(top.shape[0], bottom_flipped.shape[0])
    xor_h = np.logical_xor(top[:min_height, :], bottom_flipped[:min_height, :])
    sim_h = 1 - (np.sum(xor_h) / area_total)

    sim_v = max(0.0, min(1.0, sim_v))
    sim_h = max(0.0, min(1.0, sim_h))

    return round(sim_v, 3), round(sim_h, 3)