Full Code of wb-08/PokerVision for AI

main 7bef0d4568b1 cached
12 files
35.8 KB
10.2k tokens
46 symbols
1 requests
Download .txt
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': ''}





Download .txt
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
Download .txt
SYMBOL INDEX (46 symbols across 6 files)

FILE: scripts/equity.py
  function calc_equity (line 5) | def calc_equity(deck, hero_cards, table_cards, iters=100000):

FILE: scripts/info_box.py
  function update_label (line 13) | def update_label(text):

FILE: scripts/pokerstars_recognition.py
  class PokerStarsTableRecognizer (line 8) | class PokerStarsTableRecognizer(PokerTableRecognizer):
    method __init__ (line 10) | def __init__(self, img, cfg):
    method detect_hero_step (line 19) | def detect_hero_step(self):
    method detect_cards (line 35) | def detect_cards(self, separators, sort_bboxes_method, cards_coordinat...
    method detect_hero_cards (line 75) | def detect_hero_cards(self):
    method detect_table_cards (line 89) | def detect_table_cards(self):
    method find_total_pot (line 105) | def find_total_pot(self):
    method get_dealer_button_position (line 127) | def get_dealer_button_position(self):
    method get_missing_players (line 140) | def get_missing_players(self, players_info, path_to_template_img, flag):
    method get_empty_seats (line 161) | def get_empty_seats(self, players_info):
    method get_so_players (line 170) | def get_so_players(self, players_info):
    method assign_positions (line 179) | def assign_positions(self, players_info):
    method find_players_bet (line 200) | def find_players_bet(self, players_info):

FILE: scripts/table_recognition.py
  class PokerTableRecognizer (line 4) | class PokerTableRecognizer(ABC):
    method detect_hero_step (line 6) | def detect_hero_step(self):
    method detect_hero_cards (line 14) | def detect_hero_cards(self):
    method detect_table_cards (line 18) | def detect_table_cards(self):
    method find_total_pot (line 22) | def find_total_pot(self):
    method get_dealer_button_position (line 26) | def get_dealer_button_position(self):
    method get_empty_seats (line 33) | def get_empty_seats(self, players_info):
    method get_so_players (line 40) | def get_so_players(self, players_info):
    method find_players_bet (line 47) | def find_players_bet(self, players_info):

FILE: scripts/utils.py
  function read_config_file (line 9) | def read_config_file(filename='config.yaml'):
  function sort_bboxes (line 20) | def sort_bboxes(bounding_boxes, method):
  function mse (line 48) | def mse(img, benchmark_img):
  function image_comparison (line 65) | def image_comparison(img, benchmark_img, color_of_img):
  function thresholding (line 86) | def thresholding(img, value_1, value_2):
  function table_part_recognition (line 100) | def table_part_recognition(img, directory, color_of_img):
  function convert_contours_to_bboxes (line 119) | def convert_contours_to_bboxes(contours, min_height, min_width):
  function card_separator (line 142) | def card_separator(bboxes, separators):
  function find_by_template (line 161) | def find_by_template(img, path_to_image):
  function load_images (line 180) | def load_images(directory):
  function find_closer_point (line 193) | def find_closer_point(players_coordinates, button_coordinates):
  function remove_cards (line 211) | def remove_cards(hero_cards, table_cards):
  function set_window_size (line 228) | def set_window_size():
  function data_concatenate (line 237) | def data_concatenate(hero_hand, table_cards, total_pot, equity, players_...

FILE: unittests/TestTableRecognition.py
  class TestTableRecognition (line 9) | class TestTableRecognition(unittest.TestCase):
    method test_hero_step (line 11) | def test_hero_step(self):
    method test_hero_cards (line 18) | def test_hero_cards(self):
    method test_table_cards (line 25) | def test_table_cards(self):
    method test_total_pot (line 32) | def test_total_pot(self):
    method test_dealer_button_position (line 39) | def test_dealer_button_position(self):
    method test_player_position (line 46) | def test_player_position(self):
    method test_player_bet (line 56) | def test_player_bet(self):
Condensed preview — 12 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (38K chars).
[
  {
    "path": "readme.md",
    "chars": 545,
    "preview": "# PokerVision\nRecognition of all entities on the poker table( now only for Pokerstars) and added analytics on the basis "
  },
  {
    "path": "requirements.txt",
    "chars": 159,
    "preview": "eval7==0.1.9\nfuture==0.18.3\nmss==9.0.1\nnumpy==1.24.3\nopencv-contrib-python==4.5.5.62\nopencv-python==4.5.5.62\nPillow==9.5"
  },
  {
    "path": "scripts/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "scripts/config.yaml",
    "chars": 1958,
    "preview": "paths:\n  hero_cards_suits: '../images_templates/hero_cards_suits/'\n  hero_cards_numbers: '../images_templates/hero_cards"
  },
  {
    "path": "scripts/equity.py",
    "chars": 1212,
    "preview": "import numpy as np\nimport eval7\n\n\ndef calc_equity(deck, hero_cards, table_cards, iters=100000):\n    \"\"\"\n    Parameters:\n"
  },
  {
    "path": "scripts/grab_table.py",
    "chars": 1790,
    "preview": "from PIL import Image\nimport numpy as np\nimport cv2\nfrom mss import mss\nfrom pokerstars_recognition import PokerStarsTab"
  },
  {
    "path": "scripts/info_box.py",
    "chars": 436,
    "preview": "from tkinter import *\nfrom scripts.utils import read_config_file\n\ncfg = read_config_file()\nroot = Tk()\nroot.geometry('{0"
  },
  {
    "path": "scripts/pokerstars_recognition.py",
    "chars": 11090,
    "preview": "import cv2\nfrom scripts.table_recognition import PokerTableRecognizer\nfrom scripts.utils import sort_bboxes, thresholdin"
  },
  {
    "path": "scripts/table_recognition.py",
    "chars": 1013,
    "preview": "from abc import ABC, abstractmethod\n\n\nclass PokerTableRecognizer(ABC):\n    @abstractmethod\n    def detect_hero_step(self"
  },
  {
    "path": "scripts/utils.py",
    "chars": 9407,
    "preview": "import cv2\nimport numpy as np\nimport yaml\nimport os\nfrom time import sleep\nfrom math import sqrt\n\n\ndef read_config_file("
  },
  {
    "path": "unittests/TestTableRecognition.py",
    "chars": 3917,
    "preview": "import unittest\nfrom scripts.pokerstars_recognition import PokerStarsTableRecognizer\nfrom scripts.utils import read_conf"
  },
  {
    "path": "unittests/test_config.yaml",
    "chars": 5166,
    "preview": "paths:\n  hero_step: 'images_for_test/hero_step/'\n  hero_cards: 'images_for_test/hero_cards/'\n  table_cards: 'images_for_"
  }
]

About this extraction

This page contains the full source code of the wb-08/PokerVision GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 12 files (35.8 KB), approximately 10.2k tokens, and a symbol index with 46 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!