alidenewade commited on
Commit
e923d87
·
verified ·
1 Parent(s): 6c2bd0a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +107 -67
app.py CHANGED
@@ -87,8 +87,8 @@ def simulate_digest_and_plot_gradio(plasmid_seq_record, enzyme_name, plasmid_lab
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],
@@ -103,12 +103,15 @@ def simulate_digest_and_plot_gradio(plasmid_seq_record, enzyme_name, plasmid_lab
103
 
104
  def analyze_plasmids_gradio(file1_path, file2_path, current_plasmid_choice_for_plot):
105
  """
106
- Analyzes two plasmid files to find unique restriction enzymes.
107
- Returns status messages, plasmid data, lists of unique enzyme names,
 
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):
@@ -117,11 +120,11 @@ def analyze_plasmids_gradio(file1_path, file2_path, current_plasmid_choice_for_p
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):
@@ -140,7 +143,7 @@ def analyze_plasmids_gradio(file1_path, file2_path, current_plasmid_choice_for_p
140
  plasmid2_seq_rec = read_plasmid(file2_path, p2_orig_filename)
141
 
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():
@@ -152,17 +155,17 @@ def analyze_plasmids_gradio(file1_path, file2_path, current_plasmid_choice_for_p
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
 
161
- enzymes_cutting_p1 = set(analysis1.with_sites().keys())
162
- enzymes_cutting_p2 = set(analysis2.with_sites().keys())
163
 
164
- unique_to_1_obj = sorted(list(enzymes_cutting_p1 - enzymes_cutting_p2), key=lambda e: str(e))
165
- unique_to_2_obj = sorted(list(enzymes_cutting_p2 - enzymes_cutting_p1), key=lambda e: str(e))
166
 
167
  unique_to_1_names = [str(e) for e in unique_to_1_obj]
168
  unique_to_2_names = [str(e) for e in unique_to_2_obj]
@@ -173,9 +176,32 @@ def analyze_plasmids_gradio(file1_path, file2_path, current_plasmid_choice_for_p
173
  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}."
174
  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}."
175
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  status = "Analysis complete."
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":
@@ -185,11 +211,12 @@ def analyze_plasmids_gradio(file1_path, file2_path, current_plasmid_choice_for_p
185
 
186
  if (current_plasmid_choice_for_plot == "Plasmid 1" and unique_to_1_names) or \
187
  (current_plasmid_choice_for_plot == "Plasmid 2" and unique_to_2_names):
188
- initial_enzyme_dd_update = gr.update(choices=["Select an enzyme"] + dd_choices, value="Select an enzyme", interactive=True)
189
  else:
190
- initial_enzyme_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)
191
 
192
- return status, msg1, msg2, plasmid1_seq_rec, plasmid2_seq_rec, unique_to_1_names, unique_to_2_names, initial_enzyme_dd_update
 
193
 
194
  def plot_selected_digest_controller(plasmid_choice_label, enzyme_name, p1_data, p2_data):
195
  """
@@ -217,8 +244,8 @@ def plot_selected_digest_controller(plasmid_choice_label, enzyme_name, p1_data,
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()
@@ -227,8 +254,8 @@ def plot_selected_digest_controller(plasmid_choice_label, enzyme_name, p1_data,
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')
@@ -256,67 +283,70 @@ def load_examples_and_auto_process():
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 ---
@@ -325,10 +355,13 @@ with gr.Blocks(theme=gr.themes.Default()) as demo:
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
 
@@ -336,6 +369,7 @@ with gr.Blocks(theme=gr.themes.Default()) as demo:
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):
@@ -343,16 +377,17 @@ with gr.Blocks(theme=gr.themes.Default()) as demo:
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
 
357
  gr.Markdown("---")
358
  gr.Markdown("### 2. Visualize Digestion on Virtual Gel")
@@ -361,18 +396,18 @@ with gr.Blocks(theme=gr.themes.Default()) as demo:
361
  with gr.Column(scale=1):
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
 
369
  enzyme_for_plot_dropdown = gr.Dropdown(
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")
@@ -385,30 +420,33 @@ with gr.Blocks(theme=gr.themes.Default()) as demo:
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
 
@@ -425,11 +463,13 @@ with gr.Blocks(theme=gr.themes.Default()) as demo:
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()
 
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],
 
103
 
104
  def analyze_plasmids_gradio(file1_path, file2_path, current_plasmid_choice_for_plot):
105
  """
106
+ Analyzes two plasmid files to find unique restriction enzymes and
107
+ enzymes that cut both plasmids but with different fragmentation patterns.
108
+ Returns status messages, plasmid data, lists of enzyme names,
109
  and an update for the enzyme selection dropdown.
110
  """
111
  initial_enzyme_dd_update = gr.update(choices=["Analyze plasmids first"], value="Analyze plasmids first", interactive=False)
112
+ empty_return_for_error = ["", "", "", None, None, [], [], [], initial_enzyme_dd_update]
113
+
114
+
115
  # Check if example files exist if paths match example paths
116
  example_file_error_msg = ""
117
  if file1_path == EXAMPLE_PLASMID1_PATH and not os.path.exists(EXAMPLE_PLASMID1_PATH):
 
120
  example_file_error_msg += f"Example file not found: {EXAMPLE_PLASMID2_PATH}. Please create it in the '{EXAMPLE_DIR}' directory.\n"
121
 
122
  if example_file_error_msg:
123
+ return example_file_error_msg, *empty_return_for_error
124
 
125
 
126
  if file1_path is None or file2_path is None:
127
+ return "Error: Please upload or load both plasmid files.", *empty_return_for_error
128
 
129
  try:
130
  def read_plasmid(filepath, filename_for_error):
 
143
  plasmid2_seq_rec = read_plasmid(file2_path, p2_orig_filename)
144
 
145
  except Exception as e:
146
+ return str(e), *empty_return_for_error
147
 
148
  valid_enzyme_objects = []
149
  for enz_name in AllEnzymes.elements():
 
155
  valid_enzyme_objects.append(enzyme_obj)
156
 
157
  if not valid_enzyme_objects:
158
+ return "Error: Could not load any restriction enzymes from Biopython.", *empty_return_for_error
159
 
160
  enzymes_batch = RestrictionBatch(valid_enzyme_objects)
161
  analysis1 = Analysis(enzymes_batch, plasmid1_seq_rec.seq, linear=False)
162
  analysis2 = Analysis(enzymes_batch, plasmid2_seq_rec.seq, linear=False)
163
 
164
+ enzymes_cutting_p1_obj = set(analysis1.with_sites().keys())
165
+ enzymes_cutting_p2_obj = set(analysis2.with_sites().keys())
166
 
167
+ unique_to_1_obj = sorted(list(enzymes_cutting_p1_obj - enzymes_cutting_p2_obj), key=lambda e: str(e))
168
+ unique_to_2_obj = sorted(list(enzymes_cutting_p2_obj - enzymes_cutting_p1_obj), key=lambda e: str(e))
169
 
170
  unique_to_1_names = [str(e) for e in unique_to_1_obj]
171
  unique_to_2_names = [str(e) for e in unique_to_2_obj]
 
176
  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}."
177
  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}."
178
 
179
+ # New: Find enzymes cutting both but with different fragments
180
+ common_enzymes_obj = enzymes_cutting_p1_obj.intersection(enzymes_cutting_p2_obj)
181
+ common_diff_fragments_enzymes_names = []
182
+ for enzyme_obj in common_enzymes_obj:
183
+ try:
184
+ fragments1_seqs = enzyme_obj.catalyse(plasmid1_seq_rec.seq)
185
+ fragments2_seqs = enzyme_obj.catalyse(plasmid2_seq_rec.seq)
186
+
187
+ lengths1 = sorted([len(f) for f in fragments1_seqs])
188
+ lengths2 = sorted([len(f) for f in fragments2_seqs])
189
+
190
+ if lengths1 != lengths2:
191
+ common_diff_fragments_enzymes_names.append(str(enzyme_obj))
192
+ except Exception as e_cat_common:
193
+ print(f"Warning: Error during catalysis comparison for common enzyme {str(enzyme_obj)}: {e_cat_common}")
194
+ # Optionally skip this enzyme or log more formally
195
+
196
+ common_diff_fragments_enzymes_names = sorted(list(set(common_diff_fragments_enzymes_names)))
197
+
198
+ msg_common_diff = f"Enzymes cutting BOTH plasmids with DIFFERENT fragments ({len(common_diff_fragments_enzymes_names)}):\n" + \
199
+ ", ".join(common_diff_fragments_enzymes_names) if common_diff_fragments_enzymes_names \
200
+ else "No enzymes found that cut both plasmids with different fragment patterns."
201
+
202
  status = "Analysis complete."
203
+ if not unique_to_1_names and not unique_to_2_names and not common_diff_fragments_enzymes_names:
204
+ status += " No differentiating enzymes found."
205
 
206
  dd_choices = []
207
  if current_plasmid_choice_for_plot == "Plasmid 1":
 
211
 
212
  if (current_plasmid_choice_for_plot == "Plasmid 1" and unique_to_1_names) or \
213
  (current_plasmid_choice_for_plot == "Plasmid 2" and unique_to_2_names):
214
+ current_dd_update = gr.update(choices=["Select an enzyme"] + dd_choices, value="Select an enzyme", interactive=True)
215
  else:
216
+ 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)
217
 
218
+ return status, msg1, msg2, msg_common_diff, plasmid1_seq_rec, plasmid2_seq_rec, \
219
+ unique_to_1_names, unique_to_2_names, common_diff_fragments_enzymes_names, current_dd_update
220
 
221
  def plot_selected_digest_controller(plasmid_choice_label, enzyme_name, p1_data, p2_data):
222
  """
 
244
  return fig_placeholder
245
  target_plasmid_rec = p1_data
246
  target_label = "Plasmid 1"
247
+ if hasattr(p1_data, 'name') and p1_data.name and p1_data.name !="<unknown name>": target_label += f" ({p1_data.name})"
248
+ elif hasattr(p1_data, 'id') and p1_data.id and p1_data.id !="<unknown id>": target_label += f" ({p1_data.id})"
249
  elif plasmid_choice_label == "Plasmid 2":
250
  if p2_data is None:
251
  ax_placeholder.clear()
 
254
  return fig_placeholder
255
  target_plasmid_rec = p2_data
256
  target_label = "Plasmid 2"
257
+ if hasattr(p2_data, 'name') and p2_data.name and p2_data.name !="<unknown name>": target_label += f" ({p2_data.name})"
258
+ elif hasattr(p2_data, 'id') and p2_data.id and p2_data.id !="<unknown id>": target_label += f" ({p2_data.id})"
259
  else:
260
  ax_placeholder.clear()
261
  ax_placeholder.text(0.5, 0.5, "Invalid plasmid selection.", ha='center', va='center', wrap=True, color='red')
 
283
  Loads example files, triggers analysis, and then attempts to auto-plot.
284
  """
285
  # Step 1: Perform analysis with example files
286
+ status, msg1, msg2, msg_common_diff, p1_rec, p2_rec, p1_enz_names, p2_enz_names, \
287
+ _common_diff_list_ignore, enz_dd_update_from_analysis = \
288
  analyze_plasmids_gradio(EXAMPLE_PLASMID1_PATH, EXAMPLE_PLASMID2_PATH, "Plasmid 1")
289
 
290
  # If analysis failed (e.g., files not found), p1_rec or p2_rec might be None
291
  if p1_rec is None or p2_rec is None :
 
292
  fig_error, ax_error = plt.subplots(figsize=(6,8))
293
+ 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
294
  ax_error.set_xticks([]); ax_error.set_yticks([])
295
  plt.tight_layout()
296
+ # Ensure all expected output values are provided
297
+ return status, msg1, msg2, msg_common_diff, None, None, [], [], \
298
  gr.update(choices=["Error"], value="Error", interactive=False), \
299
+ gr.update(value="Plasmid 1"), fig_error
300
 
301
+ # Step 2: Determine auto-plot parameters and update dropdown based on analysis
302
  auto_plot_plasmid_label = None
303
  auto_plot_enzyme_name = None
304
  auto_plot_plasmid_data = None
305
+ final_radio_choice = "Plasmid 1" # Default
306
+ final_enz_dd_update = enz_dd_update_from_analysis # Use directly from analysis initially
307
 
308
  if p1_enz_names:
309
  auto_plot_plasmid_label = "Plasmid 1"
310
  auto_plot_enzyme_name = p1_enz_names[0]
311
  auto_plot_plasmid_data = p1_rec
312
  final_radio_choice = "Plasmid 1"
313
+ final_enz_dd_update = gr.update(choices=["Select an enzyme"] + p1_enz_names, value=auto_plot_enzyme_name, interactive=True)
 
 
314
  elif p2_enz_names:
315
  auto_plot_plasmid_label = "Plasmid 2"
316
  auto_plot_enzyme_name = p2_enz_names[0]
317
  auto_plot_plasmid_data = p2_rec
318
  final_radio_choice = "Plasmid 2"
319
+ final_enz_dd_update = gr.update(choices=["Select an enzyme"] + p2_enz_names, value=auto_plot_enzyme_name, interactive=True)
320
+ else: # No unique enzymes for either, dropdown should reflect current radio choice (P1 default from analyze call)
 
 
321
  if final_radio_choice == "Plasmid 1":
322
+ final_enz_dd_update = gr.update(choices=[f"No unique enzymes for P1 ({os.path.basename(EXAMPLE_PLASMID1_PATH)})"],
323
+ value=f"No unique enzymes for P1 ({os.path.basename(EXAMPLE_PLASMID1_PATH)})", interactive=False)
324
+ # No explicit else for P2 needed here as P1 is the default for initial dropdown population
325
 
326
  # Step 3: Generate plot if possible
327
  if auto_plot_enzyme_name and auto_plot_plasmid_data:
328
+ # Use the actual name from p1_data or p2_data for the label if available
329
+ plot_label_detail = ""
330
+ if auto_plot_plasmid_label == "Plasmid 1":
331
+ if hasattr(p1_rec, 'name') and p1_rec.name and p1_rec.name != "<unknown name>": plot_label_detail = f" ({p1_rec.name})"
332
+ elif hasattr(p1_rec, 'id') and p1_rec.id and p1_rec.id != "<unknown id>": plot_label_detail = f" ({p1_rec.id})"
333
+ elif auto_plot_plasmid_label == "Plasmid 2":
334
+ if hasattr(p2_rec, 'name') and p2_rec.name and p2_rec.name != "<unknown name>": plot_label_detail = f" ({p2_rec.name})"
335
+ elif hasattr(p2_rec, 'id') and p2_rec.id and p2_rec.id != "<unknown id>": plot_label_detail = f" ({p2_rec.id})"
336
+
337
+ gel_fig = simulate_digest_and_plot_gradio(auto_plot_plasmid_data, auto_plot_enzyme_name, f"{auto_plot_plasmid_label}{plot_label_detail}")
338
  else:
 
339
  fig_placeholder, ax_placeholder = plt.subplots(figsize=(6, 8))
340
  ax_placeholder.text(0.5, 0.5, "No unique enzymes found for automatic plotting.", ha='center', va='center', wrap=True)
341
  ax_placeholder.set_xticks([]); ax_placeholder.set_yticks([])
342
  plt.tight_layout()
343
  gel_fig = fig_placeholder
344
+ if not p1_enz_names and not p2_enz_names:
345
+ final_enz_dd_update = gr.update(choices=["No unique enzymes found"], value="No unique enzymes found", interactive=False)
 
 
346
 
347
  # Return all updates
348
+ return status, msg1, msg2, msg_common_diff, p1_rec, p2_rec, p1_enz_names, p2_enz_names, \
349
+ final_enz_dd_update, gr.update(value=final_radio_choice), gel_fig
350
 
351
 
352
  # --- Gradio Interface Definition ---
 
355
  gr.Markdown(
356
  "**Instructions:**\n"
357
  "1. Upload two plasmid sequence files (GenBank `.gb`/`.gbk` or FASTA `.fasta`/`.fna`/`.fa` format) OR click 'Load Example Files'.\n"
358
+ "2. If uploading manually, click `Analyze Uploaded Plasmids`. Results will show:\n"
359
+ " a. Enzymes that uniquely cut only Plasmid 1.\n"
360
+ " b. Enzymes that uniquely cut only Plasmid 2.\n"
361
+ " c. Enzymes that cut **both** plasmids but produce different fragment patterns.\n"
362
+ "3. Select which plasmid's **unique** enzymes you want to consider for plotting.\n"
363
  "4. Choose a specific enzyme from the dropdown list.\n"
364
+ "5. Click `Generate Gel Plot` to visualize the digestion pattern for the selected plasmid and enzyme.\n"
365
  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."
366
  )
367
 
 
369
  plasmid2_data_state = gr.State()
370
  p1_unique_enzymes_list_state = gr.State([])
371
  p2_unique_enzymes_list_state = gr.State([])
372
+ # common_diff_enzymes_list_state = gr.State([]) # Not strictly needed as state if only displayed
373
 
374
  with gr.Row():
375
  with gr.Column(scale=1):
 
377
  file_p1 = gr.File(label="Plasmid 1 File (e.g., .gb, .fasta)", type="filepath", file_types=[".gb", ".gbk", ".fasta", ".fna", ".fa"])
378
  file_p2 = gr.File(label="Plasmid 2 File (e.g., .gb, .fasta)", type="filepath", file_types=[".gb", ".gbk", ".fasta", ".fna", ".fa"])
379
 
380
+ _current_plasmid_choice_for_plot_hidden = gr.Textbox(value="Plasmid 1", visible=False) # For analysis logic with dropdown
381
 
382
+ analyze_btn = gr.Button("Analyze Uploaded Plasmids", variant="secondary")
383
  example_btn = gr.Button("Load Example Files & Auto-Analyze/Plot", variant="primary", elem_id="example_button")
384
 
385
  with gr.Column(scale=2):
386
  gr.Markdown("### Analysis Results")
387
+ status_message_txt = gr.Textbox(label="Status", interactive=False, lines=1, max_lines=3)
388
  unique_enzymes_p1_txt = gr.Textbox(label="Enzymes cutting only Plasmid 1", interactive=False, lines=3, max_lines=6)
389
  unique_enzymes_p2_txt = gr.Textbox(label="Enzymes cutting only Plasmid 2", interactive=False, lines=3, max_lines=6)
390
+ common_diff_enzymes_txt = gr.Textbox(label="Enzymes cutting BOTH plasmids (different fragments)", interactive=False, lines=3, max_lines=6) # New Textbox
391
 
392
  gr.Markdown("---")
393
  gr.Markdown("### 2. Visualize Digestion on Virtual Gel")
 
396
  with gr.Column(scale=1):
397
  plasmid_to_plot_choice_radio = gr.Radio(
398
  choices=["Plasmid 1", "Plasmid 2"],
399
+ label="Select Plasmid for Gel Visualization (of its unique enzymes)",
400
  value="Plasmid 1",
401
  interactive=True
402
  )
403
 
404
  enzyme_for_plot_dropdown = gr.Dropdown(
405
+ label="Select Unique Enzyme for Chosen Plasmid",
406
  choices=["Analyze plasmids first"],
407
  value="Analyze plasmids first",
408
  interactive=False
409
  )
410
+ plot_btn = gr.Button("Generate Gel Plot for Selection", variant="secondary", elem_id="plot_button")
411
 
412
  with gr.Column(scale=2):
413
  gel_plot_output = gr.Plot(label="Virtual Agarose Gel")
 
420
  plasmid_to_plot_choice_radio.change(
421
  fn=lambda x: x,
422
  inputs=[plasmid_to_plot_choice_radio],
423
+ outputs=[_current_plasmid_choice_for_plot_hidden] # Keep this to inform analyze_plasmids_gradio logic for initial dd
424
  )
425
 
426
  analyze_btn.click(
427
  fn=analyze_plasmids_gradio,
428
+ inputs=[file_p1, file_p2, _current_plasmid_choice_for_plot_hidden], # Pass the hidden value
429
  outputs=[
430
  status_message_txt, unique_enzymes_p1_txt, unique_enzymes_p2_txt,
431
+ common_diff_enzymes_txt, # New output for the new textbox
432
  plasmid1_data_state, plasmid2_data_state,
433
  p1_unique_enzymes_list_state, p2_unique_enzymes_list_state,
434
+ gr.State(), # Placeholder for common_diff_fragments_enzymes_names list (returned but not stored in state)
435
  enzyme_for_plot_dropdown
436
  ]
437
  )
438
 
439
  example_btn.click(
440
  fn=load_examples_and_auto_process,
441
+ inputs=[],
442
  outputs=[
443
  status_message_txt, unique_enzymes_p1_txt, unique_enzymes_p2_txt,
444
+ common_diff_enzymes_txt, # New output
445
  plasmid1_data_state, plasmid2_data_state,
446
  p1_unique_enzymes_list_state, p2_unique_enzymes_list_state,
447
+ enzyme_for_plot_dropdown,
448
+ plasmid_to_plot_choice_radio,
449
+ gel_plot_output
450
  ]
451
  )
452
 
 
463
  )
464
 
465
  if __name__ == '__main__':
 
466
  if not os.path.exists(EXAMPLE_DIR):
467
  os.makedirs(EXAMPLE_DIR)
468
+ 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.")
469
+
470
+ # Check for example files and print a message if they are missing
471
+ if not os.path.exists(EXAMPLE_PLASMID1_PATH) or not os.path.exists(EXAMPLE_PLASMID2_PATH):
472
+ 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.")
473
+ print("You can create dummy GenBank or FASTA files with these names for testing if needed.")
474
 
475
  demo.launch()