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

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': ''}
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
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.