calango 2.2.7__tar.gz → 2.2.9__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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: calango
3
- Version: 2.2.7
3
+ Version: 2.2.9
4
4
  Summary: It looks like calango
5
5
  Home-page: https://github.com/cereja-project/calango
6
6
  Author: Joab Leite
@@ -15,6 +15,17 @@ Requires-Dist: cereja
15
15
  Requires-Dist: opencv-python
16
16
  Requires-Dist: matplotlib
17
17
  Requires-Dist: numpy
18
+ Requires-Dist: scikit-learn
19
+ Dynamic: author
20
+ Dynamic: author-email
21
+ Dynamic: classifier
22
+ Dynamic: description
23
+ Dynamic: description-content-type
24
+ Dynamic: home-page
25
+ Dynamic: license-file
26
+ Dynamic: requires-dist
27
+ Dynamic: requires-python
28
+ Dynamic: summary
18
29
 
19
30
  # Calango Project
20
31
 
@@ -26,5 +26,7 @@ from . import settings
26
26
  from .devices import Mouse
27
27
  from .media import Image, VideoWriter, Video
28
28
 
29
- VERSION = "2.2.7.final.0"
29
+
30
+ VERSION = "2.2.9.final.0"
31
+
30
32
  __version__ = get_version_pep440_compliant(VERSION)
@@ -6,13 +6,14 @@ 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
13
13
  import numpy as np
14
14
  import logging
15
15
  from matplotlib import pyplot as plt
16
+ from sklearn.cluster import KMeans
16
17
 
17
18
  from .devices import Mouse
18
19
  from .settings import ON_COLAB_JUPYTER
@@ -33,6 +34,14 @@ def is_url(val):
33
34
  class Image(np.ndarray):
34
35
  _GRAY_SCALE = 'GRAY_SCALE'
35
36
  _color_mode = 'BGR'
37
+ COLORS_RANGE = {
38
+ 'red': (np.array([0, 0, 100]), np.array([80, 80, 255])),
39
+ 'green': (np.array([0, 100, 0]), np.array([80, 255, 80])),
40
+ 'blue': (np.array([100, 0, 0]), np.array([255, 80, 80])),
41
+ 'yellow': (np.array([0, 100, 100]), np.array([80, 255, 255])),
42
+ 'cyan': (np.array([100, 100, 0]), np.array([255, 255, 80])),
43
+ 'magenta': (np.array([100, 0, 100]), np.array([255, 80, 255])),
44
+ }
36
45
 
37
46
  def __new__(cls, im: Union[str, np.ndarray] = None, color_mode: str = 'BGR', shape=None, dtype=None,
38
47
  **kwargs) -> 'Image':
@@ -99,6 +108,129 @@ class Image(np.ndarray):
99
108
  def set_border(self, size: int = 1, color: Tuple[int, int, int] = (0, 255, 0)):
100
109
  cv2.rectangle(self, (size, size), (self.width - 1, self.height - 1), color, size)
101
110
 
111
+ def color_range(self, color: str):
112
+ assert color in self.COLORS_RANGE, f'Color {color} is not valid.'
113
+ return self.COLORS_RANGE[color]
114
+
115
+ def get_color_mask(self, lower, upper):
116
+ return cv2.inRange(self, lower, upper)
117
+
118
+ @staticmethod
119
+ def calculate_color_percentage(img, lower_bgr, upper_bgr):
120
+
121
+ # Create a mask with the pixels that fall within the color range
122
+ color_mask = cv2.inRange(img, lower_bgr, upper_bgr)
123
+
124
+ # Count the number of pixels in the color range
125
+ color_pixel_count = np.count_nonzero(color_mask)
126
+
127
+ # Count the total number of pixels in the image
128
+ total_pixels = img.shape[0] * img.shape[1]
129
+
130
+ # Calculate the percentage of the color
131
+ color_percentage = (color_pixel_count / total_pixels) * 100
132
+ return color_percentage
133
+
134
+ def get_strides(self, kernel_size, strides=1):
135
+ """
136
+ Returns batches of fixed window size (kernel_size) with a given stride
137
+ @param kernel_size: window size
138
+ @param strides: default is 1
139
+ """
140
+ for i in range(0, self.shape[0] - kernel_size + 1, strides):
141
+ for j in range(0, self.shape[1] - kernel_size + 1, strides):
142
+ yield self[i:i + kernel_size, j:j + kernel_size]
143
+
144
+ @property
145
+ def blue_percentage(self):
146
+ return self.calculate_color_percentage(self, *self.color_range('blue'))
147
+
148
+ @property
149
+ def green_percentage(self):
150
+ return self.calculate_color_percentage(self, *self.color_range('green'))
151
+
152
+ @property
153
+ def red_percentage(self):
154
+ return self.calculate_color_percentage(self, *self.color_range('red'))
155
+
156
+ @property
157
+ def yellow_percentage(self):
158
+ return self.calculate_color_percentage(self, *self.color_range('yellow'))
159
+
160
+ @property
161
+ def cyan_percentage(self):
162
+ return self.calculate_color_percentage(self, *self.color_range('cyan'))
163
+
164
+ @property
165
+ def magenta_percentage(self):
166
+ return self.calculate_color_percentage(self, *self.color_range('magenta'))
167
+
168
+ def hex_to_bgr(self, hex_color):
169
+ return tuple(int(hex_color[i:i + 2], 16) for i in (0, 2, 4))
170
+
171
+ def bgr_to_hex(self, bgr_color):
172
+ return '%02x%02x%02x' % bgr_color
173
+
174
+ def parse_color(self, color):
175
+ if isinstance(color, str):
176
+ if color.startswith('#'):
177
+ return self.hex_to_bgr(color[1:])
178
+ return self.hex_to_bgr(color)
179
+ return color
180
+
181
+ def get_color_percentage(self, min_color, max_color):
182
+ return self.calculate_color_percentage(self, self.parse_color(min_color), self.parse_color(max_color))
183
+
184
+ def similarity(self, image: Image, method=cv2.TM_CCOEFF_NORMED):
185
+ result = cv2.matchTemplate(self, image, method)
186
+ _, max_val, _, max_loc = cv2.minMaxLoc(result)
187
+ return max_val
188
+
189
+ def _padding_points(self, points, distance: 0):
190
+ if not len(points):
191
+ return []
192
+ points = cj.geolinear.find_best_locations(points, distance)
193
+ return points
194
+
195
+ def _match_template(self, template: np.ndarray, method=cv2.TM_CCOEFF_NORMED, threshold=None, all_locations=False,
196
+ distance_between_points=1):
197
+ threshold = threshold or 0.0
198
+ result = []
199
+ res = cv2.matchTemplate(self, template, method)
200
+ if all_locations:
201
+ loc = np.where(res >= threshold)
202
+ for n, pt in enumerate(zip(*loc[::-1])):
203
+ acc = res[pt[1], pt[0]]
204
+ result.append([int(pt[0] + template.shape[1] // 2), int(pt[1] + template.shape[0] // 2), acc])
205
+ else:
206
+ _, max_val, _, max_loc = cv2.minMaxLoc(res)
207
+ if max_val >= threshold:
208
+ result.append(
209
+ [int(max_loc[0] + template.shape[1] // 2), int(max_loc[1] + template.shape[0] // 2), max_val])
210
+
211
+ if distance_between_points > 1:
212
+ result = self._padding_points(result, distance_between_points)
213
+ return result
214
+
215
+ def match_template(self, template: Union[np.ndarray, List[np.ndarray]], method=cv2.TM_CCOEFF_NORMED,
216
+ threshold=None, all_locations=False, distance_between_points=1):
217
+ threshold = threshold or 0.0
218
+
219
+ result = []
220
+ if not isinstance(template, list):
221
+ template = [template]
222
+ for template in template:
223
+ res = self._match_template(template, method, threshold, all_locations=all_locations,
224
+ distance_between_points=distance_between_points)
225
+ result.extend(res)
226
+ return result
227
+
228
+ def find_locations_by_color(self, color, distance_between_points=1):
229
+
230
+ mask = self.get_color_mask(*self.color_range(color))
231
+ positions = np.where(mask == 255)[::-1]
232
+ return self._padding_points(list(zip(*positions)), distance_between_points)
233
+
102
234
  @property
103
235
  def mask(self):
104
236
  return Image(np.zeros(self.shape[:2], dtype=np.uint8))
@@ -295,6 +427,14 @@ class Image(np.ndarray):
295
427
  180: cv2.ROTATE_180
296
428
  }.get(degrees))
297
429
 
430
+ def put_on_center(self, img):
431
+ img = Image(img)
432
+ x, y = self.center_position
433
+ x1 = x - img.width // 2
434
+ y1 = y - img.height // 2
435
+ self[y1:y1 + img.height, x1: x1 + img.width] = img
436
+ return self
437
+
298
438
  def crop_by_center(self, size=None, keep_scale=False) -> Image:
299
439
  assert size is None or isinstance(size, (list, tuple)) and cj.is_numeric_sequence(size) and len(
300
440
  size) == 2, 'Send HxW image cropped output'
@@ -345,6 +485,10 @@ class Image(np.ndarray):
345
485
  cv2.circle(self, position, radius, color, thickness)
346
486
  return self
347
487
 
488
+ def rect(self, start, end, color=(0, 0, 0), thickness=1):
489
+ cv2.rectangle(self, start, end, color, thickness)
490
+ return self
491
+
348
492
  def get_mask_circle(self, radius=None, position=None):
349
493
  return self.mask.circle(radius=radius, position=position, color=(255, 255, 255), thickness=-1)
350
494
 
@@ -377,7 +521,43 @@ class Image(np.ndarray):
377
521
  def save(self, p: str):
378
522
  cv2.imwrite(p, self)
379
523
  assert cj.Path(p).exists, f'Error saving image {p}'
524
+ def get_background_foreground_colors(self, k=2):
525
+ """
526
+ Função para determinar as cores RGB do background e do foreground em uma imagem.
527
+
528
+ Parâmetros:
529
+ image (np.array): imagem em formato de array numpy.
530
+ k (int): número de clusters para segmentação de cores (2 significa fundo e primeiro plano).
531
+
532
+ Retorna:
533
+ dict: dicionário com as cores RGB do background e do foreground.
534
+ """
535
+
536
+ # Redimensiona a imagem para acelerar o processamento
537
+ resize_dim = (300, 300)
538
+ image_resized = cv2.resize(self, resize_dim)
539
+ image_rgb = cv2.cvtColor(image_resized, cv2.COLOR_BGR2RGB)
540
+
541
+ # Converte a imagem para um array de pixels
542
+ pixels = image_rgb.reshape(-1, 3)
543
+
544
+ # Aplica KMeans para segmentar em duas cores (background e foreground)
545
+ kmeans = KMeans(n_clusters=k, random_state=0)
546
+ kmeans.fit(pixels)
547
+
548
+ # Obtém as cores dos clusters
549
+ cluster_colors = kmeans.cluster_centers_
550
+ cluster_labels, counts = np.unique(kmeans.labels_, return_counts=True)
551
+
552
+ # Identifica o background como a cor com mais pixels
553
+ background_idx = cluster_labels[np.argmax(counts)]
554
+ foreground_idx = cluster_labels[np.argmin(counts)]
555
+
556
+ # Converte as cores para inteiros RGB
557
+ background_rgb = tuple(map(int, cluster_colors[background_idx]))
558
+ foreground_rgb = tuple(map(int, cluster_colors[foreground_idx]))
380
559
 
560
+ return {"background_rgb": background_rgb, "foreground_rgb": foreground_rgb}
381
561
  def plot_colors_histogram(self):
382
562
  # tuple to select colors of each channel line
383
563
  colors = ("red", "green", "blue") if self._color_mode == 'RGB' else ('blue', 'green', 'red')
@@ -504,6 +684,24 @@ class Image(np.ndarray):
504
684
  dir_path = cj.Path(dir_path)
505
685
  return [cls(im_p.path) for im_p in dir_path.list_files(ext=ext)]
506
686
 
687
+ @classmethod
688
+ def _get_image_from_window(cls):
689
+ with cj.TempDir() as tmp:
690
+ while True:
691
+ window = yield
692
+ p = tmp.path.join('frame.bmp')
693
+ window.capture_image_bmp(p.path)
694
+ yield Image(p.path)
695
+
696
+ @classmethod
697
+ def from_window(cls, window: cj.Window) -> 'Image':
698
+ """
699
+ Get image from window
700
+ """
701
+ gen = cls._get_image_from_window()
702
+ next(gen)
703
+ return gen.send(window)
704
+
507
705
 
508
706
  class VideoWriter:
509
707
  def __init__(self, p, fourcc=None, width=None, height=None, fps=30):
@@ -814,6 +1012,63 @@ class Screen(_IVideo):
814
1012
  self._capture = False
815
1013
 
816
1014
 
1015
+ class WindowStream(_IVideo):
1016
+
1017
+ def __init__(self, window: cj.Window, *args, fps=None, **kwargs):
1018
+ self._window = window
1019
+ self._fps = fps or 30
1020
+ self._total_frames = -1
1021
+ self._capture = True
1022
+ self._frames = self.__get_frame()
1023
+ self._width, self._height = self._window.size
1024
+
1025
+ def set_fps(self, fps: Union[int, float]) -> None:
1026
+ assert isinstance(fps, (int, float)), ValueError(f'{fps} value for fps is not valid. Send int or float.')
1027
+ self._fps = fps
1028
+
1029
+ @property
1030
+ def next_frame(self) -> Tuple[bool, Union[np.ndarray, None]]:
1031
+ return True, next(self._frames)
1032
+
1033
+ def __get_frame(self):
1034
+ with cj.TempDir() as tmp:
1035
+ while self._capture:
1036
+ p = tmp.path.join('frame.bmp')
1037
+ self._window.capture_image_bmp(p.path)
1038
+ yield Image(p.path)
1039
+
1040
+ @property
1041
+ def width(self) -> int:
1042
+ return self._window.size[0]
1043
+
1044
+ @property
1045
+ def height(self) -> int:
1046
+ return self._window.size[1]
1047
+
1048
+ @property
1049
+ def total_frames(self) -> int:
1050
+ return self._total_frames
1051
+
1052
+ @property
1053
+ def fps(self) -> Union[int, float]:
1054
+ return self._fps
1055
+
1056
+ @property
1057
+ def is_webcam(self) -> bool:
1058
+ return True
1059
+
1060
+ @property
1061
+ def is_stream(self) -> bool:
1062
+ return False
1063
+
1064
+ @property
1065
+ def is_opened(self) -> bool:
1066
+ return self._capture
1067
+
1068
+ def stop(self):
1069
+ self._capture = False
1070
+
1071
+
817
1072
  class Video:
818
1073
 
819
1074
  def __init__(self, *args, fps=None, frame_preprocess=None, **kwargs):
@@ -833,7 +1088,9 @@ class Video:
833
1088
 
834
1089
  def _build(self):
835
1090
  if len(self._args):
836
- if isinstance(self._args[0], str):
1091
+ if isinstance(self._args[0], cj.Window):
1092
+ self._cap = WindowStream(window=self._args[0], *self._args[1:], **self._kwargs)
1093
+ elif isinstance(self._args[0], str):
837
1094
  if self._args[0] == 'monitor':
838
1095
  self._cap = Screen()
839
1096
  elif cj.request.is_url(self._args[0]):
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: calango
3
- Version: 2.2.7
3
+ Version: 2.2.9
4
4
  Summary: It looks like calango
5
5
  Home-page: https://github.com/cereja-project/calango
6
6
  Author: Joab Leite
@@ -15,6 +15,17 @@ Requires-Dist: cereja
15
15
  Requires-Dist: opencv-python
16
16
  Requires-Dist: matplotlib
17
17
  Requires-Dist: numpy
18
+ Requires-Dist: scikit-learn
19
+ Dynamic: author
20
+ Dynamic: author-email
21
+ Dynamic: classifier
22
+ Dynamic: description
23
+ Dynamic: description-content-type
24
+ Dynamic: home-page
25
+ Dynamic: license-file
26
+ Dynamic: requires-dist
27
+ Dynamic: requires-python
28
+ Dynamic: summary
18
29
 
19
30
  # Calango Project
20
31
 
@@ -2,3 +2,4 @@ cereja
2
2
  opencv-python
3
3
  matplotlib
4
4
  numpy
5
+ scikit-learn
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes