Spaces:
Running
Running
import gradio as gr | |
import pandas as pd | |
import subprocess | |
import os | |
from tempfile import mkdtemp | |
from timeit import default_timer as timer | |
from hand_record import create_hand_record_pdf | |
from pbn_util import create_pbn_file | |
from bridge_util import validate_dataframe, df_info, roll_direction | |
# Download model and libraries from repo | |
try: | |
token = os.environ.get("model_token") | |
if token: | |
subprocess.run(["git", "clone", f"https://oauth2:{token}@huggingface.co/vincentlui/bridge_hand_detect"]) | |
subprocess.run(["git", "pull"]) | |
except: | |
print('Fail to download code') | |
try: | |
from bridge_hand_detect2.predict import CardDetectionModel | |
except Exception as e: | |
print(e) | |
from bridge_hand_detect.predict import CardDetectionModel | |
custom_css = \ | |
""" | |
/* Hide sort buttons at gr.DataFrame */ | |
.sort-button { | |
display: none !important; | |
} | |
""" | |
INPUT_IMG_HEIGHT = 480 | |
OUTPUT_IMG_HEIGHT = 320 | |
css = ".output_img {display:block; margin-left: auto; margin-right: auto}" | |
model = CardDetectionModel() | |
def predict(image_path, top_hand_idx): | |
start = timer() | |
df = None | |
try: | |
hands, (width,height) = model(image_path, augment=True, top_hand_idx=top_hand_idx) | |
print(hands) | |
# Output dataframe | |
df = default_df.copy(deep=True)#pd.DataFrame(['♠', '♥', '♦', '♣'], columns=['']) | |
for hand in hands: | |
df[hand.direction] = [''.join(c) for c in hand.cards] | |
except Exception as e: | |
print(e) | |
raise gr.Error('Cannot process image') | |
end = timer() | |
print(f'Process time: {end - start:.02f} seconds') | |
return df | |
default_df = pd.DataFrame({'':['♠', '♥', '♦', '♣'], | |
'N': ['']*4, | |
'E': ['']*4, | |
'S': ['']*4, | |
'W': ['']*4}) | |
def save_file(df, cache_dir, files, board_no): | |
d = cache_dir | |
if cache_dir is None: | |
d = mkdtemp() | |
try: | |
validate_dataframe(df) | |
except Exception as e: | |
print(e) | |
gr.Warning(f'Fail to save pbn. Error in table entries. {e}') | |
return files, files, d | |
file_name = f'board_{board_no:03d}.pbn' | |
file_path = os.path.join(d,file_name) | |
create_pbn_file(df, file_path, board_no=board_no) | |
if not file_path in files: | |
files.append(file_path) | |
return files, files, d | |
def create_hand_record(files, event, site): | |
file_path = create_hand_record_pdf(files, event=event, site=site) | |
return file_path | |
def print_df_info(df): | |
return df_info(df) | |
def change_direction_in_df(df, top_direction_idx:int, current_top_idx:int): | |
roll_idx = current_top_idx - top_direction_idx | |
return roll_direction(df, roll_idx), top_direction_idx | |
with gr.Blocks(css=custom_css) as demo: | |
gr.Markdown( | |
""" | |
# Bridge Hand Scanner - Read all four hands from an image | |
This app scans an image taken from (e.g. your smartphone) and reads all 52 cards. The top hand is regarded as North. | |
The results can be exported as a PBN file, which can be imported to other bridge software such as double dummy solvers. | |
1. Upload an image showing all four hands fanned as shown in the example. | |
2. Click *Submit*. The scan result will be displayed in the table. | |
3. Verify the output and correct any missing or wrong card in the table. | |
4. Enter the information of the deal. | |
5. Click *Save* to generate a PBN file. | |
6. In the tab *Hand Record*, You can upload all the PBN files and create a hand record as a PDF file. | |
Tips: | |
- This AI reads the values at corners of the playing cards. Make sure they are visible and as large as possible. | |
- To get the best accuracy, place the cards following the layout in the examples. | |
Please send your comments to <[email protected]>. | |
""") | |
total = gr.State(0) | |
gradio_cache_dir = gr.State() | |
files = gr.State([]) | |
current_top_idx = gr.State(0) | |
with gr.Tab('Scan Image'): | |
with gr.Row(): | |
with gr.Column(): | |
a1 = gr.Image(type="filepath",sources=['upload'],interactive=True,height=INPUT_IMG_HEIGHT) | |
with gr.Row(): | |
a2 = gr.ClearButton() | |
a3 = gr.Button('Submit',variant="primary") | |
with gr.Accordion("Board Details",open=True): | |
with gr.Row(): | |
a_board_no = gr.Number(label="Board", value=1, minimum=1, maximum=999, interactive=True, min_width=80) | |
a_top = gr.Dropdown(['N','E','S','W'], label='Top', value='N', interactive=True, min_width=80, type='index') | |
a_deck = gr.Dropdown(['Standard (AKQJ)'], label='Deck', | |
value='Standard (AKQJ)', type='index', interactive=True, min_width=80, scale=2) | |
# with gr.Accordion("Contract Details",open=False) as a_c: | |
# with gr.Row(): | |
# with gr.Column(scale=3): | |
# with gr.Group(): | |
# a_level = gr.Radio(['1','2','3','4','5','6','7','AP'], label='Contract') | |
# a_trump = gr.Radio(['♠', '♥', '♦', '♣', 'NT'], show_label=False) | |
# a_dbl = gr.Radio(['X', 'XX'], show_label=False) | |
# a_declarer = gr.Radio(['N','E','S','W'], label='Declarer', min_width=80, scale=1) | |
a4 = gr.Examples('examples', a1) | |
with gr.Column(): | |
b1 = gr.Dataframe(value=default_df, datatype="str", row_count=(4,'fixed'), col_count=(5,'fixed'), | |
headers=['', 'N', 'E', 'S', 'W'], | |
interactive=True, column_widths=['8%', '23%','23%','23%','23%']) | |
b2 = gr.Button('Save') | |
b_info_panel = gr.TextArea(lines=5,show_label=False, interactive=False) | |
b3 = gr.File(interactive=False, file_count='multiple') | |
with gr.Tab('Hand Record'): | |
with gr.Row(): | |
with gr.Column(): | |
tab2_upload_file = gr.Files(interactive=True) | |
tab2_event = gr.Textbox(max_lines=1, placeholder='Event name', label='Event') | |
tab2_site = gr.Textbox(max_lines=1, placeholder='Site name', label='Site') | |
tab2_submit_button = gr.Button('Create Hand Record',variant="primary") | |
tab2_download_file = gr.File(interactive=False) | |
tab2_submit_button.click(create_hand_record, [tab2_upload_file, tab2_event, tab2_site], tab2_download_file) | |
a2.add([a1,b1]) | |
a3.click(predict, [a1, a_top], [b1]) | |
b2.click(save_file, [b1, gradio_cache_dir, files, a_board_no], [b3, tab2_upload_file, gradio_cache_dir]) | |
b1.change(print_df_info, b1, b_info_panel) | |
a_top.change(change_direction_in_df, [b1, a_top, current_top_idx], [b1, current_top_idx]) | |
demo.queue().launch() |