|
import base64 |
|
import datetime |
|
import gradio as gr |
|
import pandas as pd |
|
import pytz |
|
import plotly.graph_objects as go |
|
import matplotlib.pyplot as plt |
|
from PIL import Image |
|
from io import BytesIO |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
df = pd.read_csv("herbologist_almanac_checklist_data.csv") |
|
TASKS = [ |
|
"task1", |
|
"task2", |
|
"task3", |
|
"task4", |
|
"color1", |
|
"color2", |
|
"color3", |
|
"color4", |
|
"color5", |
|
"color6", |
|
] |
|
PLANTS = list(df["plant"].unique()) |
|
|
|
|
|
def bin_ls2base64(ls): |
|
|
|
binary_str = "".join(str(n) for n in ls) |
|
|
|
|
|
byte_str = int(binary_str, 2).to_bytes((len(binary_str) + 7) // 8, byteorder="big") |
|
base64_str = base64.b64encode(byte_str).decode("utf-8") |
|
return base64_str |
|
|
|
|
|
def base64_to_binary(base64_str): |
|
if isinstance(base64_str, str): |
|
|
|
byte_str = base64.b64decode(base64_str) |
|
binary_str = bin(int.from_bytes(byte_str, byteorder="big"))[2:].zfill( |
|
len(TASKS) * len(PLANTS) |
|
) |
|
|
|
return binary_str |
|
|
|
else: |
|
raise TypeError( |
|
"Invalid input type. Expected str, got {}".format(type(base64_str)) |
|
) |
|
|
|
|
|
def parse_token(token): |
|
|
|
if not token: |
|
token = "\x00" |
|
|
|
if len(token) > 0: |
|
payload = base64_to_binary(token) |
|
almanac_data: list = [int(n) for n in payload] |
|
|
|
parsed_dict = {} |
|
for _, row in df.iterrows(): |
|
parsed_dict[row["plant"]] = [almanac_data.pop() for _ in range(len(TASKS))] |
|
return parsed_dict |
|
else: |
|
return parse_token("\x00") |
|
|
|
|
|
|
|
def process_data(*args): |
|
plot_library = args[-1] |
|
almanac_dict = dict(zip(PLANTS, args[:-1])) |
|
|
|
almanac_df = df.filter(items=["plant", "name"] + TASKS) |
|
almanac_bin_ls = [] |
|
for pl in PLANTS: |
|
plant_tasks = almanac_dict[pl] |
|
plant_tasks_done = [0 for _ in range(len(TASKS))] |
|
|
|
for n, i in enumerate(plant_tasks): |
|
plant_tasks_done[n] = 1 |
|
almanac_df.loc[almanac_df["plant"] == pl, TASKS[i]] = "✔" |
|
|
|
almanac_bin_ls += plant_tasks_done |
|
|
|
almanac_reverse_64 = bin_ls2base64(reversed(almanac_bin_ls)) |
|
|
|
|
|
return ( |
|
( |
|
generate_img_by_plotly(almanac_df.drop(columns=df.columns[0])) |
|
if plot_library == "plotly" |
|
else generate_img_by_matplotlib(almanac_df.drop(columns=df.columns[0])) |
|
), |
|
almanac_reverse_64, |
|
) |
|
|
|
|
|
def show_checkbox_groups(token): |
|
checklist_inputs = [] |
|
parsed_dict = parse_token(token) |
|
for index, row in df.iterrows(): |
|
tasks = [ |
|
row[col][0].upper() + row[col][1:] |
|
for col in TASKS |
|
if df.notnull().at[index, col] |
|
] |
|
with gr.Row(): |
|
checkbox = gr.CheckboxGroup( |
|
tasks, |
|
label=f"{row['name']}", |
|
value=[ |
|
tasks[i] |
|
for i, v in enumerate(parsed_dict[row["plant"]]) |
|
if v == 1 and df.notnull().at[index, TASKS[i]] |
|
], |
|
type="index", |
|
) |
|
checklist_inputs.append(checkbox) |
|
|
|
|
|
return checklist_inputs |
|
|
|
|
|
"""使用matplotlib生成图片""" |
|
|
|
|
|
def wrap_text(text, max_width=20): |
|
"""Manually wrap text based on max_width (character count)""" |
|
wrapped_lines = [] |
|
words = text.split(" ") |
|
line = "" |
|
|
|
for word in words: |
|
if len(line) + len(word) + 1 <= max_width: |
|
line += word + " " |
|
else: |
|
wrapped_lines.append(line.strip()) |
|
line = word + " " |
|
|
|
wrapped_lines.append(line.strip()) |
|
return "\n".join(wrapped_lines) |
|
|
|
|
|
def generate_img_by_matplotlib(df): |
|
fig, ax = plt.subplots( |
|
figsize=(12, 16) |
|
) |
|
ax.axis("off") |
|
|
|
|
|
table = ax.table( |
|
cellText=df.values, |
|
colLabels=df.columns, |
|
loc="center", |
|
colColours=["#f2f2f2"] * len(df.columns), |
|
colWidths=[w / len(df.columns) for w in [1] + [2] * 4 + [1] * 6], |
|
) |
|
|
|
table.auto_set_font_size(False) |
|
table.set_fontsize(10) |
|
|
|
|
|
for (row, col), cell in table.get_celld().items(): |
|
|
|
if cell.get_text().get_text() == "nan": |
|
cell.set_text_props(text="", ha="center") |
|
if row == 0: |
|
cell.set_text_props( |
|
weight="bold", ha="center" |
|
) |
|
if col == 0: |
|
text = cell.get_text().get_text() |
|
wrapped_text = wrap_text(text, 9) |
|
cell.set_text_props(text=wrapped_text, ha="center", linespacing=1) |
|
else: |
|
text = cell.get_text().get_text() |
|
wrapped_text = wrap_text(text) |
|
|
|
cell.set_text_props( |
|
text=wrapped_text, |
|
ha="center", |
|
fontsize=10, |
|
linespacing=1, |
|
) |
|
|
|
|
|
table.scale(1, 4) |
|
|
|
|
|
plt.tight_layout(pad=0.5) |
|
|
|
|
|
buffer = BytesIO() |
|
fig.savefig( |
|
buffer, format="png", pad_inches=0.2 |
|
) |
|
plt.close(fig) |
|
buffer.seek(0) |
|
image = Image.open(buffer) |
|
|
|
|
|
return image |
|
|
|
|
|
"""使用plotly生成图片""" |
|
|
|
|
|
def color_mapping(color): |
|
color_hex = { |
|
"turquoise": "#28b6aa", |
|
"chartreuse": "#dcde82", |
|
"red": "#981c05", |
|
"yellow": "#f4ca3a", |
|
"pink": "#fd8d9b", |
|
"blue": "#4194bd", |
|
"white": "#ffffff", |
|
"black": "#b7b7b7", |
|
"orange": "#d97413", |
|
"purple": "#8a659a", |
|
"viridian": "#a1c42a", |
|
} |
|
|
|
|
|
return color_hex.get(color, "white") |
|
|
|
|
|
def styled_header(header): |
|
return dict( |
|
values=[[f"<b>{attr.upper()}</b>"] for attr in header], |
|
line_color="darkslategray", |
|
fill_color="royalblue", |
|
align=["center"], |
|
font=dict(color="white", size=10), |
|
|
|
) |
|
|
|
|
|
def styled_cells(cells): |
|
return dict( |
|
values=cells, |
|
line_color="darkslategray", |
|
fill_color=["lavender", "white", "white", "white", "white"] |
|
+ [[color_mapping(str(el)) for el in col] for col in cells[5:]], |
|
align="center", |
|
font_size=10, |
|
height=30, |
|
) |
|
|
|
|
|
def handle_element(el): |
|
emoji_mapping = { |
|
"harvest": "🌱", |
|
"light": "🌞", |
|
"moisture": "💧", |
|
"mood": "💗", |
|
"sell": "💲", |
|
"collect": "🖐️", |
|
"hygiene": "🧽", |
|
"pest": "🐛", |
|
"overgrowth": "🌿", |
|
"show": "👩🏻🌾", |
|
} |
|
if isinstance(el, str): |
|
if el == "✔": |
|
return "✅" |
|
for cond in emoji_mapping.keys(): |
|
if cond in el.lower(): |
|
new_text = el + emoji_mapping[cond] |
|
return new_text |
|
|
|
return el |
|
|
|
|
|
def generate_img_by_plotly(df): |
|
df.fillna("", inplace=True) |
|
|
|
cells_values = [df[col].to_list() for col in df.columns] |
|
header_values = list(df.columns) |
|
|
|
|
|
cells_values = [[handle_element(el) for el in col] for col in cells_values] |
|
|
|
|
|
fig = go.Figure( |
|
data=[ |
|
go.Table( |
|
header=styled_header(header_values), |
|
cells=styled_cells(cells_values), |
|
columnwidth=[80, 160, 160, 160, 160, 80, 80, 80, 80, 80, 80], |
|
) |
|
] |
|
) |
|
fig.add_annotation( |
|
text=f'Herbology Almanac Checklist Generated on(GMT): {datetime.datetime.now(tz=pytz.utc).strftime("%Y-%m-%d %H:%M:%S")}', |
|
xref="paper", |
|
yref="paper", |
|
x=0.5, |
|
y=0, |
|
showarrow=False, |
|
) |
|
|
|
fig.update_layout( |
|
width=1200, |
|
height=1200, |
|
margin=dict(l=20, r=20, t=20, b=20), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
) |
|
buffer = BytesIO() |
|
fig.write_image(buffer, format="png") |
|
buffer.seek(0) |
|
image = Image.open(buffer) |
|
|
|
|
|
return image |
|
|
|
|
|
with gr.Blocks() as app: |
|
gr.Markdown( |
|
""" |
|
<center><font size=8>👩🏻🌾Herbology Almanac Checklist Generator📝</font></center> |
|
This is a simple web app that generates an almanac checklist for your plants. |
|
""" |
|
) |
|
|
|
gr.Markdown( |
|
""" |
|
# RECOVERY TOKEN |
|
""" |
|
) |
|
recovery_token = gr.Textbox( |
|
value="", |
|
label="Recovery Token", |
|
info="Save this token or paste your saved one here", |
|
placeholder="Keep this token to restore your previous input".upper(), |
|
interactive=True, |
|
) |
|
|
|
gr.Markdown( |
|
""" |
|
# YOUR RESEARCH TASKS |
|
""" |
|
) |
|
checklist_inputs = show_checkbox_groups(recovery_token.value) |
|
|
|
gr.Markdown( |
|
""" |
|
# IMAGE STYLE |
|
""" |
|
) |
|
plot_library = gr.Radio( |
|
["plotly", "matplotlib"], |
|
label="Plot Library", |
|
value="plotly", |
|
info="Choose your plot library", |
|
) |
|
|
|
submit_button = gr.Button("Generate Image and Token") |
|
|
|
|
|
|
|
generated_img = gr.Image(label="Generated Image", format="png", type="pil") |
|
|
|
logs = gr.Markdown( |
|
""" |
|
# CHANGELOG |
|
- 2024/08/31: Initial release |
|
- 2024/09/03: Fix a mistake in the tasks of mimbulus |
|
- 2024/09/04: Correct Radiant count for water hyacinth |
|
- 2024/09/05: Support image generated by plotly |
|
- 2024/09/13: Update Water Lily tasks and color options |
|
""" |
|
) |
|
|
|
submit_button.click( |
|
process_data, |
|
inputs=checklist_inputs + [plot_library], |
|
outputs=[generated_img, recovery_token], |
|
) |
|
|
|
recovery_token.change( |
|
show_checkbox_groups, |
|
inputs=[recovery_token], |
|
outputs=checklist_inputs, |
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.queue() |
|
app.launch() |
|
|