alidenewade's picture
Update app.py
e923d87 verified
import gradio as gr
import matplotlib.pyplot as plt
from Bio import SeqIO
from Bio.Seq import Seq # Though not directly used in final logic, good for context
from Bio.Restriction import RestrictionBatch, AllEnzymes, Analysis
import os # For getting filename
# Ensure matplotlib uses a non-interactive backend for Gradio
import matplotlib
matplotlib.use('Agg')
# Define paths for example files
EXAMPLE_DIR = "eg_files"
EXAMPLE_PLASMID1_PATH = os.path.join(EXAMPLE_DIR, "plasmid1_example.gb")
EXAMPLE_PLASMID2_PATH = os.path.join(EXAMPLE_DIR, "plasmid2_example.gb")
# --- Core BioPython and Plotting Functions ---
def simulate_digest_and_plot_gradio(plasmid_seq_record, enzyme_name, plasmid_label):
"""
Simulates restriction digest and plots a virtual agarose gel.
Uses enzyme.catalyse() for robust fragment generation.
"""
fig, ax = plt.subplots(figsize=(6, 8)) # Adjusted size for better readability
if plasmid_seq_record is None:
ax.text(0.5, 0.5, f"Error: Plasmid data for '{plasmid_label}' is missing.",
ha='center', va='center', wrap=True, color='red')
ax.set_xticks([]); ax.set_yticks([])
ax.set_title(f"Virtual Gel: {plasmid_label} - Error", fontsize=10)
plt.tight_layout()
return fig
try:
enzyme = AllEnzymes.get(str(enzyme_name))
if not enzyme:
raise ValueError(f"Enzyme '{enzyme_name}' not found in Biopython's AllEnzymes.")
except Exception as e:
ax.text(0.5, 0.5, f"Error: Could not load enzyme '{enzyme_name}'.\n{e}",
ha='center', va='center', wrap=True, color='red')
ax.set_xticks([]); ax.set_yticks([])
ax.set_title(f"Virtual Gel: {plasmid_label} - Error", fontsize=10)
plt.tight_layout()
return fig
fragments_seqs = enzyme.catalyse(plasmid_seq_record.seq)
is_uncut = False
if len(fragments_seqs) == 1 and len(fragments_seqs[0]) == len(plasmid_seq_record.seq):
if not enzyme.search(plasmid_seq_record.seq):
is_uncut = True
if is_uncut:
ax.text(0.5, 0.5, f"Enzyme {enzyme_name} does not cut {plasmid_label}",
ha='center', va='center', wrap=True)
ax.set_title(f"Virtual Gel: {plasmid_label} + {enzyme_name} (No Sites)", fontsize=10)
lengths = [len(plasmid_seq_record.seq)]
else:
lengths = sorted([len(f) for f in fragments_seqs], reverse=True)
ax.set_yscale("log")
min_display_size = 10
plasmid_len_for_scale = max(len(plasmid_seq_record.seq), min_display_size * 10)
max_display_size = max(plasmid_len_for_scale * 1.1, min_display_size * 2)
ax.set_ylim(min_display_size, max_display_size)
band_width = 0.6
lane_center = 0.5
if not lengths:
ax.text(0.5, 0.5, "No fragments to display.", ha='center', va='center')
else:
for i, size in enumerate(lengths):
if size < min_display_size:
ax.text(lane_center, min_display_size * 1.1 , f"(+ {len(lengths) - i} small fragments < {min_display_size}bp not shown)",
ha='center', va='top', fontsize=7, color='gray')
break
ax.plot([lane_center - band_width/2, lane_center + band_width/2], [size, size],
linewidth=6, color='royalblue', solid_capstyle='butt')
ax.text(lane_center + band_width/2 + 0.05, size, f"{size} bp",
va='center', ha='left', fontsize=8)
ax.invert_yaxis()
ax.set_title(f"Virtual Gel: {plasmid_label} digested with {enzyme_name}", fontsize=10)
ax.set_ylabel("Fragment Size (bp)", fontsize=9)
ax.set_xlabel("Lane 1", fontsize=9)
ax.set_xticks([])
ax.tick_params(axis='y', labelsize=8)
well_top_y = ax.get_ylim()[0]
well_line_y = well_top_y * 1.01
well_depth_y = well_top_y * 0.98
ax.plot([lane_center - band_width/1.5, lane_center + band_width/1.5], [well_line_y, well_line_y],
linewidth=1.5, color='black')
ax.plot([lane_center - band_width/1.5, lane_center - band_width/1.5], [well_line_y, well_depth_y],
linewidth=1.5, color='black')
ax.plot([lane_center + band_width/1.5, lane_center + band_width/1.5], [well_line_y, well_depth_y],
linewidth=1.5, color='black')
plt.tight_layout(pad=1.5)
return fig
def analyze_plasmids_gradio(file1_path, file2_path, current_plasmid_choice_for_plot):
"""
Analyzes two plasmid files to find unique restriction enzymes and
enzymes that cut both plasmids but with different fragmentation patterns.
Returns status messages, plasmid data, lists of enzyme names,
and an update for the enzyme selection dropdown.
"""
initial_enzyme_dd_update = gr.update(choices=["Analyze plasmids first"], value="Analyze plasmids first", interactive=False)
empty_return_for_error = ["", "", "", None, None, [], [], [], initial_enzyme_dd_update]
# Check if example files exist if paths match example paths
example_file_error_msg = ""
if file1_path == EXAMPLE_PLASMID1_PATH and not os.path.exists(EXAMPLE_PLASMID1_PATH):
example_file_error_msg += f"Example file not found: {EXAMPLE_PLASMID1_PATH}. Please create it in the '{EXAMPLE_DIR}' directory.\n"
if file2_path == EXAMPLE_PLASMID2_PATH and not os.path.exists(EXAMPLE_PLASMID2_PATH):
example_file_error_msg += f"Example file not found: {EXAMPLE_PLASMID2_PATH}. Please create it in the '{EXAMPLE_DIR}' directory.\n"
if example_file_error_msg:
return example_file_error_msg, *empty_return_for_error
if file1_path is None or file2_path is None:
return "Error: Please upload or load both plasmid files.", *empty_return_for_error
try:
def read_plasmid(filepath, filename_for_error):
try:
return SeqIO.read(filepath, "genbank")
except Exception:
try:
return SeqIO.read(filepath, "fasta")
except Exception as e_fasta:
raise ValueError(f"Could not parse '{filename_for_error}'. Ensure it's a valid GenBank or FASTA file. Last error: {e_fasta}")
p1_orig_filename = os.path.basename(file1_path)
p2_orig_filename = os.path.basename(file2_path)
plasmid1_seq_rec = read_plasmid(file1_path, p1_orig_filename)
plasmid2_seq_rec = read_plasmid(file2_path, p2_orig_filename)
except Exception as e:
return str(e), *empty_return_for_error
valid_enzyme_objects = []
for enz_name in AllEnzymes.elements():
enzyme_obj = AllEnzymes.get(enz_name)
if enzyme_obj and hasattr(enzyme_obj, 'site') and enzyme_obj.site is not None:
if hasattr(enzyme_obj, 'is_restriction') and enzyme_obj.is_restriction():
valid_enzyme_objects.append(enzyme_obj)
elif not hasattr(enzyme_obj, 'is_restriction'):
valid_enzyme_objects.append(enzyme_obj)
if not valid_enzyme_objects:
return "Error: Could not load any restriction enzymes from Biopython.", *empty_return_for_error
enzymes_batch = RestrictionBatch(valid_enzyme_objects)
analysis1 = Analysis(enzymes_batch, plasmid1_seq_rec.seq, linear=False)
analysis2 = Analysis(enzymes_batch, plasmid2_seq_rec.seq, linear=False)
enzymes_cutting_p1_obj = set(analysis1.with_sites().keys())
enzymes_cutting_p2_obj = set(analysis2.with_sites().keys())
unique_to_1_obj = sorted(list(enzymes_cutting_p1_obj - enzymes_cutting_p2_obj), key=lambda e: str(e))
unique_to_2_obj = sorted(list(enzymes_cutting_p2_obj - enzymes_cutting_p1_obj), key=lambda e: str(e))
unique_to_1_names = [str(e) for e in unique_to_1_obj]
unique_to_2_names = [str(e) for e in unique_to_2_obj]
p1_display_label = f"Plasmid 1 ({p1_orig_filename})"
p2_display_label = f"Plasmid 2 ({p2_orig_filename})"
msg1 = f"Enzymes cutting only {p1_display_label} ({len(unique_to_1_names)}):\n" + ", ".join(unique_to_1_names) if unique_to_1_names else f"No unique enzymes found for {p1_display_label}."
msg2 = f"Enzymes cutting only {p2_display_label} ({len(unique_to_2_names)}):\n" + ", ".join(unique_to_2_names) if unique_to_2_names else f"No unique enzymes found for {p2_display_label}."
# New: Find enzymes cutting both but with different fragments
common_enzymes_obj = enzymes_cutting_p1_obj.intersection(enzymes_cutting_p2_obj)
common_diff_fragments_enzymes_names = []
for enzyme_obj in common_enzymes_obj:
try:
fragments1_seqs = enzyme_obj.catalyse(plasmid1_seq_rec.seq)
fragments2_seqs = enzyme_obj.catalyse(plasmid2_seq_rec.seq)
lengths1 = sorted([len(f) for f in fragments1_seqs])
lengths2 = sorted([len(f) for f in fragments2_seqs])
if lengths1 != lengths2:
common_diff_fragments_enzymes_names.append(str(enzyme_obj))
except Exception as e_cat_common:
print(f"Warning: Error during catalysis comparison for common enzyme {str(enzyme_obj)}: {e_cat_common}")
# Optionally skip this enzyme or log more formally
common_diff_fragments_enzymes_names = sorted(list(set(common_diff_fragments_enzymes_names)))
msg_common_diff = f"Enzymes cutting BOTH plasmids with DIFFERENT fragments ({len(common_diff_fragments_enzymes_names)}):\n" + \
", ".join(common_diff_fragments_enzymes_names) if common_diff_fragments_enzymes_names \
else "No enzymes found that cut both plasmids with different fragment patterns."
status = "Analysis complete."
if not unique_to_1_names and not unique_to_2_names and not common_diff_fragments_enzymes_names:
status += " No differentiating enzymes found."
dd_choices = []
if current_plasmid_choice_for_plot == "Plasmid 1":
dd_choices = unique_to_1_names if unique_to_1_names else [f"No unique enzymes for {p1_display_label}"]
else:
dd_choices = unique_to_2_names if unique_to_2_names else [f"No unique enzymes for {p2_display_label}"]
if (current_plasmid_choice_for_plot == "Plasmid 1" and unique_to_1_names) or \
(current_plasmid_choice_for_plot == "Plasmid 2" and unique_to_2_names):
current_dd_update = gr.update(choices=["Select an enzyme"] + dd_choices, value="Select an enzyme", interactive=True)
else:
current_dd_update = gr.update(choices=dd_choices, value=dd_choices[0], interactive=False if not dd_choices or "No unique" in dd_choices[0] else True)
return status, msg1, msg2, msg_common_diff, plasmid1_seq_rec, plasmid2_seq_rec, \
unique_to_1_names, unique_to_2_names, common_diff_fragments_enzymes_names, current_dd_update
def plot_selected_digest_controller(plasmid_choice_label, enzyme_name, p1_data, p2_data):
"""
Controller to select the correct plasmid data and call the plotting function.
"""
fig_placeholder, ax_placeholder = plt.subplots(figsize=(6, 8))
ax_placeholder.text(0.5, 0.5, "Plot will appear here.", ha='center', va='center')
ax_placeholder.set_xticks([]); ax_placeholder.set_yticks([])
plt.tight_layout()
if not enzyme_name or enzyme_name == "Select an enzyme" or "No unique enzymes" in enzyme_name or "Analyze plasmids first" in enzyme_name:
ax_placeholder.clear()
ax_placeholder.text(0.5, 0.5, "Please select a valid plasmid and enzyme after analysis.", ha='center', va='center', wrap=True)
plt.tight_layout()
return fig_placeholder
target_plasmid_rec = None
target_label = ""
if plasmid_choice_label == "Plasmid 1":
if p1_data is None:
ax_placeholder.clear()
ax_placeholder.text(0.5, 0.5, "Plasmid 1 data not loaded. Please re-analyze.", ha='center', va='center', wrap=True, color='red')
plt.tight_layout()
return fig_placeholder
target_plasmid_rec = p1_data
target_label = "Plasmid 1"
if hasattr(p1_data, 'name') and p1_data.name and p1_data.name !="<unknown name>": target_label += f" ({p1_data.name})"
elif hasattr(p1_data, 'id') and p1_data.id and p1_data.id !="<unknown id>": target_label += f" ({p1_data.id})"
elif plasmid_choice_label == "Plasmid 2":
if p2_data is None:
ax_placeholder.clear()
ax_placeholder.text(0.5, 0.5, "Plasmid 2 data not loaded. Please re-analyze.", ha='center', va='center', wrap=True, color='red')
plt.tight_layout()
return fig_placeholder
target_plasmid_rec = p2_data
target_label = "Plasmid 2"
if hasattr(p2_data, 'name') and p2_data.name and p2_data.name !="<unknown name>": target_label += f" ({p2_data.name})"
elif hasattr(p2_data, 'id') and p2_data.id and p2_data.id !="<unknown id>": target_label += f" ({p2_data.id})"
else:
ax_placeholder.clear()
ax_placeholder.text(0.5, 0.5, "Invalid plasmid selection.", ha='center', va='center', wrap=True, color='red')
plt.tight_layout()
return fig_placeholder
return simulate_digest_and_plot_gradio(target_plasmid_rec, enzyme_name, target_label)
def update_enzyme_dropdown_choices_on_radio_change(plasmid_choice_label, p1_enzyme_names, p2_enzyme_names):
if plasmid_choice_label == "Plasmid 1":
choices = p1_enzyme_names if p1_enzyme_names else ["No unique enzymes for P1"]
if p1_enzyme_names:
return gr.update(choices=["Select an enzyme"] + choices, value="Select an enzyme", interactive=True)
return gr.update(choices=choices, value=choices[0], interactive=False)
elif plasmid_choice_label == "Plasmid 2":
choices = p2_enzyme_names if p2_enzyme_names else ["No unique enzymes for P2"]
if p2_enzyme_names:
return gr.update(choices=["Select an enzyme"] + choices, value="Select an enzyme", interactive=True)
return gr.update(choices=choices, value=choices[0], interactive=False)
return gr.update(choices=[], value=None, interactive=False)
def load_examples_and_auto_process():
"""
Loads example files, triggers analysis, and then attempts to auto-plot.
"""
# Step 1: Perform analysis with example files
status, msg1, msg2, msg_common_diff, p1_rec, p2_rec, p1_enz_names, p2_enz_names, \
_common_diff_list_ignore, enz_dd_update_from_analysis = \
analyze_plasmids_gradio(EXAMPLE_PLASMID1_PATH, EXAMPLE_PLASMID2_PATH, "Plasmid 1")
# If analysis failed (e.g., files not found), p1_rec or p2_rec might be None
if p1_rec is None or p2_rec is None :
fig_error, ax_error = plt.subplots(figsize=(6,8))
ax_error.text(0.5, 0.5, f"Error during example analysis:\n{status}", ha='center', va='center', color='red', wrap=True) # Display actual error
ax_error.set_xticks([]); ax_error.set_yticks([])
plt.tight_layout()
# Ensure all expected output values are provided
return status, msg1, msg2, msg_common_diff, None, None, [], [], \
gr.update(choices=["Error"], value="Error", interactive=False), \
gr.update(value="Plasmid 1"), fig_error
# Step 2: Determine auto-plot parameters and update dropdown based on analysis
auto_plot_plasmid_label = None
auto_plot_enzyme_name = None
auto_plot_plasmid_data = None
final_radio_choice = "Plasmid 1" # Default
final_enz_dd_update = enz_dd_update_from_analysis # Use directly from analysis initially
if p1_enz_names:
auto_plot_plasmid_label = "Plasmid 1"
auto_plot_enzyme_name = p1_enz_names[0]
auto_plot_plasmid_data = p1_rec
final_radio_choice = "Plasmid 1"
final_enz_dd_update = gr.update(choices=["Select an enzyme"] + p1_enz_names, value=auto_plot_enzyme_name, interactive=True)
elif p2_enz_names:
auto_plot_plasmid_label = "Plasmid 2"
auto_plot_enzyme_name = p2_enz_names[0]
auto_plot_plasmid_data = p2_rec
final_radio_choice = "Plasmid 2"
final_enz_dd_update = gr.update(choices=["Select an enzyme"] + p2_enz_names, value=auto_plot_enzyme_name, interactive=True)
else: # No unique enzymes for either, dropdown should reflect current radio choice (P1 default from analyze call)
if final_radio_choice == "Plasmid 1":
final_enz_dd_update = gr.update(choices=[f"No unique enzymes for P1 ({os.path.basename(EXAMPLE_PLASMID1_PATH)})"],
value=f"No unique enzymes for P1 ({os.path.basename(EXAMPLE_PLASMID1_PATH)})", interactive=False)
# No explicit else for P2 needed here as P1 is the default for initial dropdown population
# Step 3: Generate plot if possible
if auto_plot_enzyme_name and auto_plot_plasmid_data:
# Use the actual name from p1_data or p2_data for the label if available
plot_label_detail = ""
if auto_plot_plasmid_label == "Plasmid 1":
if hasattr(p1_rec, 'name') and p1_rec.name and p1_rec.name != "<unknown name>": plot_label_detail = f" ({p1_rec.name})"
elif hasattr(p1_rec, 'id') and p1_rec.id and p1_rec.id != "<unknown id>": plot_label_detail = f" ({p1_rec.id})"
elif auto_plot_plasmid_label == "Plasmid 2":
if hasattr(p2_rec, 'name') and p2_rec.name and p2_rec.name != "<unknown name>": plot_label_detail = f" ({p2_rec.name})"
elif hasattr(p2_rec, 'id') and p2_rec.id and p2_rec.id != "<unknown id>": plot_label_detail = f" ({p2_rec.id})"
gel_fig = simulate_digest_and_plot_gradio(auto_plot_plasmid_data, auto_plot_enzyme_name, f"{auto_plot_plasmid_label}{plot_label_detail}")
else:
fig_placeholder, ax_placeholder = plt.subplots(figsize=(6, 8))
ax_placeholder.text(0.5, 0.5, "No unique enzymes found for automatic plotting.", ha='center', va='center', wrap=True)
ax_placeholder.set_xticks([]); ax_placeholder.set_yticks([])
plt.tight_layout()
gel_fig = fig_placeholder
if not p1_enz_names and not p2_enz_names:
final_enz_dd_update = gr.update(choices=["No unique enzymes found"], value="No unique enzymes found", interactive=False)
# Return all updates
return status, msg1, msg2, msg_common_diff, p1_rec, p2_rec, p1_enz_names, p2_enz_names, \
final_enz_dd_update, gr.update(value=final_radio_choice), gel_fig
# --- Gradio Interface Definition ---
with gr.Blocks(theme=gr.themes.Default()) as demo:
gr.Markdown("# Plasmid Restriction Digest Analyzer & Virtual Gel")
gr.Markdown(
"**Instructions:**\n"
"1. Upload two plasmid sequence files (GenBank `.gb`/`.gbk` or FASTA `.fasta`/`.fna`/`.fa` format) OR click 'Load Example Files'.\n"
"2. If uploading manually, click `Analyze Uploaded Plasmids`. Results will show:\n"
" a. Enzymes that uniquely cut only Plasmid 1.\n"
" b. Enzymes that uniquely cut only Plasmid 2.\n"
" c. Enzymes that cut **both** plasmids but produce different fragment patterns.\n"
"3. Select which plasmid's **unique** enzymes you want to consider for plotting.\n"
"4. Choose a specific enzyme from the dropdown list.\n"
"5. Click `Generate Gel Plot` to visualize the digestion pattern for the selected plasmid and enzyme.\n"
f"Note: For 'Load Example Files', ensure `plasmid1_example.gb` and `plasmid2_example.gb` are in a folder named `{EXAMPLE_DIR}` next to this script."
)
plasmid1_data_state = gr.State()
plasmid2_data_state = gr.State()
p1_unique_enzymes_list_state = gr.State([])
p2_unique_enzymes_list_state = gr.State([])
# common_diff_enzymes_list_state = gr.State([]) # Not strictly needed as state if only displayed
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### 1. Upload Plasmids & Analyze")
file_p1 = gr.File(label="Plasmid 1 File (e.g., .gb, .fasta)", type="filepath", file_types=[".gb", ".gbk", ".fasta", ".fna", ".fa"])
file_p2 = gr.File(label="Plasmid 2 File (e.g., .gb, .fasta)", type="filepath", file_types=[".gb", ".gbk", ".fasta", ".fna", ".fa"])
_current_plasmid_choice_for_plot_hidden = gr.Textbox(value="Plasmid 1", visible=False) # For analysis logic with dropdown
analyze_btn = gr.Button("Analyze Uploaded Plasmids", variant="secondary")
example_btn = gr.Button("Load Example Files & Auto-Analyze/Plot", variant="primary", elem_id="example_button")
with gr.Column(scale=2):
gr.Markdown("### Analysis Results")
status_message_txt = gr.Textbox(label="Status", interactive=False, lines=1, max_lines=3)
unique_enzymes_p1_txt = gr.Textbox(label="Enzymes cutting only Plasmid 1", interactive=False, lines=3, max_lines=6)
unique_enzymes_p2_txt = gr.Textbox(label="Enzymes cutting only Plasmid 2", interactive=False, lines=3, max_lines=6)
common_diff_enzymes_txt = gr.Textbox(label="Enzymes cutting BOTH plasmids (different fragments)", interactive=False, lines=3, max_lines=6) # New Textbox
gr.Markdown("---")
gr.Markdown("### 2. Visualize Digestion on Virtual Gel")
with gr.Row():
with gr.Column(scale=1):
plasmid_to_plot_choice_radio = gr.Radio(
choices=["Plasmid 1", "Plasmid 2"],
label="Select Plasmid for Gel Visualization (of its unique enzymes)",
value="Plasmid 1",
interactive=True
)
enzyme_for_plot_dropdown = gr.Dropdown(
label="Select Unique Enzyme for Chosen Plasmid",
choices=["Analyze plasmids first"],
value="Analyze plasmids first",
interactive=False
)
plot_btn = gr.Button("Generate Gel Plot for Selection", variant="secondary", elem_id="plot_button")
with gr.Column(scale=2):
gel_plot_output = gr.Plot(label="Virtual Agarose Gel")
gr.Markdown("---")
gr.Markdown("Developed using Biopython, Matplotlib, and Gradio.")
gr.Markdown("Note: Large plasmid files or complex analyses might take a few moments.")
# --- Event Handlers ---
plasmid_to_plot_choice_radio.change(
fn=lambda x: x,
inputs=[plasmid_to_plot_choice_radio],
outputs=[_current_plasmid_choice_for_plot_hidden] # Keep this to inform analyze_plasmids_gradio logic for initial dd
)
analyze_btn.click(
fn=analyze_plasmids_gradio,
inputs=[file_p1, file_p2, _current_plasmid_choice_for_plot_hidden], # Pass the hidden value
outputs=[
status_message_txt, unique_enzymes_p1_txt, unique_enzymes_p2_txt,
common_diff_enzymes_txt, # New output for the new textbox
plasmid1_data_state, plasmid2_data_state,
p1_unique_enzymes_list_state, p2_unique_enzymes_list_state,
gr.State(), # Placeholder for common_diff_fragments_enzymes_names list (returned but not stored in state)
enzyme_for_plot_dropdown
]
)
example_btn.click(
fn=load_examples_and_auto_process,
inputs=[],
outputs=[
status_message_txt, unique_enzymes_p1_txt, unique_enzymes_p2_txt,
common_diff_enzymes_txt, # New output
plasmid1_data_state, plasmid2_data_state,
p1_unique_enzymes_list_state, p2_unique_enzymes_list_state,
enzyme_for_plot_dropdown,
plasmid_to_plot_choice_radio,
gel_plot_output
]
)
plasmid_to_plot_choice_radio.change(
fn=update_enzyme_dropdown_choices_on_radio_change,
inputs=[plasmid_to_plot_choice_radio, p1_unique_enzymes_list_state, p2_unique_enzymes_list_state],
outputs=[enzyme_for_plot_dropdown]
)
plot_btn.click(
fn=plot_selected_digest_controller,
inputs=[plasmid_to_plot_choice_radio, enzyme_for_plot_dropdown, plasmid1_data_state, plasmid2_data_state],
outputs=[gel_plot_output]
)
if __name__ == '__main__':
if not os.path.exists(EXAMPLE_DIR):
os.makedirs(EXAMPLE_DIR)
print(f"Created directory: {EXAMPLE_DIR}. Please add example plasmid files (plasmid1_example.gb, plasmid2_example.gb) to it for the example button to work.")
# Check for example files and print a message if they are missing
if not os.path.exists(EXAMPLE_PLASMID1_PATH) or not os.path.exists(EXAMPLE_PLASMID2_PATH):
print(f"Warning: Example files (plasmid1_example.gb, plasmid2_example.gb) not found in '{EXAMPLE_DIR}'. The 'Load Example Files' button might not work as expected.")
print("You can create dummy GenBank or FASTA files with these names for testing if needed.")
demo.launch()