alidenewade commited on
Commit
7a73738
·
verified ·
1 Parent(s): 99daaca

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +154 -89
app.py CHANGED
@@ -9,6 +9,11 @@ import os # For getting filename
9
  import matplotlib
10
  matplotlib.use('Agg')
11
 
 
 
 
 
 
12
  # --- Core BioPython and Plotting Functions ---
13
 
14
  def simulate_digest_and_plot_gradio(plasmid_seq_record, enzyme_name, plasmid_label):
@@ -18,9 +23,16 @@ def simulate_digest_and_plot_gradio(plasmid_seq_record, enzyme_name, plasmid_lab
18
  """
19
  fig, ax = plt.subplots(figsize=(6, 8)) # Adjusted size for better readability
20
 
 
 
 
 
 
 
 
 
21
  try:
22
- # enzyme_name is a string, get the Biopython enzyme object
23
- enzyme = AllEnzymes.get(str(enzyme_name)) # Ensure enzyme_name is string
24
  if not enzyme:
25
  raise ValueError(f"Enzyme '{enzyme_name}' not found in Biopython's AllEnzymes.")
26
  except Exception as e:
@@ -31,14 +43,10 @@ def simulate_digest_and_plot_gradio(plasmid_seq_record, enzyme_name, plasmid_lab
31
  plt.tight_layout()
32
  return fig
33
 
34
- # Use enzyme.catalyse() to get fragments directly
35
  fragments_seqs = enzyme.catalyse(plasmid_seq_record.seq)
36
 
37
  is_uncut = False
38
  if len(fragments_seqs) == 1 and len(fragments_seqs[0]) == len(plasmid_seq_record.seq):
39
- # Further check: does the enzyme actually have sites?
40
- # If catalyse returns the original sequence, it might be circular and cut once,
41
- # or linear and uncut, or truly no sites.
42
  if not enzyme.search(plasmid_seq_record.seq):
43
  is_uncut = True
44
 
@@ -46,24 +54,20 @@ def simulate_digest_and_plot_gradio(plasmid_seq_record, enzyme_name, plasmid_lab
46
  ax.text(0.5, 0.5, f"Enzyme {enzyme_name} does not cut {plasmid_label}",
47
  ha='center', va='center', wrap=True)
48
  ax.set_title(f"Virtual Gel: {plasmid_label} + {enzyme_name} (No Sites)", fontsize=10)
49
- # Still show the uncut plasmid band
50
  lengths = [len(plasmid_seq_record.seq)]
51
  else:
52
  lengths = sorted([len(f) for f in fragments_seqs], reverse=True)
53
 
54
  ax.set_yscale("log")
55
-
56
- min_display_size = 10 # bp
57
- plasmid_len_for_scale = max(len(plasmid_seq_record.seq), min_display_size * 10) # Ensure decent scale range
58
- # Ensure max_display_size is greater than min_display_size
59
  max_display_size = max(plasmid_len_for_scale * 1.1, min_display_size * 2)
60
-
61
  ax.set_ylim(min_display_size, max_display_size)
62
 
63
  band_width = 0.6
64
  lane_center = 0.5
65
 
66
- if not lengths: # Should not happen if is_uncut is handled
67
  ax.text(0.5, 0.5, "No fragments to display.", ha='center', va='center')
68
  else:
69
  for i, size in enumerate(lengths):
@@ -79,24 +83,20 @@ def simulate_digest_and_plot_gradio(plasmid_seq_record, enzyme_name, plasmid_lab
79
  ax.invert_yaxis()
80
  ax.set_title(f"Virtual Gel: {plasmid_label} digested with {enzyme_name}", fontsize=10)
81
  ax.set_ylabel("Fragment Size (bp)", fontsize=9)
82
- ax.set_xlabel("Lane 1", fontsize=9) # Indicate it's one lane
83
- ax.set_xticks([]) # No x-axis ticks for a single lane view
84
  ax.tick_params(axis='y', labelsize=8)
85
 
86
- # Draw well at the top (after y-axis inversion)
87
- # The y-axis top is effectively max_display_size after inversion.
88
- well_top_y = ax.get_ylim()[0] # This is the largest value on the y-axis (top of inverted plot)
89
-
90
- # Draw well slightly above the max data point or at the very top
91
- well_line_y = well_top_y * 1.01 # Position for the horizontal line of the well
92
- well_depth_y = well_top_y * 0.98 # Bottom of the well sides (relative depth)
93
 
94
  ax.plot([lane_center - band_width/1.5, lane_center + band_width/1.5], [well_line_y, well_line_y],
95
- linewidth=1.5, color='black') # Top line of well
96
  ax.plot([lane_center - band_width/1.5, lane_center - band_width/1.5], [well_line_y, well_depth_y],
97
- linewidth=1.5, color='black') # Left side of well
98
  ax.plot([lane_center + band_width/1.5, lane_center + band_width/1.5], [well_line_y, well_depth_y],
99
- linewidth=1.5, color='black') # Right side of well
100
 
101
  plt.tight_layout(pad=1.5)
102
  return fig
@@ -108,23 +108,31 @@ def analyze_plasmids_gradio(file1_path, file2_path, current_plasmid_choice_for_p
108
  and an update for the enzyme selection dropdown.
109
  """
110
  initial_enzyme_dd_update = gr.update(choices=["Analyze plasmids first"], value="Analyze plasmids first", interactive=False)
 
 
 
 
 
 
 
 
 
 
 
111
 
112
  if file1_path is None or file2_path is None:
113
- return "Error: Please upload both plasmid files.", "", "", None, None, [], [], initial_enzyme_dd_update
114
 
115
  try:
116
- # file_path is already a string (path to temp file) when type="filepath"
117
  def read_plasmid(filepath, filename_for_error):
118
  try:
119
  return SeqIO.read(filepath, "genbank")
120
- except Exception: # Broad exception for parsing
121
  try:
122
  return SeqIO.read(filepath, "fasta")
123
  except Exception as e_fasta:
124
- # More specific error message
125
  raise ValueError(f"Could not parse '{filename_for_error}'. Ensure it's a valid GenBank or FASTA file. Last error: {e_fasta}")
126
 
127
- # Get original filenames for messages
128
  p1_orig_filename = os.path.basename(file1_path)
129
  p2_orig_filename = os.path.basename(file2_path)
130
 
@@ -134,25 +142,19 @@ def analyze_plasmids_gradio(file1_path, file2_path, current_plasmid_choice_for_p
134
  except Exception as e:
135
  return str(e), "", "", None, None, [], [], initial_enzyme_dd_update
136
 
137
- # Filter for valid enzymes from AllEnzymes
138
- # Some entries in AllEnzymes might be None or lack necessary attributes
139
  valid_enzyme_objects = []
140
- for enz_name in AllEnzymes.elements(): # Iterate over names to get objects
141
  enzyme_obj = AllEnzymes.get(enz_name)
142
  if enzyme_obj and hasattr(enzyme_obj, 'site') and enzyme_obj.site is not None:
143
- # Further check if it's a real enzyme, not a category like 'Commercial'
144
  if hasattr(enzyme_obj, 'is_restriction') and enzyme_obj.is_restriction():
145
  valid_enzyme_objects.append(enzyme_obj)
146
- elif not hasattr(enzyme_obj, 'is_restriction'): # If it doesn't have this, assume it's a basic enzyme type
147
  valid_enzyme_objects.append(enzyme_obj)
148
 
149
-
150
  if not valid_enzyme_objects:
151
  return "Error: Could not load any restriction enzymes from Biopython.", "", "", None, None, [], [], initial_enzyme_dd_update
152
 
153
  enzymes_batch = RestrictionBatch(valid_enzyme_objects)
154
-
155
- # Assuming circular plasmids, common for this type of analysis
156
  analysis1 = Analysis(enzymes_batch, plasmid1_seq_rec.seq, linear=False)
157
  analysis2 = Analysis(enzymes_batch, plasmid2_seq_rec.seq, linear=False)
158
 
@@ -175,12 +177,10 @@ def analyze_plasmids_gradio(file1_path, file2_path, current_plasmid_choice_for_p
175
  if not unique_to_1_names and not unique_to_2_names:
176
  status += " No enzymes found that uniquely cut only one of the plasmids."
177
 
178
- # Determine initial choices for the enzyme dropdown based on current_plasmid_choice_for_plot
179
- # current_plasmid_choice_for_plot is "Plasmid 1" or "Plasmid 2"
180
  dd_choices = []
181
  if current_plasmid_choice_for_plot == "Plasmid 1":
182
  dd_choices = unique_to_1_names if unique_to_1_names else [f"No unique enzymes for {p1_display_label}"]
183
- else: # Plasmid 2
184
  dd_choices = unique_to_2_names if unique_to_2_names else [f"No unique enzymes for {p2_display_label}"]
185
 
186
  if (current_plasmid_choice_for_plot == "Plasmid 1" and unique_to_1_names) or \
@@ -203,7 +203,6 @@ def plot_selected_digest_controller(plasmid_choice_label, enzyme_name, p1_data,
203
  if not enzyme_name or enzyme_name == "Select an enzyme" or "No unique enzymes" in enzyme_name or "Analyze plasmids first" in enzyme_name:
204
  ax_placeholder.clear()
205
  ax_placeholder.text(0.5, 0.5, "Please select a valid plasmid and enzyme after analysis.", ha='center', va='center', wrap=True)
206
- ax_placeholder.set_xticks([]); ax_placeholder.set_yticks([])
207
  plt.tight_layout()
208
  return fig_placeholder
209
 
@@ -214,53 +213,110 @@ def plot_selected_digest_controller(plasmid_choice_label, enzyme_name, p1_data,
214
  if p1_data is None:
215
  ax_placeholder.clear()
216
  ax_placeholder.text(0.5, 0.5, "Plasmid 1 data not loaded. Please re-analyze.", ha='center', va='center', wrap=True, color='red')
217
- ax_placeholder.set_xticks([]); ax_placeholder.set_yticks([])
218
  plt.tight_layout()
219
  return fig_placeholder
220
  target_plasmid_rec = p1_data
221
  target_label = "Plasmid 1"
222
  if hasattr(p1_data, 'name') and p1_data.name: target_label += f" ({p1_data.name})"
223
  elif hasattr(p1_data, 'id') and p1_data.id: target_label += f" ({p1_data.id})"
224
-
225
-
226
  elif plasmid_choice_label == "Plasmid 2":
227
  if p2_data is None:
228
  ax_placeholder.clear()
229
  ax_placeholder.text(0.5, 0.5, "Plasmid 2 data not loaded. Please re-analyze.", ha='center', va='center', wrap=True, color='red')
230
- ax_placeholder.set_xticks([]); ax_placeholder.set_yticks([])
231
  plt.tight_layout()
232
  return fig_placeholder
233
  target_plasmid_rec = p2_data
234
  target_label = "Plasmid 2"
235
  if hasattr(p2_data, 'name') and p2_data.name: target_label += f" ({p2_data.name})"
236
  elif hasattr(p2_data, 'id') and p2_data.id: target_label += f" ({p2_data.id})"
237
-
238
- else: # Should not happen
239
  ax_placeholder.clear()
240
  ax_placeholder.text(0.5, 0.5, "Invalid plasmid selection.", ha='center', va='center', wrap=True, color='red')
241
- ax_placeholder.set_xticks([]); ax_placeholder.set_yticks([])
242
  plt.tight_layout()
243
  return fig_placeholder
244
 
245
  return simulate_digest_and_plot_gradio(target_plasmid_rec, enzyme_name, target_label)
246
 
247
  def update_enzyme_dropdown_choices_on_radio_change(plasmid_choice_label, p1_enzyme_names, p2_enzyme_names):
248
- """
249
- Updates the enzyme dropdown choices when the plasmid selection radio button changes.
250
- """
251
  if plasmid_choice_label == "Plasmid 1":
252
  choices = p1_enzyme_names if p1_enzyme_names else ["No unique enzymes for P1"]
253
- if p1_enzyme_names: # If there are actual enzymes
254
  return gr.update(choices=["Select an enzyme"] + choices, value="Select an enzyme", interactive=True)
255
- return gr.update(choices=choices, value=choices[0], interactive=False) # No unique enzymes, so not interactive
256
-
257
  elif plasmid_choice_label == "Plasmid 2":
258
  choices = p2_enzyme_names if p2_enzyme_names else ["No unique enzymes for P2"]
259
- if p2_enzyme_names: # If there are actual enzymes
260
  return gr.update(choices=["Select an enzyme"] + choices, value="Select an enzyme", interactive=True)
261
- return gr.update(choices=choices, value=choices[0], interactive=False) # No unique enzymes, so not interactive
262
-
263
- return gr.update(choices=[], value=None, interactive=False) # Fallback, should not be reached
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
 
265
 
266
  # --- Gradio Interface Definition ---
@@ -268,34 +324,33 @@ with gr.Blocks(theme=gr.themes.Default()) as demo:
268
  gr.Markdown("# Plasmid Restriction Digest Analyzer & Virtual Gel")
269
  gr.Markdown(
270
  "**Instructions:**\n"
271
- "1. Upload two plasmid sequence files (GenBank `.gb`/`.gbk` or FASTA `.fasta`/`.fna`/`.fa` format).\n"
272
- "2. Click `Analyze Plasmids`. Results will show enzymes that uniquely cut one plasmid but not the other.\n"
273
  "3. Select which plasmid's unique enzymes you want to consider for plotting.\n"
274
  "4. Choose a specific enzyme from the dropdown list.\n"
275
- "5. Click `Generate Gel Plot` to visualize the digestion pattern."
 
276
  )
277
 
278
- # States to store full plasmid SeqRecord objects and lists of unique enzyme names
279
  plasmid1_data_state = gr.State()
280
  plasmid2_data_state = gr.State()
281
- p1_unique_enzymes_list_state = gr.State([]) # Stores list of names for P1 unique enzymes
282
- p2_unique_enzymes_list_state = gr.State([]) # Stores list of names for P2 unique enzymes
283
 
284
  with gr.Row():
285
  with gr.Column(scale=1):
286
  gr.Markdown("### 1. Upload Plasmids & Analyze")
287
- file_p1 = gr.File(label="Plasmid 1 File", type="filepath", file_types=[".gb", ".gbk", ".fasta", ".fna", ".fa"])
288
- file_p2 = gr.File(label="Plasmid 2 File", type="filepath", file_types=[".gb", ".gbk", ".fasta", ".fna", ".fa"])
289
 
290
- # Hidden component to pass the current plasmid choice to the analysis function
291
- # This helps initialize the enzyme dropdown correctly after analysis
292
  _current_plasmid_choice_for_plot_hidden = gr.Textbox(value="Plasmid 1", visible=False)
293
 
294
- analyze_btn = gr.Button("Analyze Plasmids", variant="primary", elem_id="analyze_button")
 
295
 
296
  with gr.Column(scale=2):
297
  gr.Markdown("### Analysis Results")
298
- status_message_txt = gr.Textbox(label="Status", interactive=False, lines=1, max_lines=2)
299
  unique_enzymes_p1_txt = gr.Textbox(label="Enzymes cutting only Plasmid 1", interactive=False, lines=3, max_lines=6)
300
  unique_enzymes_p2_txt = gr.Textbox(label="Enzymes cutting only Plasmid 2", interactive=False, lines=3, max_lines=6)
301
 
@@ -307,7 +362,7 @@ with gr.Blocks(theme=gr.themes.Default()) as demo:
307
  plasmid_to_plot_choice_radio = gr.Radio(
308
  choices=["Plasmid 1", "Plasmid 2"],
309
  label="Select Plasmid for Gel Visualization",
310
- value="Plasmid 1", # Default choice
311
  interactive=True
312
  )
313
 
@@ -315,9 +370,9 @@ with gr.Blocks(theme=gr.themes.Default()) as demo:
315
  label="Select Unique Enzyme",
316
  choices=["Analyze plasmids first"],
317
  value="Analyze plasmids first",
318
- interactive=False # Initially not interactive until analysis is done
319
  )
320
- plot_btn = gr.Button("Generate Gel Plot", variant="primary", elem_id="plot_button")
321
 
322
  with gr.Column(scale=2):
323
  gel_plot_output = gr.Plot(label="Virtual Agarose Gel")
@@ -326,40 +381,43 @@ with gr.Blocks(theme=gr.themes.Default()) as demo:
326
  gr.Markdown("Developed using Biopython, Matplotlib, and Gradio.")
327
  gr.Markdown("Note: Large plasmid files or complex analyses might take a few moments.")
328
 
329
-
330
  # --- Event Handlers ---
331
-
332
- # Update the hidden textbox when radio button changes
333
  plasmid_to_plot_choice_radio.change(
334
  fn=lambda x: x,
335
  inputs=[plasmid_to_plot_choice_radio],
336
  outputs=[_current_plasmid_choice_for_plot_hidden]
337
  )
338
 
339
- # When Analyze button is clicked:
340
  analyze_btn.click(
341
  fn=analyze_plasmids_gradio,
342
- inputs=[file_p1, file_p2, _current_plasmid_choice_for_plot_hidden], # Pass current radio choice
343
  outputs=[
344
- status_message_txt,
345
- unique_enzymes_p1_txt,
346
- unique_enzymes_p2_txt,
347
- plasmid1_data_state, # Store full plasmid SeqRecord data
348
- plasmid2_data_state, # Store full plasmid SeqRecord data
349
- p1_unique_enzymes_list_state,
350
- p2_unique_enzymes_list_state,
351
- enzyme_for_plot_dropdown # Update dropdown based on analysis and radio choice
 
 
 
 
 
 
 
 
 
352
  ]
353
  )
354
 
355
- # When plasmid choice (Radio) changes AFTER analysis, update the enzyme dropdown:
356
  plasmid_to_plot_choice_radio.change(
357
  fn=update_enzyme_dropdown_choices_on_radio_change,
358
  inputs=[plasmid_to_plot_choice_radio, p1_unique_enzymes_list_state, p2_unique_enzymes_list_state],
359
  outputs=[enzyme_for_plot_dropdown]
360
  )
361
 
362
- # When Plot button is clicked:
363
  plot_btn.click(
364
  fn=plot_selected_digest_controller,
365
  inputs=[plasmid_to_plot_choice_radio, enzyme_for_plot_dropdown, plasmid1_data_state, plasmid2_data_state],
@@ -367,4 +425,11 @@ with gr.Blocks(theme=gr.themes.Default()) as demo:
367
  )
368
 
369
  if __name__ == '__main__':
 
 
 
 
 
 
 
370
  demo.launch()
 
9
  import matplotlib
10
  matplotlib.use('Agg')
11
 
12
+ # Define paths for example files
13
+ EXAMPLE_DIR = "eg_files"
14
+ EXAMPLE_PLASMID1_PATH = os.path.join(EXAMPLE_DIR, "plasmid1_example.gb")
15
+ EXAMPLE_PLASMID2_PATH = os.path.join(EXAMPLE_DIR, "plasmid2_example.gb")
16
+
17
  # --- Core BioPython and Plotting Functions ---
18
 
19
  def simulate_digest_and_plot_gradio(plasmid_seq_record, enzyme_name, plasmid_label):
 
23
  """
24
  fig, ax = plt.subplots(figsize=(6, 8)) # Adjusted size for better readability
25
 
26
+ if plasmid_seq_record is None:
27
+ ax.text(0.5, 0.5, f"Error: Plasmid data for '{plasmid_label}' is missing.",
28
+ ha='center', va='center', wrap=True, color='red')
29
+ ax.set_xticks([]); ax.set_yticks([])
30
+ ax.set_title(f"Virtual Gel: {plasmid_label} - Error", fontsize=10)
31
+ plt.tight_layout()
32
+ return fig
33
+
34
  try:
35
+ enzyme = AllEnzymes.get(str(enzyme_name))
 
36
  if not enzyme:
37
  raise ValueError(f"Enzyme '{enzyme_name}' not found in Biopython's AllEnzymes.")
38
  except Exception as e:
 
43
  plt.tight_layout()
44
  return fig
45
 
 
46
  fragments_seqs = enzyme.catalyse(plasmid_seq_record.seq)
47
 
48
  is_uncut = False
49
  if len(fragments_seqs) == 1 and len(fragments_seqs[0]) == len(plasmid_seq_record.seq):
 
 
 
50
  if not enzyme.search(plasmid_seq_record.seq):
51
  is_uncut = True
52
 
 
54
  ax.text(0.5, 0.5, f"Enzyme {enzyme_name} does not cut {plasmid_label}",
55
  ha='center', va='center', wrap=True)
56
  ax.set_title(f"Virtual Gel: {plasmid_label} + {enzyme_name} (No Sites)", fontsize=10)
 
57
  lengths = [len(plasmid_seq_record.seq)]
58
  else:
59
  lengths = sorted([len(f) for f in fragments_seqs], reverse=True)
60
 
61
  ax.set_yscale("log")
62
+ min_display_size = 10
63
+ plasmid_len_for_scale = max(len(plasmid_seq_record.seq), min_display_size * 10)
 
 
64
  max_display_size = max(plasmid_len_for_scale * 1.1, min_display_size * 2)
 
65
  ax.set_ylim(min_display_size, max_display_size)
66
 
67
  band_width = 0.6
68
  lane_center = 0.5
69
 
70
+ if not lengths:
71
  ax.text(0.5, 0.5, "No fragments to display.", ha='center', va='center')
72
  else:
73
  for i, size in enumerate(lengths):
 
83
  ax.invert_yaxis()
84
  ax.set_title(f"Virtual Gel: {plasmid_label} digested with {enzyme_name}", fontsize=10)
85
  ax.set_ylabel("Fragment Size (bp)", fontsize=9)
86
+ ax.set_xlabel("Lane 1", fontsize=9)
87
+ ax.set_xticks([])
88
  ax.tick_params(axis='y', labelsize=8)
89
 
90
+ well_top_y = ax.get_ylim()[0]
91
+ well_line_y = well_top_y * 1.01
92
+ well_depth_y = well_top_y * 0.98
 
 
 
 
93
 
94
  ax.plot([lane_center - band_width/1.5, lane_center + band_width/1.5], [well_line_y, well_line_y],
95
+ linewidth=1.5, color='black')
96
  ax.plot([lane_center - band_width/1.5, lane_center - band_width/1.5], [well_line_y, well_depth_y],
97
+ linewidth=1.5, color='black')
98
  ax.plot([lane_center + band_width/1.5, lane_center + band_width/1.5], [well_line_y, well_depth_y],
99
+ linewidth=1.5, color='black')
100
 
101
  plt.tight_layout(pad=1.5)
102
  return fig
 
108
  and an update for the enzyme selection dropdown.
109
  """
110
  initial_enzyme_dd_update = gr.update(choices=["Analyze plasmids first"], value="Analyze plasmids first", interactive=False)
111
+
112
+ # Check if example files exist if paths match example paths
113
+ example_file_error_msg = ""
114
+ if file1_path == EXAMPLE_PLASMID1_PATH and not os.path.exists(EXAMPLE_PLASMID1_PATH):
115
+ example_file_error_msg += f"Example file not found: {EXAMPLE_PLASMID1_PATH}. Please create it in the '{EXAMPLE_DIR}' directory.\n"
116
+ if file2_path == EXAMPLE_PLASMID2_PATH and not os.path.exists(EXAMPLE_PLASMID2_PATH):
117
+ example_file_error_msg += f"Example file not found: {EXAMPLE_PLASMID2_PATH}. Please create it in the '{EXAMPLE_DIR}' directory.\n"
118
+
119
+ if example_file_error_msg:
120
+ return example_file_error_msg, "", "", None, None, [], [], initial_enzyme_dd_update
121
+
122
 
123
  if file1_path is None or file2_path is None:
124
+ return "Error: Please upload or load both plasmid files.", "", "", None, None, [], [], initial_enzyme_dd_update
125
 
126
  try:
 
127
  def read_plasmid(filepath, filename_for_error):
128
  try:
129
  return SeqIO.read(filepath, "genbank")
130
+ except Exception:
131
  try:
132
  return SeqIO.read(filepath, "fasta")
133
  except Exception as e_fasta:
 
134
  raise ValueError(f"Could not parse '{filename_for_error}'. Ensure it's a valid GenBank or FASTA file. Last error: {e_fasta}")
135
 
 
136
  p1_orig_filename = os.path.basename(file1_path)
137
  p2_orig_filename = os.path.basename(file2_path)
138
 
 
142
  except Exception as e:
143
  return str(e), "", "", None, None, [], [], initial_enzyme_dd_update
144
 
 
 
145
  valid_enzyme_objects = []
146
+ for enz_name in AllEnzymes.elements():
147
  enzyme_obj = AllEnzymes.get(enz_name)
148
  if enzyme_obj and hasattr(enzyme_obj, 'site') and enzyme_obj.site is not None:
 
149
  if hasattr(enzyme_obj, 'is_restriction') and enzyme_obj.is_restriction():
150
  valid_enzyme_objects.append(enzyme_obj)
151
+ elif not hasattr(enzyme_obj, 'is_restriction'):
152
  valid_enzyme_objects.append(enzyme_obj)
153
 
 
154
  if not valid_enzyme_objects:
155
  return "Error: Could not load any restriction enzymes from Biopython.", "", "", None, None, [], [], initial_enzyme_dd_update
156
 
157
  enzymes_batch = RestrictionBatch(valid_enzyme_objects)
 
 
158
  analysis1 = Analysis(enzymes_batch, plasmid1_seq_rec.seq, linear=False)
159
  analysis2 = Analysis(enzymes_batch, plasmid2_seq_rec.seq, linear=False)
160
 
 
177
  if not unique_to_1_names and not unique_to_2_names:
178
  status += " No enzymes found that uniquely cut only one of the plasmids."
179
 
 
 
180
  dd_choices = []
181
  if current_plasmid_choice_for_plot == "Plasmid 1":
182
  dd_choices = unique_to_1_names if unique_to_1_names else [f"No unique enzymes for {p1_display_label}"]
183
+ else:
184
  dd_choices = unique_to_2_names if unique_to_2_names else [f"No unique enzymes for {p2_display_label}"]
185
 
186
  if (current_plasmid_choice_for_plot == "Plasmid 1" and unique_to_1_names) or \
 
203
  if not enzyme_name or enzyme_name == "Select an enzyme" or "No unique enzymes" in enzyme_name or "Analyze plasmids first" in enzyme_name:
204
  ax_placeholder.clear()
205
  ax_placeholder.text(0.5, 0.5, "Please select a valid plasmid and enzyme after analysis.", ha='center', va='center', wrap=True)
 
206
  plt.tight_layout()
207
  return fig_placeholder
208
 
 
213
  if p1_data is None:
214
  ax_placeholder.clear()
215
  ax_placeholder.text(0.5, 0.5, "Plasmid 1 data not loaded. Please re-analyze.", ha='center', va='center', wrap=True, color='red')
 
216
  plt.tight_layout()
217
  return fig_placeholder
218
  target_plasmid_rec = p1_data
219
  target_label = "Plasmid 1"
220
  if hasattr(p1_data, 'name') and p1_data.name: target_label += f" ({p1_data.name})"
221
  elif hasattr(p1_data, 'id') and p1_data.id: target_label += f" ({p1_data.id})"
 
 
222
  elif plasmid_choice_label == "Plasmid 2":
223
  if p2_data is None:
224
  ax_placeholder.clear()
225
  ax_placeholder.text(0.5, 0.5, "Plasmid 2 data not loaded. Please re-analyze.", ha='center', va='center', wrap=True, color='red')
 
226
  plt.tight_layout()
227
  return fig_placeholder
228
  target_plasmid_rec = p2_data
229
  target_label = "Plasmid 2"
230
  if hasattr(p2_data, 'name') and p2_data.name: target_label += f" ({p2_data.name})"
231
  elif hasattr(p2_data, 'id') and p2_data.id: target_label += f" ({p2_data.id})"
232
+ else:
 
233
  ax_placeholder.clear()
234
  ax_placeholder.text(0.5, 0.5, "Invalid plasmid selection.", ha='center', va='center', wrap=True, color='red')
 
235
  plt.tight_layout()
236
  return fig_placeholder
237
 
238
  return simulate_digest_and_plot_gradio(target_plasmid_rec, enzyme_name, target_label)
239
 
240
  def update_enzyme_dropdown_choices_on_radio_change(plasmid_choice_label, p1_enzyme_names, p2_enzyme_names):
 
 
 
241
  if plasmid_choice_label == "Plasmid 1":
242
  choices = p1_enzyme_names if p1_enzyme_names else ["No unique enzymes for P1"]
243
+ if p1_enzyme_names:
244
  return gr.update(choices=["Select an enzyme"] + choices, value="Select an enzyme", interactive=True)
245
+ return gr.update(choices=choices, value=choices[0], interactive=False)
 
246
  elif plasmid_choice_label == "Plasmid 2":
247
  choices = p2_enzyme_names if p2_enzyme_names else ["No unique enzymes for P2"]
248
+ if p2_enzyme_names:
249
  return gr.update(choices=["Select an enzyme"] + choices, value="Select an enzyme", interactive=True)
250
+ return gr.update(choices=choices, value=choices[0], interactive=False)
251
+ return gr.update(choices=[], value=None, interactive=False)
252
+
253
+
254
+ def load_examples_and_auto_process():
255
+ """
256
+ Loads example files, triggers analysis, and then attempts to auto-plot.
257
+ """
258
+ # Step 1: Perform analysis with example files
259
+ # Default to "Plasmid 1" for initial dropdown population logic within analyze_plasmids_gradio
260
+ status, msg1, msg2, p1_rec, p2_rec, p1_enz_names, p2_enz_names, enz_dd_update = \
261
+ analyze_plasmids_gradio(EXAMPLE_PLASMID1_PATH, EXAMPLE_PLASMID2_PATH, "Plasmid 1")
262
+
263
+ # If analysis failed (e.g., files not found), p1_rec or p2_rec might be None
264
+ if p1_rec is None or p2_rec is None :
265
+ # Create a placeholder plot for error
266
+ fig_error, ax_error = plt.subplots(figsize=(6,8))
267
+ ax_error.text(0.5, 0.5, "Error during example analysis.\nCheck file paths and content.", ha='center', va='center', color='red', wrap=True)
268
+ ax_error.set_xticks([]); ax_error.set_yticks([])
269
+ plt.tight_layout()
270
+ return status, msg1, msg2, p1_rec, p2_rec, p1_enz_names, p2_enz_names, \
271
+ gr.update(choices=["Error"], value="Error", interactive=False), \
272
+ gr.update(value="Plasmid 1"), fig_error # Default radio to P1, show error plot
273
+
274
+ # Step 2: Determine auto-plot parameters
275
+ auto_plot_plasmid_label = None
276
+ auto_plot_enzyme_name = None
277
+ auto_plot_plasmid_data = None
278
+ final_radio_choice = "Plasmid 1" # Default if P1 has unique enzymes
279
+
280
+ if p1_enz_names:
281
+ auto_plot_plasmid_label = "Plasmid 1"
282
+ auto_plot_enzyme_name = p1_enz_names[0]
283
+ auto_plot_plasmid_data = p1_rec
284
+ final_radio_choice = "Plasmid 1"
285
+ # Update enzyme dropdown for P1
286
+ enz_dd_update = gr.update(choices=["Select an enzyme"] + p1_enz_names, value=auto_plot_enzyme_name, interactive=True)
287
+
288
+ elif p2_enz_names:
289
+ auto_plot_plasmid_label = "Plasmid 2"
290
+ auto_plot_enzyme_name = p2_enz_names[0]
291
+ auto_plot_plasmid_data = p2_rec
292
+ final_radio_choice = "Plasmid 2"
293
+ # Update enzyme dropdown for P2
294
+ enz_dd_update = gr.update(choices=["Select an enzyme"] + p2_enz_names, value=auto_plot_enzyme_name, interactive=True)
295
+ else:
296
+ # No unique enzymes for auto-plotting, update dropdown to reflect current choice (P1 default)
297
+ if final_radio_choice == "Plasmid 1":
298
+ enz_dd_update = gr.update(choices=[f"No unique enzymes for Plasmid 1 ({os.path.basename(EXAMPLE_PLASMID1_PATH)})"], value=f"No unique enzymes for Plasmid 1 ({os.path.basename(EXAMPLE_PLASMID1_PATH)})", interactive=False)
299
+ # (No need to handle P2 here as P1 is checked first for default)
300
+
301
+
302
+ # Step 3: Generate plot if possible
303
+ if auto_plot_enzyme_name and auto_plot_plasmid_data:
304
+ gel_fig = simulate_digest_and_plot_gradio(auto_plot_plasmid_data, auto_plot_enzyme_name, auto_plot_plasmid_label)
305
+ else:
306
+ # Create a placeholder plot if no auto-plot target
307
+ fig_placeholder, ax_placeholder = plt.subplots(figsize=(6, 8))
308
+ ax_placeholder.text(0.5, 0.5, "No unique enzymes found for automatic plotting.", ha='center', va='center', wrap=True)
309
+ ax_placeholder.set_xticks([]); ax_placeholder.set_yticks([])
310
+ plt.tight_layout()
311
+ gel_fig = fig_placeholder
312
+ # Ensure dropdown reflects that no enzyme was selected for plotting
313
+ if not p1_enz_names and not p2_enz_names: # If truly no unique enzymes for either
314
+ enz_dd_update = gr.update(choices=["No unique enzymes found"], value="No unique enzymes found", interactive=False)
315
+
316
+
317
+ # Return all updates
318
+ return status, msg1, msg2, p1_rec, p2_rec, p1_enz_names, p2_enz_names, \
319
+ enz_dd_update, gr.update(value=final_radio_choice), gel_fig
320
 
321
 
322
  # --- Gradio Interface Definition ---
 
324
  gr.Markdown("# Plasmid Restriction Digest Analyzer & Virtual Gel")
325
  gr.Markdown(
326
  "**Instructions:**\n"
327
+ "1. Upload two plasmid sequence files (GenBank `.gb`/`.gbk` or FASTA `.fasta`/`.fna`/`.fa` format) OR click 'Load Example Files'.\n"
328
+ "2. If uploading manually, click `Analyze Plasmids`. Results will show enzymes that uniquely cut one plasmid but not the other.\n"
329
  "3. Select which plasmid's unique enzymes you want to consider for plotting.\n"
330
  "4. Choose a specific enzyme from the dropdown list.\n"
331
+ "5. Click `Generate Gel Plot` to visualize the digestion pattern.\n"
332
+ 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."
333
  )
334
 
 
335
  plasmid1_data_state = gr.State()
336
  plasmid2_data_state = gr.State()
337
+ p1_unique_enzymes_list_state = gr.State([])
338
+ p2_unique_enzymes_list_state = gr.State([])
339
 
340
  with gr.Row():
341
  with gr.Column(scale=1):
342
  gr.Markdown("### 1. Upload Plasmids & Analyze")
343
+ file_p1 = gr.File(label="Plasmid 1 File (e.g., .gb, .fasta)", type="filepath", file_types=[".gb", ".gbk", ".fasta", ".fna", ".fa"])
344
+ file_p2 = gr.File(label="Plasmid 2 File (e.g., .gb, .fasta)", type="filepath", file_types=[".gb", ".gbk", ".fasta", ".fna", ".fa"])
345
 
 
 
346
  _current_plasmid_choice_for_plot_hidden = gr.Textbox(value="Plasmid 1", visible=False)
347
 
348
+ analyze_btn = gr.Button("Analyze Uploaded Plasmids", variant="secondary") # Changed variant
349
+ example_btn = gr.Button("Load Example Files & Auto-Analyze/Plot", variant="primary", elem_id="example_button")
350
 
351
  with gr.Column(scale=2):
352
  gr.Markdown("### Analysis Results")
353
+ status_message_txt = gr.Textbox(label="Status", interactive=False, lines=1, max_lines=3) # Increased max_lines for error messages
354
  unique_enzymes_p1_txt = gr.Textbox(label="Enzymes cutting only Plasmid 1", interactive=False, lines=3, max_lines=6)
355
  unique_enzymes_p2_txt = gr.Textbox(label="Enzymes cutting only Plasmid 2", interactive=False, lines=3, max_lines=6)
356
 
 
362
  plasmid_to_plot_choice_radio = gr.Radio(
363
  choices=["Plasmid 1", "Plasmid 2"],
364
  label="Select Plasmid for Gel Visualization",
365
+ value="Plasmid 1",
366
  interactive=True
367
  )
368
 
 
370
  label="Select Unique Enzyme",
371
  choices=["Analyze plasmids first"],
372
  value="Analyze plasmids first",
373
+ interactive=False
374
  )
375
+ plot_btn = gr.Button("Generate Gel Plot for Selection", variant="secondary", elem_id="plot_button") # Changed variant
376
 
377
  with gr.Column(scale=2):
378
  gel_plot_output = gr.Plot(label="Virtual Agarose Gel")
 
381
  gr.Markdown("Developed using Biopython, Matplotlib, and Gradio.")
382
  gr.Markdown("Note: Large plasmid files or complex analyses might take a few moments.")
383
 
 
384
  # --- Event Handlers ---
 
 
385
  plasmid_to_plot_choice_radio.change(
386
  fn=lambda x: x,
387
  inputs=[plasmid_to_plot_choice_radio],
388
  outputs=[_current_plasmid_choice_for_plot_hidden]
389
  )
390
 
 
391
  analyze_btn.click(
392
  fn=analyze_plasmids_gradio,
393
+ inputs=[file_p1, file_p2, _current_plasmid_choice_for_plot_hidden],
394
  outputs=[
395
+ status_message_txt, unique_enzymes_p1_txt, unique_enzymes_p2_txt,
396
+ plasmid1_data_state, plasmid2_data_state,
397
+ p1_unique_enzymes_list_state, p2_unique_enzymes_list_state,
398
+ enzyme_for_plot_dropdown
399
+ ]
400
+ )
401
+
402
+ example_btn.click(
403
+ fn=load_examples_and_auto_process,
404
+ inputs=[], # No direct inputs, uses hardcoded paths
405
+ outputs=[
406
+ status_message_txt, unique_enzymes_p1_txt, unique_enzymes_p2_txt,
407
+ plasmid1_data_state, plasmid2_data_state,
408
+ p1_unique_enzymes_list_state, p2_unique_enzymes_list_state,
409
+ enzyme_for_plot_dropdown, # Update dropdown based on auto-selected enzyme
410
+ plasmid_to_plot_choice_radio, # Update radio based on auto-selected plasmid
411
+ gel_plot_output # Display the auto-generated plot
412
  ]
413
  )
414
 
 
415
  plasmid_to_plot_choice_radio.change(
416
  fn=update_enzyme_dropdown_choices_on_radio_change,
417
  inputs=[plasmid_to_plot_choice_radio, p1_unique_enzymes_list_state, p2_unique_enzymes_list_state],
418
  outputs=[enzyme_for_plot_dropdown]
419
  )
420
 
 
421
  plot_btn.click(
422
  fn=plot_selected_digest_controller,
423
  inputs=[plasmid_to_plot_choice_radio, enzyme_for_plot_dropdown, plasmid1_data_state, plasmid2_data_state],
 
425
  )
426
 
427
  if __name__ == '__main__':
428
+ # Create eg_files directory if it doesn't exist (optional, good for local testing)
429
+ if not os.path.exists(EXAMPLE_DIR):
430
+ os.makedirs(EXAMPLE_DIR)
431
+ print(f"Created directory: {EXAMPLE_DIR}. Please add example plasmid files to it.")
432
+ # You might want to add a check here to see if files exist and guide the user
433
+ # For Hugging Face Spaces, you'd typically upload the eg_files directory with the files.
434
+
435
  demo.launch()