ohtli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ohtli/__init__.py ADDED
@@ -0,0 +1,43 @@
1
+ """
2
+ beta_skeletons
3
+ ==============
4
+ Librería para el cálculo y visualización de β-esqueletos.
5
+
6
+ Algoritmo de fuerza bruta — O(n^3).
7
+
8
+ Referencia principal:
9
+ Kirkpatrick, D. G. y Radke, J. D. (1985).
10
+ A Framework for Computational Morphology.
11
+ Computational Geometry, pp. 217-248.
12
+
13
+ Uso básico
14
+ ----------
15
+ >>> import numpy as np
16
+ >>> from ohtli import adjacency_matrix, build_graph
17
+ >>> coords = np.array([[0,0],[1,0],[2,0],[1,1]])
18
+ >>> adj = adjacency_matrix(coords, beta=1.0)
19
+ >>> G = build_graph(coords, beta=2.0)
20
+ """
21
+
22
+ from .skeleton import adjacency_matrix, build_graph
23
+ from .geometry import point_in_lune, is_beta_neighbor
24
+ from .metrics import relative_asymmetry, control_value
25
+ from .viz import plot_points, plot_lune, plot_skeleton, plot_metric_bars
26
+
27
+ print("Cargando la librería ohtli...")
28
+
29
+ __all__ = [
30
+ "adjacency_matrix",
31
+ "build_graph",
32
+ "point_in_lune",
33
+ "is_beta_neighbor",
34
+ "relative_asymmetry",
35
+ "control_value",
36
+ "plot_points",
37
+ "plot_lune",
38
+ "plot_skeleton",
39
+ "plot_metric_bars",
40
+ ]
41
+
42
+ __version__ = "0.1.0"
43
+ __author__ = "Tu nombre"
ohtli/geometry.py ADDED
@@ -0,0 +1,99 @@
1
+ """
2
+ geometry.py
3
+ -----------
4
+ Funciones geométricas para el cálculo de β-esqueletos.
5
+ Implementa la definición de Kirkpatrick y Radke (1985) para
6
+ vecindades tipo luna (lune-based neighbourhoods).
7
+ """
8
+
9
+ import numpy as np
10
+
11
+
12
+ def _lune_centers_and_radius(p1: np.ndarray, p2: np.ndarray, beta: float):
13
+ """
14
+ Calcula los centros y radio de los dos discos que definen
15
+ la luna de influencia del par (p1, p2) para un valor beta dado.
16
+
17
+ Para beta >= 1:
18
+ radio = beta * d(p1, p2) / 2
19
+ centros desplazados hacia el interior del segmento.
20
+
21
+ Para beta < 1:
22
+ radio = d(p1, p2) / (2 * beta)
23
+ centros en p1 y p2.
24
+
25
+ Parámetros
26
+ ----------
27
+ p1, p2 : np.ndarray de forma (2,)
28
+ beta : float > 0
29
+
30
+ Retorna
31
+ -------
32
+ center1, center2 : np.ndarray de forma (2,)
33
+ r : float
34
+ """
35
+ d = np.linalg.norm(p2 - p1)
36
+ if d == 0:
37
+ raise ValueError("Los puntos p1 y p2 son idénticos.")
38
+ if beta <= 0:
39
+ raise ValueError("beta debe ser estrictamente positivo.")
40
+
41
+ if beta >= 1:
42
+ r = beta * d / 2
43
+ direction = (p2 - p1) / d
44
+ center1 = p1 + (1 - beta / 2) * (p2 - p1)
45
+ center2 = p2 + (1 - beta / 2) * (p1 - p2)
46
+ else:
47
+ r = d / (2 * beta)
48
+ center1 = p1.copy()
49
+ center2 = p2.copy()
50
+
51
+ return center1, center2, r
52
+
53
+
54
+ def point_in_lune(pk: np.ndarray, p1: np.ndarray, p2: np.ndarray, beta: float) -> bool:
55
+ """
56
+ Determina si el punto pk está en el interior de la luna
57
+ de influencia del par (p1, p2) para el parámetro beta.
58
+
59
+ Un punto está en la luna si y solo si está dentro de
60
+ ambos discos simultáneamente (intersección estricta).
61
+
62
+ Parámetros
63
+ ----------
64
+ pk : np.ndarray de forma (2,) — punto a evaluar
65
+ p1, p2 : np.ndarray de forma (2,) — par de puntos
66
+ beta : float > 0
67
+
68
+ Retorna
69
+ -------
70
+ bool : True si pk está en el interior de la luna
71
+ """
72
+ center1, center2, r = _lune_centers_and_radius(p1, p2, beta)
73
+ return (np.linalg.norm(pk - center1) < r and
74
+ np.linalg.norm(pk - center2) < r)
75
+
76
+
77
+ def is_beta_neighbor(coords: np.ndarray, i: int, j: int, beta: float) -> bool:
78
+ """
79
+ Determina si los puntos i y j son β-vecinos en el conjunto coords,
80
+ es decir, si la luna de influencia del par (i, j) está vacía.
81
+
82
+ Parámetros
83
+ ----------
84
+ coords : np.ndarray de forma (n, 2)
85
+ i, j : int — índices del par a evaluar
86
+ beta : float > 0
87
+
88
+ Retorna
89
+ -------
90
+ bool : True si la luna está vacía (i y j son β-vecinos)
91
+ """
92
+ p1, p2 = coords[i], coords[j]
93
+ n = len(coords)
94
+ for k in range(n):
95
+ if k == i or k == j:
96
+ continue
97
+ if point_in_lune(coords[k], p1, p2, beta):
98
+ return False
99
+ return True
ohtli/metrics.py ADDED
@@ -0,0 +1,83 @@
1
+ """
2
+ metrics.py
3
+ ----------
4
+ Métricas de análisis sobre grafos de β-esqueletos.
5
+
6
+ Incluye:
7
+ - Asimetría relativa (RA) por nodo
8
+ - Valor de control por nodo
9
+ """
10
+
11
+ import networkx as nx
12
+
13
+
14
+ def relative_asymmetry(G: nx.Graph) -> dict:
15
+ """
16
+ Calcula la asimetría relativa (RA) para cada nodo del grafo.
17
+
18
+ La RA mide la centralidad de un nodo en términos de profundidad
19
+ media respecto al resto de nodos del grafo.
20
+
21
+ Fórmula:
22
+ RA_i = 2 * (MD_i - 1) / (n - 2)
23
+
24
+ donde MD_i es la profundidad media del nodo i (media de las
25
+ longitudes de caminos más cortos hacia todos los demás nodos).
26
+
27
+ Parámetros
28
+ ----------
29
+ G : nx.Graph
30
+ Grafo conectado.
31
+
32
+ Retorna
33
+ -------
34
+ dict : {nodo: valor_RA}
35
+
36
+ Notas
37
+ -----
38
+ Para grafos desconectados, los nodos sin camino hacia algún otro
39
+ nodo tendrán RA indefinida. Se recomienda verificar conectividad
40
+ antes de aplicar esta métrica.
41
+ """
42
+ n = G.number_of_nodes()
43
+ if n < 3:
44
+ return {node: 0.0 for node in G.nodes()}
45
+
46
+ ra = {}
47
+ for node in G.nodes():
48
+ lengths = nx.single_source_shortest_path_length(G, node)
49
+ total_depth = sum(d for target, d in lengths.items() if target != node)
50
+ md = total_depth / (n - 1)
51
+ ra[node] = 2 * (md - 1) / (n - 2)
52
+
53
+ return ra
54
+
55
+
56
+ def control_value(G: nx.Graph) -> dict:
57
+ """
58
+ Calcula el valor de control para cada nodo del grafo.
59
+
60
+ El valor de control de un nodo i es la suma de los inversos
61
+ del grado de cada uno de sus vecinos:
62
+
63
+ CV_i = sum_{j ∈ N(i)} 1 / deg(j)
64
+
65
+ Un nodo con alto valor de control está conectado a vecinos
66
+ de bajo grado, lo que le otorga mayor influencia estructural.
67
+
68
+ Parámetros
69
+ ----------
70
+ G : nx.Graph
71
+
72
+ Retorna
73
+ -------
74
+ dict : {nodo: valor_control}
75
+ """
76
+ cv = {}
77
+ for node in G.nodes():
78
+ neighbors = list(G.neighbors(node))
79
+ if not neighbors:
80
+ cv[node] = 0.0
81
+ else:
82
+ cv[node] = sum(1 / G.degree(nb) for nb in neighbors)
83
+ return cv
ohtli/skeleton.py ADDED
@@ -0,0 +1,111 @@
1
+ """
2
+ skeleton.py
3
+ -----------
4
+ Algoritmo de fuerza bruta para calcular β-esqueletos.
5
+ Complejidad temporal: O(n^3).
6
+
7
+ Referencia:
8
+ Kirkpatrick, D. G. y Radke, J. D. (1985).
9
+ A Framework for Computational Morphology.
10
+ Computational Geometry, pp. 217-248.
11
+ """
12
+
13
+ import numpy as np
14
+ import networkx as nx
15
+
16
+ from .geometry import is_beta_neighbor
17
+
18
+
19
+ def adjacency_matrix(coords: np.ndarray, beta: float = 1.0) -> np.ndarray:
20
+ """
21
+ Calcula la matriz de adyacencia del β-esqueleto de un conjunto
22
+ de puntos mediante fuerza bruta.
23
+
24
+ Para cada par (i, j), verifica si la luna de influencia está vacía.
25
+ La matriz resultante es simétrica y sin auto-lazos.
26
+
27
+ Complejidad: O(n^3)
28
+
29
+ Parámetros
30
+ ----------
31
+ coords : np.ndarray de forma (n, 2)
32
+ Coordenadas de los n puntos en el plano.
33
+ beta : float > 0
34
+ Parámetro que controla el tamaño de la región de influencia.
35
+ beta = 1 → Grafo de Gabriel
36
+ beta = 2 → Grafo de vecindad relativa (RNG)
37
+
38
+ Retorna
39
+ -------
40
+ np.ndarray de forma (n, n), dtype int
41
+ Matriz de adyacencia binaria y simétrica.
42
+
43
+ Ejemplos
44
+ --------
45
+ >>> import numpy as np
46
+ >>> from beta_skeletons import adjacency_matrix
47
+ >>> coords = np.array([[0,0],[1,0],[2,0]])
48
+ >>> adjacency_matrix(coords, beta=1.0)
49
+ array([[0, 1, 0],
50
+ [1, 0, 1],
51
+ [0, 1, 0]])
52
+ """
53
+ coords = np.asarray(coords, dtype=float)
54
+ if coords.ndim != 2 or coords.shape[1] != 2:
55
+ raise ValueError("coords debe tener forma (n, 2).")
56
+ if beta <= 0:
57
+ raise ValueError("beta debe ser estrictamente positivo.")
58
+
59
+ n = len(coords)
60
+ matrix = np.zeros((n, n), dtype=int)
61
+
62
+ for i in range(n):
63
+ for j in range(i + 1, n):
64
+ if is_beta_neighbor(coords, i, j, beta):
65
+ matrix[i, j] = 1
66
+ matrix[j, i] = 1
67
+
68
+ return matrix
69
+
70
+
71
+ def build_graph(
72
+ coords: np.ndarray,
73
+ beta: float = 1.0,
74
+ labels: list | None = None,
75
+ ) -> nx.Graph:
76
+ """
77
+ Construye el β-esqueleto como un grafo de NetworkX.
78
+
79
+ Cada nodo almacena su posición ('pos') y, opcionalmente,
80
+ su etiqueta ('label').
81
+
82
+ Parámetros
83
+ ----------
84
+ coords : np.ndarray de forma (n, 2)
85
+ beta : float > 0
86
+ labels : list de str, opcional
87
+ Nombres de los nodos. Si es None se usan índices enteros.
88
+
89
+ Retorna
90
+ -------
91
+ nx.Graph
92
+ Grafo con atributos 'pos' y 'label' en cada nodo.
93
+ """
94
+ coords = np.asarray(coords, dtype=float)
95
+ n = len(coords)
96
+
97
+ if labels is not None and len(labels) != n:
98
+ raise ValueError("labels debe tener la misma longitud que coords.")
99
+
100
+ adj = adjacency_matrix(coords, beta=beta)
101
+ G = nx.Graph()
102
+
103
+ for i, (x, y) in enumerate(coords):
104
+ G.add_node(i, pos=(x, y), label=labels[i] if labels is not None else str(i))
105
+
106
+ for i in range(n):
107
+ for j in range(i + 1, n):
108
+ if adj[i, j] == 1:
109
+ G.add_edge(i, j)
110
+
111
+ return G
ohtli/viz.py ADDED
@@ -0,0 +1,232 @@
1
+ """
2
+ viz.py
3
+ ------
4
+ Funciones de visualización para β-esqueletos y sus métricas.
5
+ """
6
+
7
+ import numpy as np
8
+ import matplotlib.pyplot as plt
9
+ import matplotlib.cm as cm
10
+ import networkx as nx
11
+ from matplotlib.patches import Circle
12
+ from adjustText import adjust_text
13
+
14
+ from .geometry import _lune_centers_and_radius
15
+ from .skeleton import build_graph
16
+
17
+
18
+ def plot_points(
19
+ coords: np.ndarray,
20
+ labels: list | None = None,
21
+ ax: plt.Axes | None = None,
22
+ title: str = "Conjunto de puntos",
23
+ ) -> plt.Axes:
24
+ """
25
+ Grafica un conjunto de puntos con etiquetas opcionales.
26
+
27
+ Parámetros
28
+ ----------
29
+ coords : np.ndarray de forma (n, 2)
30
+ labels : list de str, opcional
31
+ ax : plt.Axes, opcional — si None se crea una figura nueva
32
+ title : str
33
+
34
+ Retorna
35
+ -------
36
+ plt.Axes
37
+ """
38
+ if ax is None:
39
+ _, ax = plt.subplots(figsize=(10, 10))
40
+
41
+ ax.scatter(coords[:, 0], coords[:, 1], color="steelblue", s=50, zorder=5)
42
+
43
+ if labels is not None:
44
+ texts = [ax.text(x, y, labels[i], fontsize=8)
45
+ for i, (x, y) in enumerate(coords)]
46
+ adjust_text(
47
+ texts, ax=ax,
48
+ arrowprops=dict(arrowstyle="->", color="gray", lw=0.5),
49
+ expand_points=(1.2, 1.2),
50
+ )
51
+
52
+ ax.set_xlabel("Longitud")
53
+ ax.set_ylabel("Latitud")
54
+ ax.set_title(title)
55
+ ax.axis("equal")
56
+ ax.grid(True)
57
+ return ax
58
+
59
+
60
+ def plot_lune(
61
+ coords: np.ndarray,
62
+ i: int,
63
+ j: int,
64
+ beta: float = 1.0,
65
+ labels: list | None = None,
66
+ ax: plt.Axes | None = None,
67
+ ) -> plt.Axes:
68
+ """
69
+ Visualiza la luna de influencia del par (i, j) para un beta dado,
70
+ clasificando los puntos restantes según si caen dentro o fuera.
71
+
72
+ Parámetros
73
+ ----------
74
+ coords : np.ndarray de forma (n, 2)
75
+ i, j : int — índices del par a visualizar
76
+ beta : float > 0
77
+ labels : list de str, opcional
78
+ ax : plt.Axes, opcional
79
+
80
+ Retorna
81
+ -------
82
+ plt.Axes
83
+ """
84
+ if ax is None:
85
+ _, ax = plt.subplots(figsize=(10, 10))
86
+
87
+ p1, p2 = coords[i], coords[j]
88
+ center1, center2, r = _lune_centers_and_radius(p1, p2, beta)
89
+ n = len(coords)
90
+
91
+ in_lune, out_lune = [], []
92
+ for k in range(n):
93
+ if k in (i, j):
94
+ continue
95
+ pk = coords[k]
96
+ inside = (np.linalg.norm(pk - center1) < r and
97
+ np.linalg.norm(pk - center2) < r)
98
+ (in_lune if inside else out_lune).append((pk, k))
99
+
100
+ if out_lune:
101
+ pts = np.array([p for p, _ in out_lune])
102
+ ax.scatter(pts[:, 0], pts[:, 1], color="lightgray",
103
+ label="Fuera de la luna", zorder=3)
104
+
105
+ if in_lune:
106
+ pts = np.array([p for p, _ in in_lune])
107
+ ax.scatter(pts[:, 0], pts[:, 1], color="steelblue",
108
+ label="Dentro de la luna", zorder=4)
109
+
110
+ ax.scatter([p1[0], p2[0]], [p1[1], p2[1]],
111
+ color="crimson", zorder=5, label="Par evaluado")
112
+ ax.plot([p1[0], p2[0]], [p1[1], p2[1]], "k--", alpha=0.4)
113
+
114
+ name_i = labels[i] if labels else str(i)
115
+ name_j = labels[j] if labels else str(j)
116
+ offset = r * 0.03
117
+ ax.text(p1[0] + offset, p1[1] + offset, name_i, fontsize=9)
118
+ ax.text(p2[0] + offset, p2[1] + offset, name_j, fontsize=9)
119
+
120
+ ax.add_patch(Circle(center1, r, color="steelblue",
121
+ fill=False, linestyle="--",
122
+ label=f"Disco de {name_i}"))
123
+ ax.add_patch(Circle(center2, r, color="seagreen",
124
+ fill=False, linestyle="--",
125
+ label=f"Disco de {name_j}"))
126
+
127
+ ax.set_xlabel("Longitud")
128
+ ax.set_ylabel("Latitud")
129
+ ax.set_title(f"Luna β = {beta} — '{name_i}' y '{name_j}'")
130
+ ax.axis("equal")
131
+ ax.grid(True)
132
+ ax.legend(fontsize=8)
133
+ return ax
134
+
135
+
136
+ def plot_skeleton(
137
+ coords: np.ndarray,
138
+ beta: float = 1.0,
139
+ labels: list | None = None,
140
+ node_color: str | list = "skyblue",
141
+ cmap=None,
142
+ colorbar_label: str | None = None,
143
+ ax: plt.Axes | None = None,
144
+ title: str | None = None,
145
+ ) -> tuple[plt.Axes, nx.Graph]:
146
+ """
147
+ Calcula y grafica el β-esqueleto.
148
+
149
+ Parámetros
150
+ ----------
151
+ coords : np.ndarray de forma (n, 2)
152
+ beta : float > 0
153
+ labels : list de str, opcional
154
+ node_color : str o list — color fijo o lista de valores numéricos
155
+ cmap : colormap de matplotlib, opcional
156
+ colorbar_label : str, opcional — título de la barra de color
157
+ ax : plt.Axes, opcional
158
+ title : str, opcional
159
+
160
+ Retorna
161
+ -------
162
+ (plt.Axes, nx.Graph)
163
+ """
164
+ if ax is None:
165
+ _, ax = plt.subplots(figsize=(12, 12))
166
+
167
+ G = build_graph(coords, beta=beta, labels=labels)
168
+ pos = nx.get_node_attributes(G, "pos")
169
+ node_labels = nx.get_node_attributes(G, "label")
170
+
171
+ draw_kwargs = dict(
172
+ pos=pos, ax=ax, with_labels=False,
173
+ node_size=100, edge_color="gray",
174
+ )
175
+
176
+ if cmap is not None and isinstance(node_color, (list, np.ndarray)):
177
+ norm = plt.Normalize(vmin=min(node_color), vmax=max(node_color))
178
+ draw_kwargs.update(node_color=node_color, cmap=cmap)
179
+ sm = cm.ScalarMappable(cmap=cmap, norm=norm)
180
+ sm.set_array([])
181
+ cbar = plt.colorbar(sm, ax=ax, shrink=0.7)
182
+ if colorbar_label:
183
+ cbar.set_label(colorbar_label)
184
+ else:
185
+ draw_kwargs["node_color"] = node_color
186
+
187
+ nx.draw(G, **draw_kwargs)
188
+
189
+ if labels is not None:
190
+ texts = [ax.text(pos[i][0], pos[i][1], node_labels[i], fontsize=8)
191
+ for i in G.nodes()]
192
+ adjust_text(
193
+ texts,
194
+ arrowprops=dict(arrowstyle="->", color="gray", lw=0.5),
195
+ expand_points=(1.2, 1.2),
196
+ )
197
+
198
+ ax.set_title(title or f"β-esqueleto (β = {beta})")
199
+ ax.axis("equal")
200
+ ax.grid(True)
201
+ return ax, G
202
+
203
+
204
+ def plot_metric_bars(
205
+ metric_values: dict,
206
+ metric_name: str = "Métrica",
207
+ ax: plt.Axes | None = None,
208
+ ) -> plt.Axes:
209
+ """
210
+ Grafica un diagrama de barras de una métrica por nodo.
211
+
212
+ Parámetros
213
+ ----------
214
+ metric_values : dict {nodo: valor}
215
+ metric_name : str — nombre de la métrica para etiquetas
216
+ ax : plt.Axes, opcional
217
+
218
+ Retorna
219
+ -------
220
+ plt.Axes
221
+ """
222
+ if ax is None:
223
+ _, ax = plt.subplots(figsize=(20, 6))
224
+
225
+ nodes, values = zip(*sorted(metric_values.items()))
226
+ ax.bar(nodes, values)
227
+ ax.set_xticks(nodes)
228
+ ax.set_xlabel("Nodo")
229
+ ax.set_ylabel(metric_name)
230
+ ax.set_title(f"{metric_name} por nodo")
231
+ ax.grid(axis="y", linestyle="--", alpha=0.5)
232
+ return ax
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.4
2
+ Name: ohtli
3
+ Version: 0.1.0
4
+ Summary: Cálculo y visualización de β-esqueletos mediante fuerza bruta
5
+ Author-email: Sara Luz Valenzuela Camacho <saraluz@gmail.com>
6
+ Maintainer-email: Sara Luz Valenzuela Camacho <saraluz@gmail.com>
7
+ Project-URL: Homepage, https://github.com/SaraLuzVC/Tesis-ITAM
8
+ Project-URL: Documentation, https://github.com/SaraLuzVC/Tesis-ITAM/tree/main/ohtli/README.md
9
+ Keywords: beta-skeleton,computational geometry,graph,archaeology
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: BSD License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: numpy>=1.24
17
+ Requires-Dist: networkx>=3.0
18
+ Requires-Dist: matplotlib>=3.7
19
+ Requires-Dist: adjustText>=0.8
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest; extra == "dev"
22
+ Requires-Dist: pytest-cov; extra == "dev"
23
+
24
+ # beta-skeletons
25
+
26
+ Librería para el cálculo y visualización de β-esqueletos mediante fuerza bruta.
27
+
28
+ ## Instalación
29
+
30
+ ```bash
31
+ pip install beta-skeletons
32
+ ```
33
+
34
+ O en modo desarrollo:
35
+
36
+ ```bash
37
+ git clone https://github.com/tu-usuario/beta-skeletons.git
38
+ cd beta-skeletons
39
+ pip install -e ".[dev]"
40
+ ```
41
+
42
+ ## Uso básico
43
+
44
+ ```python
45
+ import numpy as np
46
+ from beta_skeletons import adjacency_matrix, build_graph, plot_skeleton
47
+ from beta_skeletons import relative_asymmetry, control_value, plot_metric_bars
48
+
49
+ coords = np.array([[0,0],[1,0],[2,0],[1,1]], dtype=float)
50
+ names = ["A", "B", "C", "D"]
51
+
52
+ # Matriz de adyacencia
53
+ adj = adjacency_matrix(coords, beta=1.0)
54
+
55
+ # Grafo de NetworkX
56
+ G = build_graph(coords, beta=2.0, labels=names)
57
+
58
+ # Visualización
59
+ ax, G = plot_skeleton(coords, beta=1.0, labels=names)
60
+
61
+ # Métricas
62
+ ra = relative_asymmetry(G)
63
+ cv = control_value(G)
64
+ plot_metric_bars(ra, metric_name="Asimetría relativa")
65
+ ```
66
+
67
+ ## Estructura del paquete
68
+
69
+ ```
70
+ beta_skeletons/
71
+ ├── geometry.py # cálculo de lunas y vecindades
72
+ ├── skeleton.py # matriz de adyacencia y construcción del grafo
73
+ ├── metrics.py # asimetría relativa y valor de control
74
+ └── viz.py # visualizaciones
75
+ ```
76
+
77
+ ## Referencia
78
+
79
+ Kirkpatrick, D. G. y Radke, J. D. (1985).
80
+ *A Framework for Computational Morphology*.
81
+ Computational Geometry, pp. 217-248.
@@ -0,0 +1,9 @@
1
+ ohtli/__init__.py,sha256=KCWpG4dzl4IKXgHTFzZGq3c3b2dfZvjDqsNPR765FF4,1083
2
+ ohtli/geometry.py,sha256=AnW8UIjRGRUttXuG7Hnd36QlwvEYA_RaIgA9EMr73aE,2783
3
+ ohtli/metrics.py,sha256=nAx83Ex3JZKqV4VQuFqw7K-p7r81AGznze9pTLOJIZ8,2051
4
+ ohtli/skeleton.py,sha256=GgFwu5G-CSpLDfMpWqlPdOf5YFziLLF-KE5rYRP1zTI,2974
5
+ ohtli/viz.py,sha256=_CyuyTu2CKcXvs5x3Gd8F-KDhm6glFAqnUlS2FLjbYM,6557
6
+ ohtli-0.1.0.dist-info/METADATA,sha256=t9xw6ydDJcjrMnw5VmtP1KZiYqyoH7cParwo2J0ysTo,2299
7
+ ohtli-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
8
+ ohtli-0.1.0.dist-info/top_level.txt,sha256=O1kypZQ6C0kIsDWRoxlECWCMn1DoqSr0WCjwKjtN5nw,6
9
+ ohtli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ ohtli