calango 2.2.6__tar.gz → 2.2.8__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.
- {calango-2.2.6 → calango-2.2.8}/PKG-INFO +5 -1
- {calango-2.2.6 → calango-2.2.8}/calango/__init__.py +1 -1
- {calango-2.2.6 → calango-2.2.8}/calango/media.py +172 -1
- calango-2.2.8/calango/utils.py +48 -0
- {calango-2.2.6 → calango-2.2.8}/calango.egg-info/PKG-INFO +5 -1
- {calango-2.2.6 → calango-2.2.8}/calango.egg-info/requires.txt +0 -2
- calango-2.2.6/calango/utils.py +0 -19
- {calango-2.2.6 → calango-2.2.8}/LICENSE +0 -0
- {calango-2.2.6 → calango-2.2.8}/README.md +0 -0
- {calango-2.2.6 → calango-2.2.8}/calango/devices.py +0 -0
- {calango-2.2.6 → calango-2.2.8}/calango/settings.py +0 -0
- {calango-2.2.6 → calango-2.2.8}/calango.egg-info/SOURCES.txt +0 -0
- {calango-2.2.6 → calango-2.2.8}/calango.egg-info/dependency_links.txt +0 -0
- {calango-2.2.6 → calango-2.2.8}/calango.egg-info/top_level.txt +0 -0
- {calango-2.2.6 → calango-2.2.8}/setup.cfg +0 -0
- {calango-2.2.6 → calango-2.2.8}/setup.py +0 -0
- {calango-2.2.6 → calango-2.2.8}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: calango
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.8
|
|
4
4
|
Summary: It looks like calango
|
|
5
5
|
Home-page: https://github.com/cereja-project/calango
|
|
6
6
|
Author: Joab Leite
|
|
@@ -11,6 +11,10 @@ Classifier: Operating System :: OS Independent
|
|
|
11
11
|
Requires-Python: >=3.6
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
+
Requires-Dist: cereja
|
|
15
|
+
Requires-Dist: opencv-python
|
|
16
|
+
Requires-Dist: matplotlib
|
|
17
|
+
Requires-Dist: numpy
|
|
14
18
|
|
|
15
19
|
# Calango Project
|
|
16
20
|
|
|
@@ -6,7 +6,7 @@ import subprocess
|
|
|
6
6
|
import threading
|
|
7
7
|
import time
|
|
8
8
|
from abc import abstractmethod
|
|
9
|
-
from typing import Union, Tuple, Sequence, Iterator
|
|
9
|
+
from typing import Union, Tuple, Sequence, Iterator, List
|
|
10
10
|
from urllib.parse import urlparse
|
|
11
11
|
import cereja as cj
|
|
12
12
|
import cv2
|
|
@@ -33,6 +33,14 @@ def is_url(val):
|
|
|
33
33
|
class Image(np.ndarray):
|
|
34
34
|
_GRAY_SCALE = 'GRAY_SCALE'
|
|
35
35
|
_color_mode = 'BGR'
|
|
36
|
+
COLORS_RANGE = {
|
|
37
|
+
'red': (np.array([0, 0, 100]), np.array([80, 80, 255])),
|
|
38
|
+
'green': (np.array([0, 100, 0]), np.array([80, 255, 80])),
|
|
39
|
+
'blue': (np.array([100, 0, 0]), np.array([255, 80, 80])),
|
|
40
|
+
'yellow': (np.array([0, 100, 100]), np.array([80, 255, 255])),
|
|
41
|
+
'cyan': (np.array([100, 100, 0]), np.array([255, 255, 80])),
|
|
42
|
+
'magenta': (np.array([100, 0, 100]), np.array([255, 80, 255])),
|
|
43
|
+
}
|
|
36
44
|
|
|
37
45
|
def __new__(cls, im: Union[str, np.ndarray] = None, color_mode: str = 'BGR', shape=None, dtype=None,
|
|
38
46
|
**kwargs) -> 'Image':
|
|
@@ -99,6 +107,129 @@ class Image(np.ndarray):
|
|
|
99
107
|
def set_border(self, size: int = 1, color: Tuple[int, int, int] = (0, 255, 0)):
|
|
100
108
|
cv2.rectangle(self, (size, size), (self.width - 1, self.height - 1), color, size)
|
|
101
109
|
|
|
110
|
+
def color_range(self, color: str):
|
|
111
|
+
assert color in self.COLORS_RANGE, f'Color {color} is not valid.'
|
|
112
|
+
return self.COLORS_RANGE[color]
|
|
113
|
+
|
|
114
|
+
def get_color_mask(self, lower, upper):
|
|
115
|
+
return cv2.inRange(self, lower, upper)
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def calculate_color_percentage(img, lower_bgr, upper_bgr):
|
|
119
|
+
|
|
120
|
+
# Create a mask with the pixels that fall within the color range
|
|
121
|
+
color_mask = cv2.inRange(img, lower_bgr, upper_bgr)
|
|
122
|
+
|
|
123
|
+
# Count the number of pixels in the color range
|
|
124
|
+
color_pixel_count = np.count_nonzero(color_mask)
|
|
125
|
+
|
|
126
|
+
# Count the total number of pixels in the image
|
|
127
|
+
total_pixels = img.shape[0] * img.shape[1]
|
|
128
|
+
|
|
129
|
+
# Calculate the percentage of the color
|
|
130
|
+
color_percentage = (color_pixel_count / total_pixels) * 100
|
|
131
|
+
return color_percentage
|
|
132
|
+
|
|
133
|
+
def get_strides(self, kernel_size, strides=1):
|
|
134
|
+
"""
|
|
135
|
+
Returns batches of fixed window size (kernel_size) with a given stride
|
|
136
|
+
@param kernel_size: window size
|
|
137
|
+
@param strides: default is 1
|
|
138
|
+
"""
|
|
139
|
+
for i in range(0, self.shape[0] - kernel_size + 1, strides):
|
|
140
|
+
for j in range(0, self.shape[1] - kernel_size + 1, strides):
|
|
141
|
+
yield self[i:i + kernel_size, j:j + kernel_size]
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def blue_percentage(self):
|
|
145
|
+
return self.calculate_color_percentage(self, *self.color_range('blue'))
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def green_percentage(self):
|
|
149
|
+
return self.calculate_color_percentage(self, *self.color_range('green'))
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def red_percentage(self):
|
|
153
|
+
return self.calculate_color_percentage(self, *self.color_range('red'))
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def yellow_percentage(self):
|
|
157
|
+
return self.calculate_color_percentage(self, *self.color_range('yellow'))
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def cyan_percentage(self):
|
|
161
|
+
return self.calculate_color_percentage(self, *self.color_range('cyan'))
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def magenta_percentage(self):
|
|
165
|
+
return self.calculate_color_percentage(self, *self.color_range('magenta'))
|
|
166
|
+
|
|
167
|
+
def hex_to_bgr(self, hex_color):
|
|
168
|
+
return tuple(int(hex_color[i:i + 2], 16) for i in (0, 2, 4))
|
|
169
|
+
|
|
170
|
+
def bgr_to_hex(self, bgr_color):
|
|
171
|
+
return '%02x%02x%02x' % bgr_color
|
|
172
|
+
|
|
173
|
+
def parse_color(self, color):
|
|
174
|
+
if isinstance(color, str):
|
|
175
|
+
if color.startswith('#'):
|
|
176
|
+
return self.hex_to_bgr(color[1:])
|
|
177
|
+
return self.hex_to_bgr(color)
|
|
178
|
+
return color
|
|
179
|
+
|
|
180
|
+
def get_color_percentage(self, min_color, max_color):
|
|
181
|
+
return self.calculate_color_percentage(self, self.parse_color(min_color), self.parse_color(max_color))
|
|
182
|
+
|
|
183
|
+
def similarity(self, image: Image, method=cv2.TM_CCOEFF_NORMED):
|
|
184
|
+
result = cv2.matchTemplate(self, image, method)
|
|
185
|
+
_, max_val, _, max_loc = cv2.minMaxLoc(result)
|
|
186
|
+
return max_val
|
|
187
|
+
|
|
188
|
+
def _padding_points(self, points, distance: 0):
|
|
189
|
+
if not len(points):
|
|
190
|
+
return []
|
|
191
|
+
points = cj.geolinear.find_best_locations(points, distance)
|
|
192
|
+
return points
|
|
193
|
+
|
|
194
|
+
def _match_template(self, template: np.ndarray, method=cv2.TM_CCOEFF_NORMED, threshold=None, all_locations=False,
|
|
195
|
+
distance_between_points=1):
|
|
196
|
+
threshold = threshold or 0.0
|
|
197
|
+
result = []
|
|
198
|
+
res = cv2.matchTemplate(self, template, method)
|
|
199
|
+
if all_locations:
|
|
200
|
+
loc = np.where(res >= threshold)
|
|
201
|
+
for n, pt in enumerate(zip(*loc[::-1])):
|
|
202
|
+
acc = res[pt[1], pt[0]]
|
|
203
|
+
result.append([int(pt[0] + template.shape[1] // 2), int(pt[1] + template.shape[0] // 2), acc])
|
|
204
|
+
else:
|
|
205
|
+
_, max_val, _, max_loc = cv2.minMaxLoc(res)
|
|
206
|
+
if max_val >= threshold:
|
|
207
|
+
result.append(
|
|
208
|
+
[int(max_loc[0] + template.shape[1] // 2), int(max_loc[1] + template.shape[0] // 2), max_val])
|
|
209
|
+
|
|
210
|
+
if distance_between_points > 1:
|
|
211
|
+
result = self._padding_points(result, distance_between_points)
|
|
212
|
+
return result
|
|
213
|
+
|
|
214
|
+
def match_template(self, template: Union[np.ndarray, List[np.ndarray]], method=cv2.TM_CCOEFF_NORMED,
|
|
215
|
+
threshold=None, all_locations=False, distance_between_points=1):
|
|
216
|
+
threshold = threshold or 0.0
|
|
217
|
+
|
|
218
|
+
result = []
|
|
219
|
+
if not isinstance(template, list):
|
|
220
|
+
template = [template]
|
|
221
|
+
for template in template:
|
|
222
|
+
res = self._match_template(template, method, threshold, all_locations=all_locations,
|
|
223
|
+
distance_between_points=distance_between_points)
|
|
224
|
+
result.extend(res)
|
|
225
|
+
return result
|
|
226
|
+
|
|
227
|
+
def find_locations_by_color(self, color, distance_between_points=1):
|
|
228
|
+
|
|
229
|
+
mask = self.get_color_mask(*self.color_range(color))
|
|
230
|
+
positions = np.where(mask == 255)[::-1]
|
|
231
|
+
return self._padding_points(list(zip(*positions)), distance_between_points)
|
|
232
|
+
|
|
102
233
|
@property
|
|
103
234
|
def mask(self):
|
|
104
235
|
return Image(np.zeros(self.shape[:2], dtype=np.uint8))
|
|
@@ -258,6 +389,16 @@ class Image(np.ndarray):
|
|
|
258
389
|
return Image(cv2.cvtColor(self, code_))
|
|
259
390
|
return self
|
|
260
391
|
|
|
392
|
+
def gray_to_bgr(self) -> Image:
|
|
393
|
+
self[:, ] = cv2.cvtColor(self, cv2.COLOR_GRAY2BGR)
|
|
394
|
+
self._color_mode = "BGR"
|
|
395
|
+
return self
|
|
396
|
+
|
|
397
|
+
def gray_to_rgb(self) -> Image:
|
|
398
|
+
self[:, ] = cv2.cvtColor(self, cv2.COLOR_GRAY2RGB)
|
|
399
|
+
self._color_mode = "RGB"
|
|
400
|
+
return self
|
|
401
|
+
|
|
261
402
|
def rgb_to_bgr(self) -> Image:
|
|
262
403
|
if self._color_mode == 'RGB':
|
|
263
404
|
self._color_mode = 'BGR'
|
|
@@ -285,6 +426,14 @@ class Image(np.ndarray):
|
|
|
285
426
|
180: cv2.ROTATE_180
|
|
286
427
|
}.get(degrees))
|
|
287
428
|
|
|
429
|
+
def put_on_center(self, img):
|
|
430
|
+
img = Image(img)
|
|
431
|
+
x, y = self.center_position
|
|
432
|
+
x1 = x - img.width // 2
|
|
433
|
+
y1 = y - img.height // 2
|
|
434
|
+
self[y1:y1 + img.height, x1: x1 + img.width] = img
|
|
435
|
+
return self
|
|
436
|
+
|
|
288
437
|
def crop_by_center(self, size=None, keep_scale=False) -> Image:
|
|
289
438
|
assert size is None or isinstance(size, (list, tuple)) and cj.is_numeric_sequence(size) and len(
|
|
290
439
|
size) == 2, 'Send HxW image cropped output'
|
|
@@ -307,6 +456,24 @@ class Image(np.ndarray):
|
|
|
307
456
|
|
|
308
457
|
return Image(cv2.resize(im_crop[start[1]:end[1], start[0]:end[0]], (new_w, new_h)))
|
|
309
458
|
|
|
459
|
+
@staticmethod
|
|
460
|
+
def remove_shadow(image):
|
|
461
|
+
if image.shape[-1] > 1:
|
|
462
|
+
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
|
463
|
+
|
|
464
|
+
# 2. Apply bilateral filter to smooth the image
|
|
465
|
+
bilateral = cv2.bilateralFilter(image, 15, 75, 75)
|
|
466
|
+
|
|
467
|
+
# 3. Compute the background using morphological closing
|
|
468
|
+
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (21, 21))
|
|
469
|
+
background = cv2.morphologyEx(bilateral, cv2.MORPH_CLOSE, kernel)
|
|
470
|
+
|
|
471
|
+
# 4. Compute the ratio of the grayscale and background image to remove shadows
|
|
472
|
+
ratio = (image.astype('float') / background.astype('float')) * 255.0
|
|
473
|
+
shadow_removed = np.clip(ratio, 0, 255).astype('uint8')
|
|
474
|
+
|
|
475
|
+
return shadow_removed
|
|
476
|
+
|
|
310
477
|
def prune(self) -> Image:
|
|
311
478
|
min_len = self.min_len
|
|
312
479
|
return Image(self.crop_by_center((min_len, min_len)))
|
|
@@ -317,6 +484,10 @@ class Image(np.ndarray):
|
|
|
317
484
|
cv2.circle(self, position, radius, color, thickness)
|
|
318
485
|
return self
|
|
319
486
|
|
|
487
|
+
def rect(self, start, end, color=(0, 0, 0), thickness=1):
|
|
488
|
+
cv2.rectangle(self, start, end, color, thickness)
|
|
489
|
+
return self
|
|
490
|
+
|
|
320
491
|
def get_mask_circle(self, radius=None, position=None):
|
|
321
492
|
return self.mask.circle(radius=radius, position=position, color=(255, 255, 255), thickness=-1)
|
|
322
493
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import io
|
|
3
|
+
from .settings import ON_COLAB_JUPYTER
|
|
4
|
+
from matplotlib import pyplot as plt
|
|
5
|
+
import math
|
|
6
|
+
|
|
7
|
+
__all__ = ['show_local_mp4']
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def show_local_mp4(file_name, width=640, height=480):
|
|
11
|
+
assert ON_COLAB_JUPYTER, 'show_local_mp4 can bee used only on colab runtime'
|
|
12
|
+
# noinspection PyUnresolvedReferences
|
|
13
|
+
from IPython.display import HTML
|
|
14
|
+
video_encoded = base64.b64encode(io.open(file_name, "rb").read())
|
|
15
|
+
return HTML(
|
|
16
|
+
data="""<video width="{0}" height="{1}" alt="test" controls>
|
|
17
|
+
<source src="data:video/mp4;base64,{2}" type="video/mp4" />
|
|
18
|
+
</video>""".format(
|
|
19
|
+
width, height, video_encoded.decode("ascii")
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ImagePlot:
|
|
25
|
+
def __init__(self, fig_size=(13, 13), max_cols=4):
|
|
26
|
+
self.fig_size = fig_size
|
|
27
|
+
self.figure = plt.figure(figsize=fig_size) # TODO: does it need to be global?
|
|
28
|
+
self._max_cols = max_cols
|
|
29
|
+
self._total_images = 0
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def current_row(self):
|
|
33
|
+
return math.ceil(self._total_images / self._max_cols)
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def current_col(self):
|
|
37
|
+
return ((self._total_images - 1) % self._max_cols) + 1
|
|
38
|
+
|
|
39
|
+
def plot(self, image, title=None, color='gray'):
|
|
40
|
+
try:
|
|
41
|
+
self._total_images += 1
|
|
42
|
+
plt.subplot(self._max_cols, self._max_cols, self._total_images)
|
|
43
|
+
plt.title(self.current_col if title is None else title)
|
|
44
|
+
plt.xticks([]), plt.yticks([])
|
|
45
|
+
plt.imshow(image.copy(), color)
|
|
46
|
+
except Exception as err:
|
|
47
|
+
self._total_images -= 1 # garante consistencia da quantidade de imagens
|
|
48
|
+
raise err
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: calango
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.8
|
|
4
4
|
Summary: It looks like calango
|
|
5
5
|
Home-page: https://github.com/cereja-project/calango
|
|
6
6
|
Author: Joab Leite
|
|
@@ -11,6 +11,10 @@ Classifier: Operating System :: OS Independent
|
|
|
11
11
|
Requires-Python: >=3.6
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
+
Requires-Dist: cereja
|
|
15
|
+
Requires-Dist: opencv-python
|
|
16
|
+
Requires-Dist: matplotlib
|
|
17
|
+
Requires-Dist: numpy
|
|
14
18
|
|
|
15
19
|
# Calango Project
|
|
16
20
|
|
calango-2.2.6/calango/utils.py
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import base64
|
|
2
|
-
import io
|
|
3
|
-
from .settings import ON_COLAB_JUPYTER
|
|
4
|
-
|
|
5
|
-
__all__ = ['show_local_mp4']
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def show_local_mp4(file_name, width=640, height=480):
|
|
9
|
-
assert ON_COLAB_JUPYTER, 'show_local_mp4 can bee used only on colab runtime'
|
|
10
|
-
# noinspection PyUnresolvedReferences
|
|
11
|
-
from IPython.display import HTML
|
|
12
|
-
video_encoded = base64.b64encode(io.open(file_name, "rb").read())
|
|
13
|
-
return HTML(
|
|
14
|
-
data="""<video width="{0}" height="{1}" alt="test" controls>
|
|
15
|
-
<source src="data:video/mp4;base64,{2}" type="video/mp4" />
|
|
16
|
-
</video>""".format(
|
|
17
|
-
width, height, video_encoded.decode("ascii")
|
|
18
|
-
)
|
|
19
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|