Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -671,16 +671,26 @@ with gr.Blocks(title="Diffusion as Shader") as demo:
|
|
| 671 |
with right_column:
|
| 672 |
tracking_video = gr.Video(label="Tracking Video")
|
| 673 |
|
| 674 |
-
# 初始状态下按钮不可用
|
| 675 |
apply_tracking_btn = gr.Button("Generate Video", variant="primary", size="lg", interactive=False)
|
| 676 |
output_video = gr.Video(label="Generated Video")
|
| 677 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 678 |
with left_column:
|
| 679 |
source_upload = gr.UploadButton("1. Upload Source", file_types=["image", "video"])
|
| 680 |
gr.Markdown("Upload a video or image, We will extract the motion and space structure from it")
|
| 681 |
source_preview = gr.Video(label="Source Preview")
|
| 682 |
|
| 683 |
-
# 上传文件后更新预览
|
| 684 |
def update_source_preview(file):
|
| 685 |
if file is None:
|
| 686 |
return None
|
|
@@ -692,14 +702,15 @@ with gr.Blocks(title="Diffusion as Shader") as demo:
|
|
| 692 |
inputs=[source_upload],
|
| 693 |
outputs=[source_preview]
|
| 694 |
)
|
| 695 |
-
|
|
|
|
| 696 |
gr.Markdown("2. Describe the scene and the motion you want to create")
|
| 697 |
common_prompt = gr.Textbox(label="Prompt: ", lines=2)
|
| 698 |
|
| 699 |
with gr.Tabs() as task_tabs:
|
| 700 |
# Motion Transfer tab
|
| 701 |
with gr.TabItem("Motion Transfer"):
|
| 702 |
-
gr.Markdown("
|
| 703 |
|
| 704 |
# Simplified controls - Radio buttons for Yes/No and separate file upload
|
| 705 |
with gr.Row():
|
|
@@ -742,281 +753,7 @@ with gr.Blocks(title="Diffusion as Shader") as demo:
|
|
| 742 |
|
| 743 |
with gr.TabItem("Object Manipulation"):
|
| 744 |
gr.Markdown("Object Manipulation is not available in Huggingface Space, please deploy our GitHub project on your own machine")
|
| 745 |
-
|
| 746 |
-
# # Camera Control tab
|
| 747 |
-
# with gr.TabItem("Camera Control"):
|
| 748 |
-
# gr.Markdown("## Camera Control")
|
| 749 |
-
|
| 750 |
-
# cc_camera_motion = gr.Textbox(
|
| 751 |
-
# label="Current Camera Motion Sequence",
|
| 752 |
-
# placeholder="Your camera motion sequence will appear here...",
|
| 753 |
-
# interactive=False
|
| 754 |
-
# )
|
| 755 |
-
|
| 756 |
-
# # Use tabs for different motion types
|
| 757 |
-
# with gr.Tabs() as cc_motion_tabs:
|
| 758 |
-
# # Translation tab
|
| 759 |
-
# with gr.TabItem("Translation (trans)"):
|
| 760 |
-
# with gr.Row():
|
| 761 |
-
# cc_trans_x = gr.Slider(minimum=-1.0, maximum=1.0, value=0.0, step=0.05, label="X-axis Movement")
|
| 762 |
-
# cc_trans_y = gr.Slider(minimum=-1.0, maximum=1.0, value=0.0, step=0.05, label="Y-axis Movement")
|
| 763 |
-
# cc_trans_z = gr.Slider(minimum=-1.0, maximum=1.0, value=0.0, step=0.05, label="Z-axis Movement (depth)")
|
| 764 |
-
|
| 765 |
-
# with gr.Row():
|
| 766 |
-
# cc_trans_start = gr.Number(minimum=0, maximum=48, value=0, step=1, label="Start Frame", precision=0)
|
| 767 |
-
# cc_trans_end = gr.Number(minimum=0, maximum=48, value=48, step=1, label="End Frame", precision=0)
|
| 768 |
-
|
| 769 |
-
# cc_trans_note = gr.Markdown("""
|
| 770 |
-
# **Translation Notes:**
|
| 771 |
-
# - Positive X: Move right, Negative X: Move left
|
| 772 |
-
# - Positive Y: Move down, Negative Y: Move up
|
| 773 |
-
# - Positive Z: Zoom in, Negative Z: Zoom out
|
| 774 |
-
# """)
|
| 775 |
-
|
| 776 |
-
# # Add translation button in the Translation tab
|
| 777 |
-
# cc_add_trans = gr.Button("Add Camera Translation", variant="secondary")
|
| 778 |
-
|
| 779 |
-
# # Function to add translation motion
|
| 780 |
-
# def add_translation_motion(current_motion, trans_x, trans_y, trans_z, trans_start, trans_end):
|
| 781 |
-
# # Format: trans dx dy dz [start_frame end_frame]
|
| 782 |
-
# frame_range = f" {int(trans_start)} {int(trans_end)}" if trans_start != 0 or trans_end != 48 else ""
|
| 783 |
-
# new_motion = f"trans {trans_x:.2f} {trans_y:.2f} {trans_z:.2f}{frame_range}"
|
| 784 |
-
|
| 785 |
-
# # Append to existing motion string with semicolon separator if needed
|
| 786 |
-
# if current_motion and current_motion.strip():
|
| 787 |
-
# updated_motion = f"{current_motion}; {new_motion}"
|
| 788 |
-
# else:
|
| 789 |
-
# updated_motion = new_motion
|
| 790 |
-
|
| 791 |
-
# return updated_motion
|
| 792 |
-
|
| 793 |
-
# # Connect translation button
|
| 794 |
-
# cc_add_trans.click(
|
| 795 |
-
# fn=add_translation_motion,
|
| 796 |
-
# inputs=[
|
| 797 |
-
# cc_camera_motion,
|
| 798 |
-
# cc_trans_x, cc_trans_y, cc_trans_z, cc_trans_start, cc_trans_end
|
| 799 |
-
# ],
|
| 800 |
-
# outputs=[cc_camera_motion]
|
| 801 |
-
# )
|
| 802 |
-
|
| 803 |
-
# # Rotation tab
|
| 804 |
-
# with gr.TabItem("Rotation (rot)"):
|
| 805 |
-
# with gr.Row():
|
| 806 |
-
# cc_rot_axis = gr.Dropdown(choices=["x", "y", "z"], value="y", label="Rotation Axis")
|
| 807 |
-
# cc_rot_angle = gr.Slider(minimum=-30, maximum=30, value=5, step=1, label="Rotation Angle (degrees)")
|
| 808 |
-
|
| 809 |
-
# with gr.Row():
|
| 810 |
-
# cc_rot_start = gr.Number(minimum=0, maximum=48, value=0, step=1, label="Start Frame", precision=0)
|
| 811 |
-
# cc_rot_end = gr.Number(minimum=0, maximum=48, value=48, step=1, label="End Frame", precision=0)
|
| 812 |
-
|
| 813 |
-
# cc_rot_note = gr.Markdown("""
|
| 814 |
-
# **Rotation Notes:**
|
| 815 |
-
# - X-axis rotation: Tilt camera up/down
|
| 816 |
-
# - Y-axis rotation: Pan camera left/right
|
| 817 |
-
# - Z-axis rotation: Roll camera
|
| 818 |
-
# """)
|
| 819 |
-
|
| 820 |
-
# # Add rotation button in the Rotation tab
|
| 821 |
-
# cc_add_rot = gr.Button("Add Camera Rotation", variant="secondary")
|
| 822 |
-
|
| 823 |
-
# # Function to add rotation motion
|
| 824 |
-
# def add_rotation_motion(current_motion, rot_axis, rot_angle, rot_start, rot_end):
|
| 825 |
-
# # Format: rot axis angle [start_frame end_frame]
|
| 826 |
-
# frame_range = f" {int(rot_start)} {int(rot_end)}" if rot_start != 0 or rot_end != 48 else ""
|
| 827 |
-
# new_motion = f"rot {rot_axis} {rot_angle}{frame_range}"
|
| 828 |
-
|
| 829 |
-
# # Append to existing motion string with semicolon separator if needed
|
| 830 |
-
# if current_motion and current_motion.strip():
|
| 831 |
-
# updated_motion = f"{current_motion}; {new_motion}"
|
| 832 |
-
# else:
|
| 833 |
-
# updated_motion = new_motion
|
| 834 |
-
|
| 835 |
-
# return updated_motion
|
| 836 |
-
|
| 837 |
-
# # Connect rotation button
|
| 838 |
-
# cc_add_rot.click(
|
| 839 |
-
# fn=add_rotation_motion,
|
| 840 |
-
# inputs=[
|
| 841 |
-
# cc_camera_motion,
|
| 842 |
-
# cc_rot_axis, cc_rot_angle, cc_rot_start, cc_rot_end
|
| 843 |
-
# ],
|
| 844 |
-
# outputs=[cc_camera_motion]
|
| 845 |
-
# )
|
| 846 |
-
|
| 847 |
-
# # Add a clear button to reset the motion sequence
|
| 848 |
-
# cc_clear_motion = gr.Button("Clear All Motions", variant="stop")
|
| 849 |
-
|
| 850 |
-
# def clear_camera_motion():
|
| 851 |
-
# return ""
|
| 852 |
-
|
| 853 |
-
# cc_clear_motion.click(
|
| 854 |
-
# fn=clear_camera_motion,
|
| 855 |
-
# inputs=[],
|
| 856 |
-
# outputs=[cc_camera_motion]
|
| 857 |
-
# )
|
| 858 |
-
|
| 859 |
-
# cc_tracking_method = gr.Radio(
|
| 860 |
-
# label="Tracking Method",
|
| 861 |
-
# choices=["moge", "cotracker"],
|
| 862 |
-
# value="cotracker"
|
| 863 |
-
# )
|
| 864 |
-
|
| 865 |
-
# # Add run button for Camera Control tab
|
| 866 |
-
# cc_run_btn = gr.Button("Generate Tracking", variant="primary", size="lg")
|
| 867 |
-
|
| 868 |
-
# # Connect to process function, but don't apply tracking
|
| 869 |
-
# cc_run_btn.click(
|
| 870 |
-
# fn=process_camera_control,
|
| 871 |
-
# inputs=[
|
| 872 |
-
# source_upload, common_prompt,
|
| 873 |
-
# cc_camera_motion, cc_tracking_method
|
| 874 |
-
# ],
|
| 875 |
-
# outputs=[tracking_video, video_tensor_state, tracking_tensor_state, repaint_img_tensor_state, fps_state]
|
| 876 |
-
# ).then(
|
| 877 |
-
# fn=enable_apply_button,
|
| 878 |
-
# inputs=[tracking_video],
|
| 879 |
-
# outputs=[apply_tracking_btn]
|
| 880 |
-
# )
|
| 881 |
-
|
| 882 |
-
# # Object Manipulation tab
|
| 883 |
-
# with gr.TabItem("Object Manipulation"):
|
| 884 |
-
# gr.Markdown("## Object Manipulation")
|
| 885 |
-
# om_object_mask = gr.File(
|
| 886 |
-
# label="Object Mask Image",
|
| 887 |
-
# file_types=["image"]
|
| 888 |
-
# )
|
| 889 |
-
# gr.Markdown("Upload a binary mask image, white areas indicate the object to manipulate")
|
| 890 |
-
# om_object_motion = gr.Dropdown(
|
| 891 |
-
# label="Object Motion Type",
|
| 892 |
-
# choices=["up", "down", "left", "right", "front", "back", "rot"],
|
| 893 |
-
# value="up"
|
| 894 |
-
# )
|
| 895 |
-
# om_tracking_method = gr.Radio(
|
| 896 |
-
# label="Tracking Method",
|
| 897 |
-
# choices=["moge", "cotracker"],
|
| 898 |
-
# value="cotracker"
|
| 899 |
-
# )
|
| 900 |
-
|
| 901 |
-
# # Add run button for Object Manipulation tab
|
| 902 |
-
# om_run_btn = gr.Button("Generate Tracking", variant="primary", size="lg")
|
| 903 |
-
|
| 904 |
-
# # Connect to process function, but don't apply tracking
|
| 905 |
-
# om_run_btn.click(
|
| 906 |
-
# fn=process_object_manipulation,
|
| 907 |
-
# inputs=[
|
| 908 |
-
# source_upload, common_prompt,
|
| 909 |
-
# om_object_motion, om_object_mask, om_tracking_method
|
| 910 |
-
# ],
|
| 911 |
-
# outputs=[tracking_video, video_tensor_state, tracking_tensor_state, repaint_img_tensor_state, fps_state]
|
| 912 |
-
# ).then(
|
| 913 |
-
# fn=enable_apply_button,
|
| 914 |
-
# inputs=[tracking_video],
|
| 915 |
-
# outputs=[apply_tracking_btn]
|
| 916 |
-
# )
|
| 917 |
-
|
| 918 |
-
# # Animating meshes to video tab
|
| 919 |
-
# with gr.TabItem("Animating meshes to video"):
|
| 920 |
-
# gr.Markdown("## Mesh Animation to Video")
|
| 921 |
-
# gr.Markdown("""
|
| 922 |
-
# Note: Currently only supports tracking videos generated with Blender (version > 4.0).
|
| 923 |
-
# Please run the script `scripts/blender.py` in your Blender project to generate tracking videos.
|
| 924 |
-
# """)
|
| 925 |
-
# ma_tracking_video = gr.File(
|
| 926 |
-
# label="Tracking Video",
|
| 927 |
-
# file_types=["video"],
|
| 928 |
-
# # 添加 change 事件处理器,当上传文件时自动激活 Generate Video 按钮
|
| 929 |
-
# elem_id="ma_tracking_video"
|
| 930 |
-
# )
|
| 931 |
-
# gr.Markdown("Tracking video needs to be generated from Blender")
|
| 932 |
-
|
| 933 |
-
# # Simplified controls - Radio buttons for Yes/No and separate file upload
|
| 934 |
-
# with gr.Row():
|
| 935 |
-
# ma_repaint_option = gr.Radio(
|
| 936 |
-
# label="Repaint First Frame",
|
| 937 |
-
# choices=["No", "Yes"],
|
| 938 |
-
# value="No"
|
| 939 |
-
# )
|
| 940 |
-
# gr.Markdown("### Note: If you want to use your own image as repainted first frame, please upload the image in below.")
|
| 941 |
-
# # Custom image uploader (always visible)
|
| 942 |
-
# ma_repaint_image = gr.File(
|
| 943 |
-
# label="Custom Repaint Image",
|
| 944 |
-
# file_types=["image"]
|
| 945 |
-
# )
|
| 946 |
-
|
| 947 |
-
# # 修改按钮名称为 "Apply Repaint"
|
| 948 |
-
# ma_run_btn = gr.Button("Apply Repaint", variant="primary", size="lg")
|
| 949 |
-
|
| 950 |
-
# # 添加 tracking video 上传事件处理
|
| 951 |
-
# def handle_tracking_upload(file):
|
| 952 |
-
# if file is not None:
|
| 953 |
-
# tracking_path = save_uploaded_file(file)
|
| 954 |
-
# if tracking_path:
|
| 955 |
-
# return tracking_path, gr.update(interactive=True)
|
| 956 |
-
# return None, gr.update(interactive=False)
|
| 957 |
-
|
| 958 |
-
# # 当上传 tracking video 时,直接显示并激活 Generate Video 按钮
|
| 959 |
-
# ma_tracking_video.change(
|
| 960 |
-
# fn=handle_tracking_upload,
|
| 961 |
-
# inputs=[ma_tracking_video],
|
| 962 |
-
# outputs=[tracking_video, apply_tracking_btn]
|
| 963 |
-
# )
|
| 964 |
-
|
| 965 |
-
# # 修改 process_mesh_animation 函数的行为
|
| 966 |
-
# def process_mesh_animation_repaint(source, prompt, ma_repaint_option, ma_repaint_image):
|
| 967 |
-
# """只处理重绘部分,不处理跟踪视频"""
|
| 968 |
-
# try:
|
| 969 |
-
# # 保存上传的文件
|
| 970 |
-
# input_video_path = save_uploaded_file(source)
|
| 971 |
-
# if input_video_path is None:
|
| 972 |
-
# return None, None, None, None
|
| 973 |
-
|
| 974 |
-
# das = get_das_pipeline()
|
| 975 |
-
# video_tensor, fps, is_video = load_media(input_video_path)
|
| 976 |
-
# das.fps = fps
|
| 977 |
-
|
| 978 |
-
# repaint_img_tensor = None
|
| 979 |
-
# if ma_repaint_image is not None:
|
| 980 |
-
# repaint_path = save_uploaded_file(ma_repaint_image)
|
| 981 |
-
# repaint_img_tensor, _, _ = load_media(repaint_path)
|
| 982 |
-
# repaint_img_tensor = repaint_img_tensor[0]
|
| 983 |
-
# elif ma_repaint_option == "Yes":
|
| 984 |
-
# repainter = FirstFrameRepainter(gpu_id=GPU_ID, output_dir=OUTPUT_DIR)
|
| 985 |
-
# repaint_img_tensor = repainter.repaint(
|
| 986 |
-
# video_tensor[0],
|
| 987 |
-
# prompt=prompt,
|
| 988 |
-
# depth_path=None
|
| 989 |
-
# )
|
| 990 |
-
|
| 991 |
-
# # 返回处理结果,但不包括跟踪视频路径
|
| 992 |
-
# return video_tensor, None, repaint_img_tensor, fps
|
| 993 |
-
# except Exception as e:
|
| 994 |
-
# import traceback
|
| 995 |
-
# print(f"Processing failed: {str(e)}\n{traceback.format_exc()}")
|
| 996 |
-
# return None, None, None, None
|
| 997 |
-
|
| 998 |
-
# # 连接到修改后的处理函数
|
| 999 |
-
# ma_run_btn.click(
|
| 1000 |
-
# fn=process_mesh_animation_repaint,
|
| 1001 |
-
# inputs=[
|
| 1002 |
-
# source_upload, common_prompt,
|
| 1003 |
-
# ma_repaint_option, ma_repaint_image
|
| 1004 |
-
# ],
|
| 1005 |
-
# outputs=[video_tensor_state, tracking_tensor_state, repaint_img_tensor_state, fps_state]
|
| 1006 |
-
# )
|
| 1007 |
-
|
| 1008 |
-
# 在所有 UI 元素定义之后,添加 Examples 组件
|
| 1009 |
-
examples_list = load_examples()
|
| 1010 |
-
if examples_list:
|
| 1011 |
-
with gr.Blocks() as examples_block:
|
| 1012 |
-
gr.Examples(
|
| 1013 |
-
examples=examples_list,
|
| 1014 |
-
inputs=[source_preview, mt_repaint_preview, common_prompt, tracking_video, output_video],
|
| 1015 |
-
outputs=[source_preview, mt_repaint_preview, common_prompt, tracking_video, output_video],
|
| 1016 |
-
fn=lambda *args: args, # 简单地返回输入作为输出
|
| 1017 |
-
cache_examples=True,
|
| 1018 |
-
label="Examples"
|
| 1019 |
-
)
|
| 1020 |
|
| 1021 |
# Launch interface
|
| 1022 |
if __name__ == "__main__":
|
|
|
|
| 671 |
with right_column:
|
| 672 |
tracking_video = gr.Video(label="Tracking Video")
|
| 673 |
|
|
|
|
| 674 |
apply_tracking_btn = gr.Button("Generate Video", variant="primary", size="lg", interactive=False)
|
| 675 |
output_video = gr.Video(label="Generated Video")
|
| 676 |
|
| 677 |
+
examples_list = load_examples()
|
| 678 |
+
if examples_list:
|
| 679 |
+
with gr.Blocks() as examples_block:
|
| 680 |
+
gr.Examples(
|
| 681 |
+
examples=examples_list,
|
| 682 |
+
inputs=[source_preview, mt_repaint_preview, common_prompt, tracking_video, output_video],
|
| 683 |
+
outputs=[source_preview, mt_repaint_preview, common_prompt, tracking_video, output_video],
|
| 684 |
+
fn=lambda *args: args,
|
| 685 |
+
cache_examples=True,
|
| 686 |
+
label="Examples"
|
| 687 |
+
)
|
| 688 |
+
|
| 689 |
with left_column:
|
| 690 |
source_upload = gr.UploadButton("1. Upload Source", file_types=["image", "video"])
|
| 691 |
gr.Markdown("Upload a video or image, We will extract the motion and space structure from it")
|
| 692 |
source_preview = gr.Video(label="Source Preview")
|
| 693 |
|
|
|
|
| 694 |
def update_source_preview(file):
|
| 695 |
if file is None:
|
| 696 |
return None
|
|
|
|
| 702 |
inputs=[source_upload],
|
| 703 |
outputs=[source_preview]
|
| 704 |
)
|
| 705 |
+
|
| 706 |
+
gr.Markdown("### Prompt")
|
| 707 |
gr.Markdown("2. Describe the scene and the motion you want to create")
|
| 708 |
common_prompt = gr.Textbox(label="Prompt: ", lines=2)
|
| 709 |
|
| 710 |
with gr.Tabs() as task_tabs:
|
| 711 |
# Motion Transfer tab
|
| 712 |
with gr.TabItem("Motion Transfer"):
|
| 713 |
+
gr.Markdown("### Motion Transfer")
|
| 714 |
|
| 715 |
# Simplified controls - Radio buttons for Yes/No and separate file upload
|
| 716 |
with gr.Row():
|
|
|
|
| 753 |
|
| 754 |
with gr.TabItem("Object Manipulation"):
|
| 755 |
gr.Markdown("Object Manipulation is not available in Huggingface Space, please deploy our GitHub project on your own machine")
|
| 756 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 757 |
|
| 758 |
# Launch interface
|
| 759 |
if __name__ == "__main__":
|