teody-vision 0.1.0__tar.gz
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.
- teody_vision-0.1.0/LICENSE +0 -0
- teody_vision-0.1.0/PKG-INFO +16 -0
- teody_vision-0.1.0/README.md +0 -0
- teody_vision-0.1.0/pyproject.toml +25 -0
- teody_vision-0.1.0/setup.cfg +4 -0
- teody_vision-0.1.0/teody_vision/__init__.py +11 -0
- teody_vision-0.1.0/teody_vision/core.py +27 -0
- teody_vision-0.1.0/teody_vision/matcher.py +60 -0
- teody_vision-0.1.0/teody_vision/tensor.py +51 -0
- teody_vision-0.1.0/teody_vision/topology.py +27 -0
- teody_vision-0.1.0/teody_vision.egg-info/PKG-INFO +16 -0
- teody_vision-0.1.0/teody_vision.egg-info/SOURCES.txt +14 -0
- teody_vision-0.1.0/teody_vision.egg-info/dependency_links.txt +1 -0
- teody_vision-0.1.0/teody_vision.egg-info/requires.txt +3 -0
- teody_vision-0.1.0/teody_vision.egg-info/top_level.txt +1 -0
- teody_vision-0.1.0/tests/test_engine.py +80 -0
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: teody-vision
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A deterministic, non-neural topological tensor framework for real-time UI text and field localization.
|
|
5
|
+
Author-email: Teody Arboneda Banagan <teody882011@gmail.com>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: numpy>=1.21.0
|
|
14
|
+
Requires-Dist: opencv-python>=4.5.0
|
|
15
|
+
Requires-Dist: mss>=9.0.0
|
|
16
|
+
Dynamic: license-file
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "teody-vision"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A deterministic, non-neural topological tensor framework for real-time UI text and field localization."
|
|
9
|
+
authors = [
|
|
10
|
+
{ name="Teody Arboneda Banagan", email="teody882011@gmail.com" }
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
requires-python = ">=3.9"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"numpy>=1.21.0",
|
|
17
|
+
"opencv-python>=4.5.0",
|
|
18
|
+
"mss>=9.0.0"
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"Topic :: Scientific/Engineering :: Image Processing",
|
|
25
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TEODY-Vision Package Initialization
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .core import TEODYEngine
|
|
6
|
+
from .tensor import TEODYTensorEngine
|
|
7
|
+
from .topology import TEODYTopologyEngine
|
|
8
|
+
from .matcher import TEODYMatcher
|
|
9
|
+
|
|
10
|
+
__version__ = "0.1.0"
|
|
11
|
+
__all__ = ["TEODYEngine", "TEODYTensorEngine", "TEODYTopologyEngine", "TEODYMatcher"]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import cv2
|
|
2
|
+
import numpy as np
|
|
3
|
+
from mss import mss
|
|
4
|
+
from .tensor import TEODYTensorEngine
|
|
5
|
+
from .topology import TEODYTopologyEngine
|
|
6
|
+
|
|
7
|
+
class TEODYEngine:
|
|
8
|
+
def __init__(self, monitor_index: int = 1, block_size: int = 3, k: float = 0.04):
|
|
9
|
+
self.monitor_index = monitor_index
|
|
10
|
+
self.sct = mss()
|
|
11
|
+
# Initialize tensor engine with block_size and k matching its definition
|
|
12
|
+
self.tensor_engine = TEODYTensorEngine(block_size=block_size, k=k)
|
|
13
|
+
self.topology_engine = TEODYTopologyEngine()
|
|
14
|
+
|
|
15
|
+
def capture_frame(self) -> np.ndarray:
|
|
16
|
+
"""Captures a high-speed screen frame using mss."""
|
|
17
|
+
monitor = self.sct.monitors[self.monitor_index]
|
|
18
|
+
sct_img = self.sct.grab(monitor)
|
|
19
|
+
frame = np.array(sct_img)
|
|
20
|
+
# Convert BGRA to BGR
|
|
21
|
+
return cv2.cvtColor(frame, cv2.COLOR_BGRA2BGR) if frame.shape[2] == 4 else frame
|
|
22
|
+
|
|
23
|
+
def process_frame(self, frame: np.ndarray) -> tuple[np.ndarray, list[tuple[int, int, int, int]]]:
|
|
24
|
+
"""Runs the tensor coherence and topological region extraction pipeline."""
|
|
25
|
+
coherence = self.tensor_engine.compute_coherence(frame)
|
|
26
|
+
regions = self.topology_engine.extract_regions(coherence)
|
|
27
|
+
return coherence, regions
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import cv2
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
class TEODYMatcher:
|
|
5
|
+
@staticmethod
|
|
6
|
+
def match_template_in_regions(frame: np.ndarray,
|
|
7
|
+
template: np.ndarray,
|
|
8
|
+
regions: list[tuple[int, int, int, int]],
|
|
9
|
+
threshold: float = 0.48) -> list[tuple[int, int, int, int]]:
|
|
10
|
+
"""
|
|
11
|
+
Performs Multi-Scale Gradient Magnitude Template Matching.
|
|
12
|
+
Resilient to browser anti-aliasing, font smoothing, and variable font sizes.
|
|
13
|
+
"""
|
|
14
|
+
if template is None or len(regions) == 0:
|
|
15
|
+
return []
|
|
16
|
+
|
|
17
|
+
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) if len(frame.shape) == 3 else frame
|
|
18
|
+
gray_template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY) if len(template.shape) == 3 else template
|
|
19
|
+
|
|
20
|
+
def get_gradient_magnitude(img):
|
|
21
|
+
grad_x = cv2.Sobel(img, cv2.CV_32F, 1, 0, ksize=3)
|
|
22
|
+
grad_y = cv2.Sobel(img, cv2.CV_32F, 0, 1, ksize=3)
|
|
23
|
+
magnitude = cv2.magnitude(grad_x, grad_y)
|
|
24
|
+
return cv2.normalize(magnitude, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
|
|
25
|
+
|
|
26
|
+
template_grad_base = get_gradient_magnitude(gray_template)
|
|
27
|
+
|
|
28
|
+
# Scale pyramid range supporting small sidebar fonts (0.6x) up to large headers (1.8x)
|
|
29
|
+
scales = [0.6, 0.8, 1.0, 1.2, 1.5, 1.8]
|
|
30
|
+
matched_boxes = []
|
|
31
|
+
|
|
32
|
+
for (x, y, w, h) in regions:
|
|
33
|
+
roi = gray_frame[y:y+h, x:x+w]
|
|
34
|
+
if roi.size == 0:
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
roi_grad = get_gradient_magnitude(roi)
|
|
38
|
+
best_match_val = -1
|
|
39
|
+
best_box = None
|
|
40
|
+
|
|
41
|
+
for scale in scales:
|
|
42
|
+
scaled_w = int(template_grad_base.shape[1] * scale)
|
|
43
|
+
scaled_h = int(template_grad_base.shape[0] * scale)
|
|
44
|
+
|
|
45
|
+
if scaled_w <= 0 or scaled_h <= 0 or scaled_w > roi_grad.shape[1] or scaled_h > roi_grad.shape[0]:
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
scaled_template = cv2.resize(template_grad_base, (scaled_w, scaled_h), interpolation=cv2.INTER_AREA)
|
|
49
|
+
|
|
50
|
+
result = cv2.matchTemplate(roi_grad, scaled_template, cv2.TM_CCOEFF_NORMED)
|
|
51
|
+
_, max_val, _, max_loc = cv2.minMaxLoc(result)
|
|
52
|
+
|
|
53
|
+
if max_val > best_match_val:
|
|
54
|
+
best_match_val = max_val
|
|
55
|
+
best_box = (x + max_loc[0], y + max_loc[1], scaled_w, scaled_h)
|
|
56
|
+
|
|
57
|
+
if best_match_val >= threshold and best_box is not None:
|
|
58
|
+
matched_boxes.append(best_box)
|
|
59
|
+
|
|
60
|
+
return matched_boxes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import cv2
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
class TEODYTensorEngine:
|
|
5
|
+
def __init__(self, block_size: int = 3, k: float = 0.04):
|
|
6
|
+
self.block_size = block_size
|
|
7
|
+
self.k = k
|
|
8
|
+
|
|
9
|
+
def compute_structure_tensor(self, frame: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
10
|
+
"""Calculates spatial image derivatives and structure tensor components."""
|
|
11
|
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) if len(frame.shape) == 3 else frame
|
|
12
|
+
|
|
13
|
+
# Spatial gradients using Sobel operators
|
|
14
|
+
Ix = cv2.Sobel(gray, cv2.CV_32F, 1, 0, ksize=3)
|
|
15
|
+
Iy = cv2.Sobel(gray, cv2.CV_32F, 0, 1, ksize=3)
|
|
16
|
+
|
|
17
|
+
# Structure tensor elements
|
|
18
|
+
Ixx = Ix * Ix
|
|
19
|
+
Iyy = Iy * Iy
|
|
20
|
+
Ixy = Ix * Iy
|
|
21
|
+
|
|
22
|
+
# Smooth components over the local neighborhood
|
|
23
|
+
ksize = (self.block_size, self.block_size)
|
|
24
|
+
Sxx = cv2.GaussianBlur(Ixx, ksize, 0)
|
|
25
|
+
Syy = cv2.GaussianBlur(Iyy, ksize, 0)
|
|
26
|
+
Sxy = cv2.GaussianBlur(Ixy, ksize, 0)
|
|
27
|
+
|
|
28
|
+
return Sxx, Syy, Sxy
|
|
29
|
+
|
|
30
|
+
def compute_coherence(self, frame: np.ndarray) -> np.ndarray:
|
|
31
|
+
"""Computes anisotropic coherence map from structure tensor eigenvalues."""
|
|
32
|
+
Sxx, Syy, Sxy = self.compute_structure_tensor(frame)
|
|
33
|
+
|
|
34
|
+
# Trace and determinant
|
|
35
|
+
trace = Sxx + Syy
|
|
36
|
+
det = (Sxx * Syy) - (Sxy ** 2)
|
|
37
|
+
|
|
38
|
+
# Difference squared term for eigenvalues
|
|
39
|
+
diff = Sxx - Syy
|
|
40
|
+
lambda_diff_sq = (diff ** 2) + (4 * (Sxy ** 2))
|
|
41
|
+
lambda_diff = np.sqrt(np.maximum(lambda_diff_sq, 0))
|
|
42
|
+
|
|
43
|
+
# Eigenvalues
|
|
44
|
+
lambda1 = 0.5 * (trace + lambda_diff)
|
|
45
|
+
lambda2 = 0.5 * (trace - lambda_diff)
|
|
46
|
+
|
|
47
|
+
# Anisotropic coherence metric (normalized difference)
|
|
48
|
+
denom = lambda1 + lambda2 + 1e-6
|
|
49
|
+
coherence = (lambda1 - lambda2) / denom
|
|
50
|
+
|
|
51
|
+
return np.clip(coherence * 255, 0, 255).astype(np.uint8)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import cv2
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
class TEODYTopologyEngine:
|
|
5
|
+
def __init__(self, min_width: int = 10, min_height: int = 6):
|
|
6
|
+
self.min_width = min_width
|
|
7
|
+
self.min_height = min_height
|
|
8
|
+
|
|
9
|
+
def extract_regions(self, coherence_map: np.ndarray) -> list[tuple[int, int, int, int]]:
|
|
10
|
+
"""Isolates UI text and layout boundaries via morphological operations and contour analysis."""
|
|
11
|
+
# Binarize coherence map
|
|
12
|
+
_, thresh = cv2.threshold(coherence_map, 40, 255, cv2.THRESH_BINARY)
|
|
13
|
+
|
|
14
|
+
# Morphological closing to connect nearby character strokes and text components
|
|
15
|
+
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 3))
|
|
16
|
+
closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
|
|
17
|
+
|
|
18
|
+
# Find external contours
|
|
19
|
+
contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
|
20
|
+
|
|
21
|
+
regions = []
|
|
22
|
+
for cnt in contours:
|
|
23
|
+
x, y, w, h = cv2.boundingRect(cnt)
|
|
24
|
+
if w >= self.min_width and h >= self.min_height:
|
|
25
|
+
regions.append((x, y, w, h))
|
|
26
|
+
|
|
27
|
+
return regions
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: teody-vision
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A deterministic, non-neural topological tensor framework for real-time UI text and field localization.
|
|
5
|
+
Author-email: Teody Arboneda Banagan <teody882011@gmail.com>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: numpy>=1.21.0
|
|
14
|
+
Requires-Dist: opencv-python>=4.5.0
|
|
15
|
+
Requires-Dist: mss>=9.0.0
|
|
16
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
teody_vision/__init__.py
|
|
5
|
+
teody_vision/core.py
|
|
6
|
+
teody_vision/matcher.py
|
|
7
|
+
teody_vision/tensor.py
|
|
8
|
+
teody_vision/topology.py
|
|
9
|
+
teody_vision.egg-info/PKG-INFO
|
|
10
|
+
teody_vision.egg-info/SOURCES.txt
|
|
11
|
+
teody_vision.egg-info/dependency_links.txt
|
|
12
|
+
teody_vision.egg-info/requires.txt
|
|
13
|
+
teody_vision.egg-info/top_level.txt
|
|
14
|
+
tests/test_engine.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
teody_vision
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TEODY-Vision: Automated Unit Test Suite
|
|
3
|
+
File: tests/test_engine.py
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import unittest
|
|
7
|
+
import numpy as np
|
|
8
|
+
import cv2
|
|
9
|
+
from teody_vision import TEODYTensorEngine, TEODYTopologyEngine, TEODYMatcher
|
|
10
|
+
|
|
11
|
+
class TestTEODYVisionEngine(unittest.TestCase):
|
|
12
|
+
"""Test cases for core TEODY-Vision non-neural computer vision components."""
|
|
13
|
+
|
|
14
|
+
def setUp(self):
|
|
15
|
+
"""Set up synthetic test images simulating UI screens with text and borders."""
|
|
16
|
+
self.tensor_engine = TEODYTensorEngine(sigma=1.0, rho=2.0)
|
|
17
|
+
self.topology_engine = TEODYTopologyEngine(min_width=10, min_height=5)
|
|
18
|
+
|
|
19
|
+
# Create a blank 300x300 grayscale synthetic UI screen canvas
|
|
20
|
+
self.synthetic_screen = np.full((300, 300), 255, dtype=np.uint8)
|
|
21
|
+
|
|
22
|
+
# Draw a simulated UI box (representing a button or input field)
|
|
23
|
+
cv2.rectangle(self.synthetic_screen, (50, 50), (180, 90), 0, 2)
|
|
24
|
+
|
|
25
|
+
# Draw simulated text/label inside the box
|
|
26
|
+
cv2.putText(self.synthetic_screen, "Login", (60, 78), cv2.FONT_HERSHEY_SIMPLEX, 0.7, 0, 2, cv2.LINE_AA)
|
|
27
|
+
|
|
28
|
+
def test_tensor_anisotropy_computation(self):
|
|
29
|
+
"""Test that the structure tensor computes anisotropic coherence maps successfully."""
|
|
30
|
+
coherence_map = self.tensor_engine.compute_anisotropic_coherence(self.synthetic_screen)
|
|
31
|
+
|
|
32
|
+
# Verify output dimensions match input frame
|
|
33
|
+
self.assertEqual(coherence_map.shape, self.synthetic_screen.shape)
|
|
34
|
+
# Verify output is an 8-bit unsigned integer array
|
|
35
|
+
self.assertEqual(coherence_map.dtype, np.uint8)
|
|
36
|
+
# Verify that tensor values detect high-contrast edges (max coherence > 0)
|
|
37
|
+
self.assertGreater(np.max(coherence_map), 0)
|
|
38
|
+
|
|
39
|
+
def test_topology_boundary_extraction(self):
|
|
40
|
+
"""Test that topological morphology isolates UI text and field bounding boxes."""
|
|
41
|
+
coherence_map = self.tensor_engine.compute_anisotropic_coherence(self.synthetic_screen)
|
|
42
|
+
regions = self.topology_engine.extract_field_boundaries(coherence_map)
|
|
43
|
+
|
|
44
|
+
# Verify that regions are returned as a list
|
|
45
|
+
self.assertIsInstance(regions, list)
|
|
46
|
+
# Verify that at least one valid bounding box was detected around our simulated UI elements
|
|
47
|
+
self.assertGreater(len(regions), 0, "Failed to isolate any structural UI regions.")
|
|
48
|
+
|
|
49
|
+
# Check that bounding box tuples have 4 elements (x, y, w, h)
|
|
50
|
+
for box in regions:
|
|
51
|
+
self.assertEqual(len(box), 4)
|
|
52
|
+
x, y, w, h = box
|
|
53
|
+
self.assertGreater(w, 0)
|
|
54
|
+
self.assertGreater(h, 0)
|
|
55
|
+
|
|
56
|
+
def test_deterministic_matcher(self):
|
|
57
|
+
"""Test that template matching successfully locates a target word within extracted regions."""
|
|
58
|
+
# Generate a synthetic template for the word "Login"
|
|
59
|
+
font = cv2.FONT_HERSHEY_SIMPLEX
|
|
60
|
+
(tw, th), _ = cv2.getTextSize("Login", font, 0.7, 2)
|
|
61
|
+
template = np.full((th + 10, tw + 10), 255, dtype=np.uint8)
|
|
62
|
+
cv2.putText(template, "Login", (5, th + 5), font, 0.7, 0, 2, cv2.LINE_AA)
|
|
63
|
+
|
|
64
|
+
# Extract regions from synthetic screen
|
|
65
|
+
coherence_map = self.tensor_engine.compute_anisotropic_coherence(self.synthetic_screen)
|
|
66
|
+
regions = self.topology_engine.extract_field_boundaries(coherence_map)
|
|
67
|
+
|
|
68
|
+
# Run matcher
|
|
69
|
+
matches = TEODYMatcher.match_template_in_regions(self.synthetic_screen, template, regions, threshold=0.75)
|
|
70
|
+
|
|
71
|
+
# Verify matching behavior
|
|
72
|
+
self.assertIsInstance(matches, list)
|
|
73
|
+
if len(matches) > 0:
|
|
74
|
+
mx, my, mw, mh = matches[0]
|
|
75
|
+
self.assertGreaterEqual(mw, tw)
|
|
76
|
+
self.assertGreaterEqual(mh, th)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
unittest.main()
|