Repository: wb-08/PokerVision Branch: main Commit: 7bef0d4568b1 Files: 12 Total size: 35.8 KB Directory structure: gitextract_yixxlhtu/ ├── readme.md ├── requirements.txt ├── scripts/ │ ├── __init__.py │ ├── config.yaml │ ├── equity.py │ ├── grab_table.py │ ├── info_box.py │ ├── pokerstars_recognition.py │ ├── table_recognition.py │ └── utils.py └── unittests/ ├── TestTableRecognition.py └── test_config.yaml ================================================ FILE CONTENTS ================================================ ================================================ FILE: readme.md ================================================ # PokerVision Recognition of all entities on the poker table( now only for Pokerstars) and added analytics on the basis of which you can make decisions about your moves ![pokerstars.gif](pokerstars.gif) NOTE: it only works with the theme that I have and for 6 players. It has been tested on ubuntu with python version 3.8 # Installing and Running ``` $ git clone https://github.com/wb-08/PokerVision $ cd PokerVision $ virtualenv venv $ source venv/bin/activate $ pip3 install -r requirements.txt $ cd scripts $ python3 grab_table.py ``` ================================================ FILE: requirements.txt ================================================ eval7==0.1.9 future==0.18.3 mss==9.0.1 numpy==1.24.3 opencv-contrib-python==4.5.5.62 opencv-python==4.5.5.62 Pillow==9.5.0 pyparsing==3.0.9 PyYAML==6.0 Cython ================================================ FILE: scripts/__init__.py ================================================ ================================================ FILE: scripts/config.yaml ================================================ paths: hero_cards_suits: '../images_templates/hero_cards_suits/' hero_cards_numbers: '../images_templates/hero_cards_numbers/' table_cards_numbers: '../images_templates/table_cards_numbers/' table_cards_suits: '../images_templates/table_cards_suits/' pot_numbers: '../images_templates/pot_numbers/' pot_image: '../images_templates/pot/pot.png' dealer_button: '../images_templates/dealer_button/button.png' empty_seat: '../images_templates/empty_seat/empty_seat.png' sitting_out: '../images_templates/sitting_out/so.png' table_size: width: 1090 height: 900 info_box_size: width: 470 height: 500 hero_step_define: x_0: 511 y_0: 605 x_1: 528 y_1: 644 lower_gray_color: [0, 5, 50] upper_gray_color: [179, 10, 255] min_white_pixels: 400 hero_cards: x_0: 478 y_0: 535 x_1: 616 y_1: 592 # x coordinate where the card ends separator_1: 67 separator_2: 137 table_cards: x_0: 364 y_0: 319 x_1: 730 y_1: 414 # x coordinate where the card ends separator_1: 72 separator_2: 144 separator_3: 218 separator_4: 290 separator_5: 363 pot: # in what area looking for pot x_0: 472 y_0: 279 x_1: 636 y_1: 328 # the maximum width and height over which # the sum of the bet can be placed width: 100 height: 20 pot_template_width: 33 player_center_coordinates: # the first player is the hero and then clockwise # coordinates are stored in [x, y] format 1: [469, 598] 2: [208,463] 3: [237,278] 4: [620,200] 5: [857, 274] 6: [867, 486] players_coordinates: # coordinates are stored in [x_0, y_0, x_1, y_1] format 1: [437,592,655,661] 2: [21,450, 245,525] 3: [49,214, 275,285] 4: [437, 139, 659, 213] 5: [814, 214, 1040,288] 6: [850, 449, 1075, 524] players_bet: # coordinates are stored in [x_0, y_0, x_1, y_1] format 2: [279, 457, 379, 480] 3: [292, 296, 392, 318] 4: [600, 222, 700, 246] 5: [688, 270, 788, 292] 6: [717, 457, 817, 480] ================================================ FILE: scripts/equity.py ================================================ import numpy as np import eval7 def calc_equity(deck, hero_cards, table_cards, iters=100000): """ Parameters: deck(list of str): all other cards, not including cards that are on the table and hero cards hero_cards(list of str): cards that belong to the hero table_cards(list of str): cards that are on the table iters(int): the amount that the table generates Returns: """ deck = [eval7.Card(card) for card in deck] table_cards = [eval7.Card(card) for card in table_cards] hero_cards = [eval7.Card(card) for card in hero_cards] max_table_cards = 5 win_count = 0 for _ in range(iters): np.random.shuffle(deck) num_remaining = max_table_cards - len(table_cards) draw = deck[:num_remaining+2] opp_hole, remaining_comm = draw[:2], draw[2:] player_hand = hero_cards + table_cards + remaining_comm opp_hand = opp_hole + table_cards + remaining_comm player_strength = eval7.evaluate(player_hand) opp_strength = eval7.evaluate(opp_hand) if player_strength > opp_strength: win_count += 1 win_prob = (win_count / iters) * 100 return round(win_prob, 2) ================================================ FILE: scripts/grab_table.py ================================================ from PIL import Image import numpy as np import cv2 from mss import mss from pokerstars_recognition import PokerStarsTableRecognizer from utils import read_config_file, set_window_size, remove_cards, data_concatenate from equity import calc_equity from info_box import update_label sct = mss() config = read_config_file() set_window_size() table_data = [] while True: updated_table_data = [] monitor = {'top': 80, 'left': 70, 'width': config['table_size']['width'], 'height': config['table_size']['height']} img = Image.frombytes('RGB', (config['table_size']['width'], config['table_size']['height']), sct.grab(monitor).rgb) img = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2RGB) recognizer = PokerStarsTableRecognizer(img, config) hero_step = recognizer.detect_hero_step() if hero_step: hero_cards = recognizer.detect_hero_cards() table_cards = recognizer.detect_table_cards() total_pot = recognizer.find_total_pot() updated_table_data.append([hero_cards, table_cards, total_pot]) if table_data == updated_table_data: pass else: table_data = updated_table_data deck = remove_cards(hero_cards, table_cards) equity = calc_equity(deck, hero_cards, table_cards) players_info = recognizer.get_dealer_button_position() players_info = recognizer.get_empty_seats(players_info) players_info = recognizer.get_so_players(players_info) players_info = recognizer.assign_positions(players_info) players_info = recognizer.find_players_bet(players_info) text = data_concatenate(hero_cards, table_cards, total_pot, equity, players_info) update_label(text) ================================================ FILE: scripts/info_box.py ================================================ from tkinter import * from scripts.utils import read_config_file cfg = read_config_file() root = Tk() root.geometry('{0}x{1}'.format(cfg['info_box_size']['width'], cfg['info_box_size']['height'])) root.title('PokerStarsHelper') root.configure(background='ivory3') lab = Label(root, anchor="w", justify=LEFT, font=("Arial", 18)) lab.pack(fill="both", expand=True) def update_label(text): lab.configure(text=text) root.update() ================================================ FILE: scripts/pokerstars_recognition.py ================================================ import cv2 from scripts.table_recognition import PokerTableRecognizer from scripts.utils import sort_bboxes, thresholding, card_separator, table_part_recognition, \ convert_contours_to_bboxes, find_by_template, find_closer_point, read_config_file import numpy as np class PokerStarsTableRecognizer(PokerTableRecognizer): def __init__(self, img, cfg): """ Parameters: img(numpy.ndarray): image of the whole table cfg (dict): config file """ self.img = img self.cfg = cfg def detect_hero_step(self): """ Based on the area under hero's cards, we determine whether hero should make a move Returns: Boolean Value(True or False): True, if hero step now """ res_img = self.img[self.cfg['hero_step_define']['y_0']:self.cfg['hero_step_define']['y_1'], self.cfg['hero_step_define']['x_0']:self.cfg['hero_step_define']['x_1']] hsv_img = cv2.cvtColor(res_img, cv2.COLOR_BGR2HSV_FULL) mask = cv2.inRange(hsv_img, np.array(self.cfg['hero_step_define']['lower_gray_color']), np.array(self.cfg['hero_step_define']['upper_gray_color'])) count_of_white_pixels = cv2.countNonZero(mask) return True if count_of_white_pixels > self.cfg['hero_step_define']['min_white_pixels'] else False def detect_cards(self, separators, sort_bboxes_method, cards_coordinates, path_to_numbers, path_to_suits): """ Parameters: separators(list of int): contains values where the card ends sort_bboxes_method(str): defines how we will sort the contours. It can be left-to-right, bottom-to-top, top-to-bottom cards_coordinates(str): path to cards coordinates path_to_numbers(str): path where located numbers (J, K etc.) path_to_suits(str) : path where located suits Returns: cards_name(list of str): name of the cards """ cards_name = [] img = self.img[self.cfg[cards_coordinates]['y_0']:self.cfg[cards_coordinates]['y_1'], self.cfg[cards_coordinates]['x_0']:self.cfg[cards_coordinates]['x_1']] binary_img = thresholding(img, 200, 255) contours, _ = cv2.findContours(binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) bounding_boxes = convert_contours_to_bboxes(contours, 10, 2) bounding_boxes = sort_bboxes(bounding_boxes, method=sort_bboxes_method) cards_bboxes_dct = card_separator(bounding_boxes, separators) for _, cards_bboxes in cards_bboxes_dct.items(): if len(cards_bboxes) == 3: cards_bboxes = [cards_bboxes[0]] elif len(cards_bboxes) == 0: return [] elif len(cards_bboxes) > 3: raise ValueError("The number of bounding boxes should not be more than 3!") card_name = '' for key, bbox in enumerate(cards_bboxes): color_of_img, directory = (cv2.IMREAD_COLOR, self.cfg['paths'][path_to_suits]) if key == 0 \ else (cv2.IMREAD_GRAYSCALE, self.cfg['paths'][path_to_numbers]) res_img = img[bbox[1]:bbox[3], bbox[0]:bbox[2]] card_part = table_part_recognition(res_img, directory, color_of_img) card_name = card_part + 'T' if len(cards_bboxes) == 1 else card_name + card_part cards_name.append(card_name[::-1]) return cards_name def detect_hero_cards(self): """ Returns: cards_name(list of str): name of the hero's cards """ separators = [self.cfg['hero_cards']['separator_1'], self.cfg['hero_cards']['separator_2']] sort_bboxes_method = 'bottom-to-top' cards_coordinates = 'hero_cards' path_to_numbers = 'hero_cards_numbers' path_to_suits = 'hero_cards_suits' cards_name = self.detect_cards(separators, sort_bboxes_method, cards_coordinates, path_to_numbers, path_to_suits) return cards_name def detect_table_cards(self): """ Returns: cards_name(list of str): name of the cards on the table """ separators = [self.cfg['table_cards']['separator_1'], self.cfg['table_cards']['separator_2'], self.cfg['table_cards']['separator_3'], self.cfg['table_cards']['separator_4'], self.cfg['table_cards']['separator_5']] sort_bboxes_method = 'top-to-bottom' cards_coordinates = 'table_cards' path_to_numbers = 'table_cards_numbers' path_to_suits = 'table_cards_suits' cards_name = self.detect_cards(separators, sort_bboxes_method, cards_coordinates, path_to_numbers, path_to_suits) return cards_name def find_total_pot(self): """ Returns: number(str): number with total pot """ img = self.img[self.cfg['pot']['y_0']:self.cfg['pot']['y_1'], self.cfg['pot']['x_0']:self.cfg['pot']['x_1']] _, max_loc = find_by_template(img, self.cfg['paths']['pot_image']) bet_img = img[max_loc[1] - 3:max_loc[1] + self.cfg['pot']['height'], max_loc[0] + self.cfg['pot']['pot_template_width']: max_loc[0] + self.cfg['pot']['pot_template_width'] + self.cfg['pot']['width']] binary_img = thresholding(bet_img, 105, 255) contours, _ = cv2.findContours(binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) bounding_boxes = convert_contours_to_bboxes(contours, 3, 1) bounding_boxes = sort_bboxes(bounding_boxes, method='left-to-right') number = '' for bbox in bounding_boxes: number_img = bet_img[bbox[1]:bbox[3], bbox[0]:bbox[2]] symbol = table_part_recognition(number_img, self.cfg['paths']['pot_numbers'], cv2.IMREAD_GRAYSCALE) number += symbol return number def get_dealer_button_position(self): """ determine who is closer to the dealer button Returns: player_info(dict): here is information about all players as it becomes available """ player_info = {key: value for key in range(1, 7) for value in ['']} players_coordinates = self.cfg['player_center_coordinates'] _, button_coordinates = find_by_template(self.img, self.cfg['paths']['dealer_button']) player_with_button = find_closer_point(players_coordinates, button_coordinates) player_info[player_with_button] = 'dealer_button' return player_info def get_missing_players(self, players_info, path_to_template_img, flag): """ find players who are currently absent for various reasons Parameters: players_info(dict): key - player number, value - '' - if the player is in the game; '-' - if a player's seat is available; '-so-' - if the player is absent path_to_template_img(str): the path to the images where the benchmark images are located flag(str): It can be - and -so- Returns: players_info(dict): info about players """ players_coordinates = self.cfg['players_coordinates'] players_for_checking = [key for key, value in players_info.items() if value == ''] for player, bbox in players_coordinates.items(): if player != 1 and player in players_for_checking: player_img = self.img[bbox[1]:bbox[3], bbox[0]:bbox[2]] max_val, _ = find_by_template(player_img, self.cfg['paths'][path_to_template_img]) if max_val > 0.8: players_info[player] = flag return players_info def get_empty_seats(self, players_info): """ find players whose places are currently vacant """ path_to_template_img = 'empty_seat' flag = '-' players_info = self.get_missing_players(players_info, path_to_template_img, flag) return players_info def get_so_players(self, players_info): """ find players who are not currently in the game """ path_to_template_img = 'sitting_out' flag = '-so-' players_info = self.get_missing_players(players_info, path_to_template_img, flag) return players_info def assign_positions(self, players_info): """ assign each player one of six positions if the player in the game Parameters: players_info(dict): info about players in {1:'', 2: 'dealer_button',3:'-so-' etc. } format Returns: players_info(dict): info about players in {1: 'BB', 2: 'SB', 3: '-so-' etc. } format """ busy_seats = [k for k, v in players_info.items() if v != '-' and v != '-so-'] exist_positions = ['BTN', 'SB', 'BB', 'UTG', 'MP', 'CO'] del exist_positions[3:3 + (6 - len(busy_seats))] player_with_button = [k for k, v in players_info.items() if v == 'dealer_button'][0] for index, player_number in enumerate( busy_seats[busy_seats.index(player_with_button):] + busy_seats[:busy_seats.index(player_with_button)]): if len(busy_seats) == 2: position = 'SB' if index == 0 else 'BB' players_info[player_number] = position else: players_info[player_number] = exist_positions[index] return players_info def find_players_bet(self, players_info): """ Parameters: players_info(dict): info about players in {1:'BTN', 2:'SB', 3:'BB' etc. } format Returns: updated_players_info(dict): info about players in {'Hero':'BTN', 'SB':'', 'BB':'50' etc. } format """ players_bet_location = self.cfg['players_bet'] updated_players_info = {'Hero': players_info[1]} for i, location_coordinates in players_bet_location.items(): if players_info[i] not in ('-so-', '-'): bet_img = self.img[location_coordinates[1]:location_coordinates[3], location_coordinates[0]:location_coordinates[2]] binary_img = thresholding(bet_img, 105, 255) contours, _ = cv2.findContours(binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) bounding_boxes = convert_contours_to_bboxes(contours, 3, 1) bounding_boxes = sort_bboxes(bounding_boxes, method='left-to-right') number = '' for bbox in bounding_boxes: number_img = bet_img[bbox[1]:bbox[3], bbox[0]:bbox[2]] symbol = table_part_recognition(number_img, self.cfg['paths']['pot_numbers'], cv2.IMREAD_GRAYSCALE) number += symbol updated_players_info[players_info[i]] = number else: updated_players_info[i] = players_info[i] return updated_players_info ================================================ FILE: scripts/table_recognition.py ================================================ from abc import ABC, abstractmethod class PokerTableRecognizer(ABC): @abstractmethod def detect_hero_step(self): """ Based on the area under hero's cards, we determine whether hero should make a move """ pass @abstractmethod def detect_hero_cards(self): pass @abstractmethod def detect_table_cards(self): pass @abstractmethod def find_total_pot(self): pass @abstractmethod def get_dealer_button_position(self): """ determine who is closer to the dealer button """ pass @abstractmethod def get_empty_seats(self, players_info): """ find players whose places are currently vacant """ pass @abstractmethod def get_so_players(self, players_info): """ find players who are not currently in the game """ pass @abstractmethod def find_players_bet(self, players_info): pass ================================================ FILE: scripts/utils.py ================================================ import cv2 import numpy as np import yaml import os from time import sleep from math import sqrt def read_config_file(filename='config.yaml'): """ Parameters: filename (str): config file name Returns: loaded_data (dict) """ with open(filename, 'r') as stream: loaded_data = yaml.safe_load(stream) return loaded_data def sort_bboxes(bounding_boxes, method): """ Parameters: bounding_boxes(list of lists of int): bounding_boxes in [x_0, y_0, x_1, y_1] format method(int): the method of sorting bounding boxes. It can be left-to-right, bottom-to-top or top-to-bottom Returns: bounding_boxes (list of tuple of int): sorted bounding boxes. Each bounding box presented in [x_0, y_0, x_1, y_1] format """ methods = ['left-to-right', 'bottom-to-top', 'top-to-bottom'] if method not in methods: raise ValueError("Invalid method. Expected one of: %s" % methods) else: if method == 'left-to-right': bounding_boxes.sort(key=lambda tup: tup[0]) elif method == 'bottom-to-top': bounding_boxes.sort(key=lambda tup: tup[1], reverse=True) elif method == 'top-to-bottom': bounding_boxes.sort(key=lambda tup: tup[1], reverse=False) return bounding_boxes def mse(img, benchmark_img): """ the 'Mean Squared Error' between two images that is the sum of the squared difference between the two images. NOTE: the two images must have the same dimension. Parameters: img(numpy.ndarray): image of a part of the table benchmark_img(numpy.ndarray): benchmark image. This image read from the folder. Returns: err (float): the error between two images """ err = np.sum((img.astype("float") - benchmark_img.astype("float")) ** 2) err /= float(img.shape[0] * img.shape[1]) return err def image_comparison(img, benchmark_img, color_of_img): """ Parameters: img(numpy.ndarray): image of a part of the table benchmark_img(str): path to benchmark image color_of_img(int): set in which format to read the image. It can be cv2.IMREAD_COLOR or cv2.IMREAD_GRAYSCALE Returns: err (float): the error between two images """ colors = [cv2.IMREAD_COLOR, cv2.IMREAD_GRAYSCALE] if color_of_img not in colors: raise ValueError("Invalid method. Expected one of: %s" % colors) benchmark_img = cv2.imread(benchmark_img, color_of_img) res_img = cv2.resize(img, (benchmark_img.shape[1], benchmark_img.shape[0])) if color_of_img == cv2.IMREAD_GRAYSCALE: res_img = cv2.cvtColor(res_img, cv2.COLOR_BGR2GRAY) err = mse(res_img, benchmark_img) return err def thresholding(img, value_1, value_2): """ Parameters: img(numpy.ndarray): image of a part of the table value_1(int): threshold value value_2(int): the maximum value that is assigned to pixel values that exceed the threshold value Returns: binary_img(numpy.ndarray) """ img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) _, binary_img = cv2.threshold(img, value_1, value_2, cv2.THRESH_BINARY) return binary_img def table_part_recognition(img, directory, color_of_img): """ Parameters: img(numpy.ndarray): image of a part of the table directory(str): path to the template images color_of_img(int): set in which format to read the image. It can be cv2.IMREAD_COLOR or cv2.IMREAD_GRAYSCALE Returns: table_part(str): recognized suit, value (J, K etc.), pot etc. """ err_dict = {} for full_image_name in os.listdir(directory): image_name = full_image_name.split('.')[0] err = image_comparison(img, directory + full_image_name, color_of_img) err_dict[image_name] = err table_part = min(err_dict, key=err_dict.get) return table_part def convert_contours_to_bboxes(contours, min_height, min_width): """ convert contours to bboxes, also remove all small bounding boxes Parameters: contours (tuple): each individual contour is a numpy array of (x, y) coordinates of boundary points of the object contours(list of tuples of int): bounding boxes suits and values (J, K etc.) in [x, y, w, h] format Returns: cards_bboxes(list of lists of int): bounding boxes suits and values (J, K etc.) in [x_0, y_0, x_1, y_1] format """ bboxes = [cv2.boundingRect(contour) for contour in contours] cards_bboxes = [] for i in range(0, len(bboxes)): x, y, w, h = bboxes[i][0], bboxes[i][1], \ bboxes[i][2], bboxes[i][3] if h >= min_height and w >= min_width: contour_coordinates = [x - 1, y - 1, x + w + 1, y + h + 1] cards_bboxes.append(contour_coordinates) return cards_bboxes def card_separator(bboxes, separators): """ determine which bounding box belongs to which card Parameters: bboxes(list of lists of int): bounding boxes suits and values (J, K etc.) separators(list of int): contains values where the card ends Returns: sorted_dct(dict) key - card number, value - bounding boxes """ dct = {} for bbox in bboxes: for separator in separators: if bbox[2] < separator: dct[separators.index(separator)] = dct.get(separators.index(separator), []) + [bbox] break sorted_dct = {key: value for key, value in sorted(dct.items(), key=lambda item: int(item[0]))} return sorted_dct def find_by_template(img, path_to_image): """ Object detection using a "template". Parameters: img(numpy.ndarray): image of a part of the table/of the entire table path_to_image(str): the path to a template image Returns: max_val(float): largest value with the most likely match max_loc(tuple of int): location with the largest value. Location is presented in (x, y) format """ template_img_gray = cv2.imread(path_to_image, 0) img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) result = cv2.matchTemplate(img_gray, template_img_gray, cv2.TM_CCOEFF_NORMED) (min_val, max_val, min_loc, max_loc) = cv2.minMaxLoc(result) return max_val, max_loc def load_images(directory): """ Parameters: directory(str): path to specific directory Returns: images(list of numpy.ndarray): images of a part of the table file_names(list of str): the name of the files in the folder """ images = [cv2.imread(directory + file) for file in sorted(os.listdir(directory))] file_names = [file for file in sorted(os.listdir(directory))] return images, file_names def find_closer_point(players_coordinates, button_coordinates): """ find the distance between two points and find the minimum distance Parameters: players_coordinates(dict): key - player number, value - player coordinates button_coordinates(tuple): Coordinates are presented in (x, y) format Returns: player_with_button(int): the number of the player who is closest to the buttonthe number of the player who is closest to the button """ distance_dict = {} for player, player_coordinates in players_coordinates.items(): distance = sqrt((player_coordinates[0] - button_coordinates[0]) ** 2 + (player_coordinates[1] - button_coordinates[1]) ** 2) distance_dict[player] = distance player_with_button = min(distance_dict, key=distance_dict.get) return player_with_button def remove_cards(hero_cards, table_cards): """ Parameters: hero_cards(list of str): cards that belong to the hero table_cards(list of str): cards that are on the table Returns: all_cards(list of str): all other cards, not including cards that are on the table and hero cards """ all_cards = ['2c', '2d', '2h', '2s', '3c', '3d', '3h', '3s', '4c', '4d', '4h', '4s', '5c', '5d', '5h', '5s', '6c', '6d', '6h', '6s', '7c', '7d', '7h', '7s', '8c', '8d', '8h', '8s', '9c', '9d', '9h', '9s', 'Tc', 'Td', 'Th', 'Ts', 'Jc', 'Jd', 'Jh', 'Js', 'Qc', 'Qd', 'Qh', 'Qs', 'Kc', 'Kd', 'Kh', 'Ks', 'Ac', 'Ad', 'Ah', 'As'] for card in hero_cards+table_cards: all_cards.remove(card) return all_cards def set_window_size(): """ set the application window in the right place and with the right size """ sleep(3) cmd = 'wmctrl -r :ACTIVE: -e 0,0,0,1100,900' os.system(cmd) def data_concatenate(hero_hand, table_cards, total_pot, equity, players_info): """ information from all lists, dictionaries are added to one common line """ text_players_info = '' for key, value in players_info.items(): value = 'no bet found' if value == '' else value text_players_info += str(key) + ':' + str(value) + '\n' table_cards = ['no cards on the table'] if table_cards == [] else table_cards text = 'Hero hand: ' + ' '.join(hero_hand) + '\n' + 'Board: ' + ' '.join(table_cards) + '\n' + \ 'Pot: ' + total_pot + '\n' + 'Equity: ' + str(equity) + '%' + '\n' + \ '------------------------------' + '\n' + text_players_info return text ================================================ FILE: unittests/TestTableRecognition.py ================================================ import unittest from scripts.pokerstars_recognition import PokerStarsTableRecognizer from scripts.utils import read_config_file, load_images cfg = read_config_file('../scripts/config.yaml') test_cfg = read_config_file('test_config.yaml') class TestTableRecognition(unittest.TestCase): def test_hero_step(self): images, file_names = load_images(test_cfg['paths']['hero_step']) for image, filename in zip(images, file_names): with self.subTest("TestHeroStep Incorrect detection in the image", filename=filename): recognizer = PokerStarsTableRecognizer(image, cfg) self.assertEqual(recognizer.detect_hero_step(), test_cfg['hero_step'][filename]) def test_hero_cards(self): images, file_names = load_images(test_cfg['paths']['hero_cards']) for image, filename in zip(images, file_names): with self.subTest("TestHeroCards Incorrect detection in the image", filename=filename): recognizer = PokerStarsTableRecognizer(image, cfg) self.assertEqual(recognizer.detect_hero_cards(), test_cfg['hero_cards'][filename]) def test_table_cards(self): images, file_names = load_images(test_cfg['paths']['table_cards']) for image, filename in zip(images, file_names): with self.subTest("TestTableCards Incorrect detection in the image", filename=filename): recognizer = PokerStarsTableRecognizer(image, cfg) self.assertEqual(recognizer.detect_table_cards(), test_cfg['table_cards'][filename]) def test_total_pot(self): images, file_names = load_images(test_cfg['paths']['total_pot']) for image, filename in zip(images, file_names): with self.subTest("TestTotalPot Incorrect detection in the image", filename=filename): recognizer = PokerStarsTableRecognizer(image, cfg) self.assertEqual(recognizer.find_total_pot(), test_cfg['total_pot'][filename]) def test_dealer_button_position(self): images, file_names = load_images(test_cfg['paths']['dealer_button_position']) for image, filename in zip(images, file_names): with self.subTest("TestDealerButton Incorrect detection in the image", filename=filename): recognizer = PokerStarsTableRecognizer(image, cfg) self.assertEqual(recognizer.get_dealer_button_position(), test_cfg['dealer_button_position'][filename]) def test_player_position(self): images, file_names = load_images(test_cfg['paths']['player_position']) for image, filename in zip(images, file_names): with self.subTest("TestPlayerPosition Incorrect detection in the image", filename=filename): recognizer = PokerStarsTableRecognizer(image, cfg) players_info = recognizer.get_dealer_button_position() players_info = recognizer.get_empty_seats(players_info) players_info = recognizer.get_so_players(players_info) self.assertEqual(recognizer.assign_positions(players_info), test_cfg['player_position'][filename]) def test_player_bet(self): images, file_names = load_images(test_cfg['paths']['player_bet']) for image, filename in zip(images, file_names): with self.subTest("TestPlayerBet Incorrect detection in the image", filename=filename): recognizer = PokerStarsTableRecognizer(image, cfg) players_info = recognizer.get_dealer_button_position() players_info = recognizer.get_empty_seats(players_info) players_info = recognizer.get_so_players(players_info) players_info = recognizer.assign_positions(players_info) self.assertEqual(recognizer.find_players_bet(players_info), test_cfg['player_bet'][filename]) if __name__ == '__main__': unittest.main() ================================================ FILE: unittests/test_config.yaml ================================================ paths: hero_step: 'images_for_test/hero_step/' hero_cards: 'images_for_test/hero_cards/' table_cards: 'images_for_test/table_cards/' total_pot: 'images_for_test/total_pot/' dealer_button_position: 'images_for_test/dealer_button_position/' player_position: 'images_for_test/player_position/' player_bet: 'images_for_test/player_bet/' hero_step: test_image_1.png: True test_image_2.png: False test_image_3.png: True test_image_4.png: False test_image_5.png: False test_image_6.png: False test_image_7.png: False test_image_8.png: True test_image_9.png: True test_image_10.png: False test_image_11.png: False test_image_12.png: True test_image_13.png: True test_image_14.png: False test_image_15.png: False test_image_16.png: False test_image_17.png: True test_image_18.png: False test_image_19.png: True hero_cards: test_image_1.png: ['Qs', 'As'] test_image_2.png: [] test_image_3.png: ['7c','Kh'] test_image_4.png: [] test_image_5.png: ['2h','Ks'] test_image_6.png: ['2h','Ks'] test_image_7.png: ['Ah', '4h'] test_image_8.png: ['Ah', '4h'] test_image_9.png: ['2d', '6s'] test_image_10.png: ['Qd', '3s'] test_image_11.png: ['Kc', 'Ad'] test_image_12.png: ['As', 'Td'] test_image_13.png: ['5s', '9c'] test_image_14.png: ['Kd', 'Kh'] test_image_15.png: ['Ad', 'Td'] test_image_16.png: ['2d', 'Tc'] table_cards: test_image_1.png: ['9h','5d','6d','9c'] test_image_2.png: ['6c','4s','Ts','2s','8s'] test_image_3.png: ['7h','4c','5h','Ac','Td'] test_image_4.png: ['2d','3s','7h','Qh','Ah'] test_image_5.png: ['Qc','Kd','2s'] test_image_6.png: ['Th','Jc','Qc'] test_image_7.png: ['Th','Jc','Qc','Jh'] test_image_8.png: ['2s','Qh','Qs'] test_image_9.png: ['2s','Qh','Qs', 'Ah'] test_image_11.png: ['2s','Qh','Qs', 'Ah','Ac'] test_image_12.png: ['Tc','9h','8d'] test_image_13.png: ['5c','Qs','3d','2s'] test_image_14.png: ['5c','Qs','3d','2s', 'Ah'] test_image_15.png: ['Ad','Tc','4s','7h','3h'] test_image_16.png: ['4c','7d','9d'] test_image_17.png: ['9s','6c','7s','Jh'] test_image_18.png: ['6d','Tc','5s'] test_image_19.png: [] test_image_20.png: ['3h','Qc','7c'] total_pot: test_image_1.png: '1,550' test_image_2.png: '150' test_image_3.png: '2,268' test_image_4.png: '250' test_image_5.png: '861' test_image_7.png: '3,496' test_image_8.png: '17,450' test_image_9.png: '350' test_image_10.png: '2,354' test_image_11.png: '400' test_image_12.png: '750' test_image_13.png: '550' test_image_14.png: '38,169' test_image_15.png: '283' test_image_16.png: '478' dealer_button_position: test_image_1.png: {1: '', 2: 'dealer_button', 3: '', 4: '', 5: '', 6: ''} test_image_2.png: {1: '', 2: '', 3: 'dealer_button', 4: '', 5: '', 6: ''} test_image_3.png: {1: '', 2: '', 3: '', 4: 'dealer_button', 5: '', 6: ''} test_image_4.png: {1: '', 2: '', 3: '', 4: '', 5: '', 6: 'dealer_button'} test_image_5.png: {1: '', 2: '', 3: '', 4: 'dealer_button', 5: '', 6: ''} test_image_6.png: {1: 'dealer_button', 2: '', 3: '', 4: '', 5: '', 6: ''} test_image_7.png: {1: 'dealer_button', 2: '', 3: '', 4: '', 5: '', 6: ''} test_image_8.png: {1: '', 2: '', 3: '', 4: '', 5: 'dealer_button', 6: ''} test_image_9.png: {1: '', 2: '', 3: 'dealer_button', 4: '', 5: '', 6: ''} test_image_10.png: {1: '', 2: '', 3: '', 4: '', 5: '', 6: 'dealer_button'} test_image_11.png: {1: '', 2: '', 3: '', 4: 'dealer_button', 5: '', 6: ''} player_position: test_image_1.png: {1: 'CO', 2: 'BTN', 3: 'SB', 4: 'BB', 5: '-so-', 6: 'MP'} test_image_2.png: {1: 'MP', 2: 'CO', 3: 'BTN', 4: 'SB', 5: '-so-', 6: 'BB'} test_image_3.png: {1: 'BB', 2: '-so-', 3: 'CO', 4: 'BTN', 5: '-so-', 6: 'SB'} test_image_4.png: {1: 'SB', 2: 'BB', 3: 'MP', 4: 'CO', 5: '-so-', 6: 'BTN'} test_image_5.png: {1: 'BB', 2: 'CO', 3: '-so-', 4: '-so-', 5: 'BTN', 6: 'SB'} test_image_6.png: {1: 'BB', 2: 'MP', 3: 'CO', 4: 'BTN', 5: '-so-', 6: 'SB'} test_image_7.png: {1: 'BB', 2: '-so-', 3: '-so-', 4: '-', 5: '-', 6: 'SB'} test_image_8.png: {1: 'BB', 2: '-', 3: 'CO', 4: 'BTN', 5: '-', 6: 'SB'} test_image_9.png: {1: 'BTN', 2: 'SB', 3: 'BB', 4: 'UTG', 5: 'MP', 6: 'CO'} test_image_10.png: {1: 'BB', 2: '-', 3: 'CO', 4: 'BTN', 5: '-', 6: 'SB'} player_bet: test_image_1.png: {'Hero': 'CO', 'BTN': '', 'SB': '700', 'BB': '', 5: '-so-', 'MP': ''} test_image_2.png: {'Hero': 'SB', BB: '8,000', MP: '', CO: '', 5: '-so-', BTN: ''} test_image_3.png: {'Hero': 'BB', 2: '-so-', CO: '', BTN: '', 5: '-so-', SB: '50'} test_image_4.png: {'Hero': 'CO', BTN: '1,748', SB: '1,748', BB: '', 5: '-so-', MP: ''} test_image_5.png: {'Hero': 'SB', 2: '-', 'BB': '100', 'CO': '', 5: '-', BTN: '100'} test_image_6.png: {'Hero': 'BTN', 'SB': '50', 'BB': '100', 'MP': '100', 5: '-so-', CO: '100'} test_image_7.png: {'Hero': 'SB', 'BB': '', 3: '-so-', 4: '-so-', 'CO': '', 'BTN': ''} test_image_8.png: {'Hero': 'BB', 2: '-', 'CO': '', 'BTN': '100', 5: '-', 'SB': ''} test_image_9.png: {'Hero': 'BTN', 'SB': '', 'BB': '50', 'UTG': '100', 'MP': '200', 'CO': '200'} test_image_10.png: {'Hero': 'BB', 'CO': '2,362', 3: '-', 4: '-', 'BTN': '', 'SB': ''}