常用的兩款AI視覺化互動應用比較:
Gradio
Gradio的優勢在於易用性,程式碼結構相比Streamlit簡單,只需簡單定義輸入和輸出介面即可快速構建簡單的互動頁面,更輕鬆部署模型。適合場景相對簡單,想要快速部署應用的開發者。便於分享:gradio可以在啟動應用時設定share=True引數建立外部分享連結,可以直接在微信中分享給使用者使用。
方便偵錯:gradio可以在jupyter中直接展示頁面,更加方便偵錯。
Streamlit
Streamlit的優勢在於可延伸性,相比Gradio複雜,完全熟練使用需要一定時間。可以使用Python編寫完整的包含前後端的互動式應用。適合場景相對複雜,想要構建豐富多樣互動頁面的開發者。
Gradio官網連結:https://gradio.app/
Python第三方庫Gradio快速上手,當前版本V3.27.0
pip install gradio
#為了更快安裝,可以使用清華映象源
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple gradio
安裝完直接在IDE上啟動快速,
import gradio as gr
#輸入文書處理程式
def greet(name):
return "Hello " + name + "!"
#介面建立函數
#fn設定處理常式,inputs設定輸入介面元件,outputs設定輸出介面元件
#fn,inputs,outputs都是必填函數
demo = gr.Interface(fn=greet, inputs="text", outputs="text")
demo.launch()
執行程式後,開啟 http://localhost:7860 即可看到網頁效果。左邊是文字輸入框,右邊是結果展示框。Clear按鈕用於重置網頁狀態,Submit按鈕用於執行處理程式,Flag按鈕用於儲存結果到本地。
#執行結果
Running on local URL: http://127.0.0.1:7860
To create a public link, set `share=True` in `launch()`.
開啟瀏覽器使用即可
在本地開發時,如果你想將程式碼作為Python指令碼執行,你可以使用Gradio CLI在過載模式下啟動應用程式,這將提供無縫和快速的開發。
gradio app.py
Note:你也可以做python app.py,但它不會提供自動重新載入機制。
Gradio 可以包裝幾乎任何 Python 函數為易於使用的使用者介面。從上面例子我們看到,簡單的基於文字的函數。但這個函數還可以處理很多型別。
Interface類通過以下三個引數進行初始化:
通過這三個引數,我們可以快速建立一個介面並行布他們。
最常用的基礎模組構成。
應用介面:gr.Interface(簡易場景), gr.Blocks(客製化化場景)
輸入輸出:gr.Image(影象), gr.Textbox(文字方塊), gr.DataFrame(資料框), gr.Dropdown(下拉選項), gr.Number(數位), gr.Markdown, gr.Files
控制元件:gr.Button(按鈕)
佈局元件:gr.Tab(分頁), gr.Row(行佈局), gr.Column(列布局)
import gradio as gr
def greet(name):
return "Hello " + name + "!"
demo = gr.Interface(
fn=greet,
# 自定義輸入框
# 具體設定方法檢視官方檔案
inputs=gr.Textbox(lines=3, placeholder="Name Here...",label="my input"),
outputs="text",
)
demo.launch()
Interface.launch()方法返回三個值
import gradio as gr
def greet(name):
return "Hello " + name + "!"
iface = gr.Interface(
fn=greet,
inputs=gr.inputs.Textbox(lines=2, placeholder="Name Here..."),
outputs="text",
)
if __name__ == "__main__":
app, local_url, share_url = iface.launch()
對於複雜程式,輸入列表中的每個元件按順序對應於函數的一個引數。輸出列表中的每個元件按順序排列對應於函數返回的一個值。
import gradio as gr
#該函數有3個輸入引數和2個輸出引數
def greet(name, is_morning, temperature):
salutation = "Good morning" if is_morning else "Good evening"
greeting = f"{salutation} {name}. It is {temperature} degrees today"
celsius = (temperature - 32) * 5 / 9
return greeting, round(celsius, 2)
demo = gr.Interface(
fn=greet,
#按照處理程式設定輸入元件
inputs=["text", "checkbox", gr.Slider(0, 100)],
#按照處理程式設定輸出元件
outputs=["text", "number"],
)
demo.launch()
inputs列表裡的每個欄位按順序對應函數的每個引數,outputs同理。
Gradio支援許多型別的元件,如image、dataframe、video。使用範例如下:
import numpy as np
import gradio as gr
def sepia(input_img):
#處理影象
sepia_filter = np.array([
[0.393, 0.769, 0.189],
[0.349, 0.686, 0.168],
[0.272, 0.534, 0.131]
])
sepia_img = input_img.dot(sepia_filter.T)
sepia_img /= sepia_img.max()
return sepia_img
#shape設定輸入影象大小
demo = gr.Interface(sepia, gr.Image(shape=(200, 200)), "image")
demo.launch()
當使用Image元件作為輸入時,函數將收到一個維度為(w,h,3)的numpy陣列,按照RGB的通道順序排列。要注意的是,我們的輸入影象元件帶有一個編輯按鈕,可以對影象進行裁剪和放大。以這種方式處理影象可以幫助揭示機器學習模型中的偏差或隱藏的缺陷。此外對於輸入元件有個shape引數,指的設定輸入影象大小。但是處理方式是保持長寬比的情況下,將影象最短邊縮放為指定長度,然後按照中心裁剪方式裁剪最長邊到指定長度。當影象不大的情況,一種更好的方式是不設定shape,這樣直接傳入原圖。輸入元件Image也可以設定輸入型別type,比如type=filepath設定傳入處理影象的路徑。具體可以檢視官方檔案,檔案寫的很清楚。
在Interface新增live=True引數,只要輸入發生變化,結果馬上發生改變。
import gradio as gr
def calculator(num1, operation, num2):
if operation == "add":
return num1 + num2
elif operation == "subtract":
return num1 - num2
elif operation == "multiply":
return num1 * num2
elif operation == "divide":
return num1 / num2
iface = gr.Interface(
calculator,
["number", gr.inputs.Radio(["add", "subtract", "multiply", "divide"]), "number"],
"number",
live=True,
)
iface.launch()
import gradio as gr
#一個簡單計算器,含範例說明
def calculator(num1, operation, num2):
if operation == "add":
return num1 + num2
elif operation == "subtract":
return num1 - num2
elif operation == "multiply":
return num1 * num2
elif operation == "divide":
if num2 == 0:
# 設定報錯彈窗
raise gr.Error("Cannot divide by zero!")
return num1 / num2
demo = gr.Interface(
calculator,
# 設定輸入
[
"number",
gr.Radio(["add", "subtract", "multiply", "divide"]),
"number"
],
# 設定輸出
"number",
# 設定輸入引數範例
examples=[
[5, "add", 3],
[4, "divide", 2],
[-4, "multiply", 2.5],
[0, "subtract", 1.2],
],
# 設定網頁標題
title="Toy Calculator",
# 左上角的描述文字
description="Here's a sample toy calculator. Enjoy!",
# 左下角的文字
article = "Check out the examples",
)
demo.launch()
全域性變數的好處就是在呼叫函數後仍然能夠儲存,例如在機器學習中通過全域性變數從外部載入一個大型模型,並在函數內部使用它,以便每次函數呼叫都不需要重新載入模型。下面就展示了全域性變數使用的好處。
import gradio as gr
scores = []
def track_score(score):
scores.append(score)
#返回分數top3
top_scores = sorted(scores, reverse=True)[:3]
return top_scores
demo = gr.Interface(
track_score,
gr.Number(label="Score"),
gr.JSON(label="Top Scores")
)
demo.launch()
Gradio支援的另一種資料永續性是對談狀態,資料在一個頁面對談中的多次提交中持久存在。然而,資料不會在你模型的不同使用者之間共用。對談狀態的典型例子就是聊天機器人,你想存取使用者之前提交的資訊,但你不能將聊天記錄儲存在一個全域性變數中,因為那樣的話,聊天記錄會在不同的使用者之間亂成一團。注意該狀態會在每個頁面內的提交中持續存在,但如果您在另一個分頁中載入該演示(或重新整理頁面),該演示將不會共用聊天曆史。
要在對談狀態下儲存資料,你需要做三件事。
import random
import gradio as gr
def chat(message, history):
history = history or []
message = message.lower()
if message.startswith("how many"):
response = random.randint(1, 10)
elif message.startswith("how"):
response = random.choice(["Great", "Good", "Okay", "Bad"])
elif message.startswith("where"):
response = random.choice(["Here", "There", "Somewhere"])
else:
response = "I don't know"
history.append((message, response))
return history, history
#設定一個對話窗
chatbot = gr.Chatbot().style(color_map=("green", "pink"))
demo = gr.Interface(
chat,
# 新增state元件
["text", "state"],
[chatbot, "state"],
# 設定沒有儲存資料的按鈕
allow_flagging="never",
)
demo.launch()
在Interface中設定live=True,則輸出會跟隨輸入實時變化。這個時候介面不會有submit按鈕,因為不需要手動提交輸入。
同1.2.4
在許多情形下,我們的輸入是實時視訊流或者音訊流,那麼意味這資料不停地傳送到後端,這是可以採用streaming模式處理資料。
import gradio as gr
import numpy as np
def flip(im):
return np.flipud(im)
demo = gr.Interface(
flip,
gr.Image(source="webcam", streaming=True),
"image",
live=True
)
demo.launch()
相比Interface,Blocks提供了一個低階別的API,用於設計具有更靈活佈局和資料流的網路應用。Blocks允許控制元件在頁面上出現的位置,處理複雜的資料流(例如,輸出可以作為其他函數的輸入),並根據使用者互動更新元件的屬性可見性。可以客製化更多元件,更多詳細客製化可檢視檔案
import gradio as gr
def greet(name):
return "Hello " + name + "!"
with gr.Blocks() as demo:
#設定輸入元件
name = gr.Textbox(label="Name")
# 設定輸出元件
output = gr.Textbox(label="Output Box")
#設定按鈕
greet_btn = gr.Button("Greet")
#設定按鈕點選事件
greet_btn.click(fn=greet, inputs=name, outputs=output)
demo.launch()
Blocks方式需要with語句新增元件,如果不設定佈局方式,那麼元件將按照建立的順序垂直出現在應用程式中,執行介面
import numpy as np
import gradio as gr
def flip_text(x):
return x[::-1]
def flip_image(x):
return np.fliplr(x)
with gr.Blocks() as demo:
#用markdown語法編輯輸出一段話
gr.Markdown("Flip text or image files using this demo.")
# 設定tab索引標籤
with gr.Tab("Flip Text"):
#Blocks特有元件,設定所有子元件按垂直排列
#垂直排列是預設情況,不加也沒關係
with gr.Column():
text_input = gr.Textbox()
text_output = gr.Textbox()
text_button = gr.Button("Flip")
with gr.Tab("Flip Image"):
#Blocks特有元件,設定所有子元件按水平排列
with gr.Row():
image_input = gr.Image()
image_output = gr.Image()
image_button = gr.Button("Flip")
#設定摺疊內容
with gr.Accordion("Open for More!"):
gr.Markdown("Look at me...")
text_button.click(flip_text, inputs=text_input, outputs=text_output)
image_button.click(flip_image, inputs=image_input, outputs=image_output)
demo.launch()
相信有小夥伴已經注意到,輸出框下有個Flag按鈕。當測試您的模型的使用者看到某個輸入導致輸出錯誤或意外的模型行為,他們可以標記這個輸入讓開發者知道。這個資料夾由Interface的flagging_dir引數指定,預設為’flagged’。將這些會導致錯誤的輸入儲存到一個csv檔案。如果Interface包含檔案資料,資料夾也會建立來儲存這些標記資料。
開啟log.csv展示如下:
在Gradio官方檔案,搜尋不同的元件加.style(如image.style),可以獲取該元件的樣式引數設定樣例。例如image元件的設定如下:
img = gr.Image("lion.jpg").style(height='24', rounded=False)
demo = gr.Interface(...).queue()
demo.launch()
#或
with gr.Blocks() as demo:
#...
demo.queue()
demo.launch()
在某些情況下,你可能想顯示一連串的輸出,而不是單一的輸出。例如,你可能有一個影象生成模型,如果你想顯示在每個步驟中生成的影象,從而得到最終的影象。在這種情況下,你可以向Gradio提供一個生成器函數,而不是一個常規函數。下面是一個生成器的例子,每隔1秒返回1張圖片。
import gradio as gr
import numpy as np
import time
#生成steps張圖片,每隔1秒鐘返回
def fake_diffusion(steps):
for _ in range(steps):
time.sleep(1)
image = np.random.randint(255, size=(300, 600, 3))
yield image
demo = gr.Interface(fake_diffusion,
#設定滑窗,最小值為1,最大值為10,初始值為3,每次改動增減1位
inputs=gr.Slider(1, 10, value=3, step=1),
outputs="image")
#生成器必須要queue函數
demo.queue()
demo.launch()
任何輸入的元件內容都是可編輯的,而輸出元件預設是不能編輯的。如果想要使得輸出元件內容可編輯,設定interactive=True即可。
import gradio as gr
def greet(name):
return "Hello " + name + "!"
with gr.Blocks() as demo:
name = gr.Textbox(label="Name")
# 不可互動
# output = gr.Textbox(label="Output Box")
# 可互動
output = gr.Textbox(label="Output", interactive=True)
greet_btn = gr.Button("Greet")
greet_btn.click(fn=greet, inputs=name, outputs=output)
demo.launch()
我們可以為不同的元件設定不同事件,如為輸入元件新增change事件。可以進一步檢視官方檔案,看看元件還有哪些事件。
import gradio as gr
def welcome(name):
return f"Welcome to Gradio, {name}!"
with gr.Blocks() as demo:
gr.Markdown(
"""
# Hello World!
Start typing below to see the output.
""")
inp = gr.Textbox(placeholder="What is your name?")
out = gr.Textbox()
#設定change事件
inp.change(fn = welcome, inputs = inp, outputs = out)
demo.launch()
如果想處理多個資料流,只要設定相應的輸入輸出元件即可。
import gradio as gr
def increase(num):
return num + 1
with gr.Blocks() as demo:
a = gr.Number(label="a")
b = gr.Number(label="b")
# 要想b>a,則使得b = a+1
atob = gr.Button("b > a")
atob.click(increase, a, b)
# 要想a>b,則使得a = b+1
btoa = gr.Button("a > b")
btoa.click(increase, b, a)
demo.launch()
import gradio as gr
with gr.Blocks() as demo:
food_box = gr.Number(value=10, label="Food Count")
status_box = gr.Textbox()
def eat(food):
if food > 0:
return food - 1, "full"
else:
return 0, "hungry"
gr.Button("EAT").click(
fn=eat,
inputs=food_box,
#根據返回值改變輸入元件和輸出元件
outputs=[food_box, status_box]
)
demo.launch()
事件監聽器函數的返回值通常是相應的輸出元件的更新值。有時我們也想更新元件的設定,比如說可見性。在這種情況下,我們可以通過返回update函數更新元件的設定。
import gradio as gr
def change_textbox(choice):
#根據不同輸入對輸出控制元件進行更新
if choice == "short":
return gr.update(lines=2, visible=True, value="Short story: ")
elif choice == "long":
return gr.update(lines=8, visible=True, value="Long story...")
else:
return gr.update(visible=False)
with gr.Blocks() as demo:
radio = gr.Radio(
["short", "long", "none"], label="Essay Length to Write?"
)
text = gr.Textbox(lines=2, interactive=True)
radio.change(fn=change_textbox, inputs=radio, outputs=text)
demo.launch()
Blocks應用的是html中的flexbox模型佈局,預設情況下元件垂直排列。
使用Row函數會將元件按照水平排列,但是在Row函數塊裡面的元件都會保持同等高度。
import gradio as gr
with gr.Blocks() as demo:
with gr.Row():
img1 = gr.Image()
text1 = gr.Text()
btn1 = gr.Button("button")
demo.launch()
元件通常是垂直排列,我們可以通過Row函數和Column函數生成不同複雜的佈局。
import gradio as gr
with gr.Blocks() as demo:
with gr.Row():
text1 = gr.Textbox(label="t1")
slider2 = gr.Textbox(label="s2")
drop3 = gr.Dropdown(["a", "b", "c"], label="d3")
with gr.Row():
# scale與相鄰列相比的相對寬度。例如,如果列A的比例為2,列B的比例為1,則A的寬度將是B的兩倍。
# min_width設定最小寬度,防止列太窄
with gr.Column(scale=2, min_width=600):
text1 = gr.Textbox(label="prompt 1")
text2 = gr.Textbox(label="prompt 2")
inbtw = gr.Button("Between")
text4 = gr.Textbox(label="prompt 1")
text5 = gr.Textbox(label="prompt 2")
with gr.Column(scale=1, min_width=600):
img1 = gr.Image("test.jpg")
btn = gr.Button("Go")
demo.launch()
如下所示,我們可以通過visible和update函數構建更為複雜的應用。
import gradio as gr
with gr.Blocks() as demo:
# 出錯提示框
error_box = gr.Textbox(label="Error", visible=False)
# 輸入框
name_box = gr.Textbox(label="Name")
age_box = gr.Number(label="Age")
symptoms_box = gr.CheckboxGroup(["Cough", "Fever", "Runny Nose"])
submit_btn = gr.Button("Submit")
# 輸出不可見
with gr.Column(visible=False) as output_col:
diagnosis_box = gr.Textbox(label="Diagnosis")
patient_summary_box = gr.Textbox(label="Patient Summary")
def submit(name, age, symptoms):
if len(name) == 0:
return {error_box: gr.update(value="Enter name", visible=True)}
if age < 0 or age > 200:
return {error_box: gr.update(value="Enter valid age", visible=True)}
return {
output_col: gr.update(visible=True),
diagnosis_box: "covid" if "Cough" in symptoms else "flu",
patient_summary_box: f"{name}, {age} y/o"
}
submit_btn.click(
submit,
[name_box, age_box, symptoms_box],
[error_box, diagnosis_box, patient_summary_box, output_col],
)
demo.launch()
在某些情況下,您可能希望在實際在UI中呈現元件之前定義元件。例如,您可能希望在相應的gr.Textbox輸入上方顯示使用gr.examples的範例部分。由於gr.Examples需要輸入元件物件作為引數,因此您需要先定義輸入元件,然後在定義gr.Exmples物件後再進行渲染。解決方法是在gr.Blocks()範圍外定義gr.Textbox,並在UI中希望放置的任何位置使用元件的.render()方法。
import gradio as gr
input_textbox = gr.Textbox()
with gr.Blocks() as demo:
#提供範例輸入給input_textbox,範例輸入以巢狀列表形式設定
gr.Examples(["hello", "bonjour", "merhaba"], input_textbox)
# render函數渲染input_textbox
input_textbox.render()
demo.launch()
要獲得額外的樣式功能,您可以設定行內css屬性將任何樣式給應用程式。如下所示。
import gradio as gr
#修改blocks的背景顏色
with gr.Blocks(css=".gradio-container {background-color: red}") as demo:
box1 = gr.Textbox(value="Good Job")
box2 = gr.Textbox(value="Failure")
demo.launch()
您可以向任何元件新增HTML元素。通過elem_id選擇對應的css元素。
import gradio as gr
# 這裡用的是id屬性設定
with gr.Blocks(css="#warning {background-color: red}") as demo:
box1 = gr.Textbox(value="Good Job", elem_id="warning")
box2 = gr.Textbox(value="Failure")
box3 = gr.Textbox(value="None", elem_id="warning")
demo.launch()
如果執行環境能夠連線網際網路,在launch函數中設定share引數為True,那麼執行程式後。Gradio的伺服器會提供XXXXX.gradio.app地址。通過其他裝置,比如手機或者筆記型電腦,都可以存取該應用。這種方式下該連結只是本地伺服器的代理,不會儲存通過本地應用程式傳送的任何資料。這個連結在有效期內是免費的,好處就是不需要自己搭建伺服器,壞處就是太慢了,畢竟資料經過別人的伺服器。
demo.launch(share=True)
為了便於向合作伙伴永久展示我們的模型App,可以將gradio的模型部署到 HuggingFace的 Space託管空間中,完全免費的哦。
方法如下:
1,註冊huggingface賬號:https://huggingface.co/join
2,在space空間中建立專案:https://huggingface.co/spaces
3,建立好的專案有一個Readme檔案,可以根據說明操作,也可以手工編輯app.py和requirements.txt檔案。
通過設定server_name=‘0.0.0.0’(表示使用本機ip),server_port(可不改,預設值是7860)。那麼可以通過本機ip:埠號在區域網內分享應用。
#show_error為True表示在控制檯顯示錯誤資訊。
demo.launch(server_name='0.0.0.0', server_port=8080, show_error=True)
這裡host地址可以自行在電腦查詢,C:\Windows\System32\drivers\etc\hosts 修改一下即可 127.0.0.1再製定埠號
在首次開啟網頁前,可以設定賬戶密碼。比如auth引數為(賬戶,密碼)的元組資料。這種模式下不能夠使用queue函數。
demo.launch(auth=("admin", "pass1234"))
如果想設定更為複雜的賬戶密碼和密碼提示,可以通過函數設定校驗規則。
#賬戶和密碼相同就可以通過
def same_auth(username, password):
return username == password
demo.launch(auth=same_auth,auth_message="username and password must be the same")
#!pip install gradio, ultralytics, transformers, torchkeras
import gradio as gr
from transformers import pipeline
pipe = pipeline("text-classification")
def clf(text):
result = pipe(text)
label = result[0]['label']
score = result[0]['score']
res = {label:score,'POSITIVE' if label=='NEGATIVE' else 'NEGATIVE': 1-score}
return res
demo = gr.Interface(fn=clf, inputs="text", outputs="label")
gr.close_all()
demo.launch(share=True)
import gradio as gr
import pandas as pd
from ultralytics import YOLO
from skimage import data
from PIL import Image
model = YOLO('yolov8n-cls.pt')
def predict(img):
result = model.predict(source=img)
df = pd.Series(result[0].names).to_frame()
df.columns = ['names']
df['probs'] = result[0].probs
df = df.sort_values('probs',ascending=False)
res = dict(zip(df['names'],df['probs']))
return res
gr.close_all()
demo = gr.Interface(fn = predict,inputs = gr.Image(type='pil'), outputs = gr.Label(num_top_classes=5),
examples = ['cat.jpeg','people.jpeg','coffee.jpeg'])
demo.launch()
import gradio as gr
import pandas as pd
from skimage import data
from ultralytics.yolo.data import utils
model = YOLO('yolov8n.pt')
#load class_names
yaml_path = str(Path(ultralytics.__file__).parent/'datasets/coco128.yaml')
class_names = utils.yaml_load(yaml_path)['names']
def detect(img):
if isinstance(img,str):
img = get_url_img(img) if img.startswith('http') else Image.open(img).convert('RGB')
result = model.predict(source=img)
if len(result[0].boxes.boxes)>0:
vis = plots.plot_detection(img,boxes=result[0].boxes.boxes,
class_names=class_names, min_score=0.2)
else:
vis = img
return vis
with gr.Blocks() as demo:
gr.Markdown("# yolov8目標檢測演示")
with gr.Tab("捕捉攝像頭喔"):
in_img = gr.Image(source='webcam',type='pil')
button = gr.Button("執行檢測",variant="primary")
gr.Markdown("## 預測輸出")
out_img = gr.Image(type='pil')
button.click(detect,
inputs=in_img,
outputs=out_img)
gr.close_all()
demo.queue(concurrency_count=5)
demo.launch()
儘管gradio的設計初衷是為了快速建立機器學習使用者互動頁面。但實際上,通過組合gradio的各種元件,使用者可以很方便地實現非常實用的各種應用小工具。
例如: 資料分析展示dashboard, 資料標註工具, 製作一個小遊戲介面等等。
本範例我們將應用 gradio來構建一個圖片篩選器,從百度爬取的一堆貓咪表情包中刷選一些我們喜歡的出來。
#!pip install -U torchkeras
import torchkeras
from torchkeras.data import download_baidu_pictures
download_baidu_pictures('貓咪表情包',100)
import gradio as gr
from PIL import Image
import time,os
from pathlib import Path
base_dir = '貓咪表情包'
selected_dir = 'selected'
files = [str(x) for x in
Path(base_dir).rglob('*.jp*g')
if 'checkpoint' not in str(x)]
def show_img(path):
return Image.open(path)
def fn_before(done,todo):
...
return done,todo,path,img
def fn_next(done,todo):
...
return done,todo,path,img
def save_selected(img_path):
...
return msg
def get_default_msg():
...
return msg
with gr.Blocks() as demo:
with gr.Row():
total = gr.Number(len(files),label='總數量')
with gr.Row(scale = 1):
bn_before = gr.Button("上一張")
bn_next = gr.Button("下一張")
with gr.Row(scale = 2):
done = gr.Number(0,label='已完成')
todo = gr.Number(len(files),label='待完成')
path = gr.Text(files[0],lines=1, label='當前圖片路徑')
feedback_button = gr.Button("選擇圖片",variant="primary")
msg = gr.TextArea(value=get_default_msg,lines=3,max_lines = 5)
img = gr.Image(value = show_img(files[0]),type='pil')
bn_before.click(fn_before,
inputs= [done,todo],
outputs=[done,todo,path,img])
bn_next.click(fn_next,
inputs= [done,todo],
outputs=[done,todo,path,img])
feedback_button.click(save_selected,
inputs = path,
outputs = msg
)
demo.launch()