Spaces:
No application file
No application file
Update app.py
Browse files
app.py
CHANGED
|
@@ -217,28 +217,19 @@ def cleanup_file(file_path: str) -> None:
|
|
| 217 |
logger.warning(f"Failed to clean up {file_path}: {cleanup_error}")
|
| 218 |
|
| 219 |
def phylogenetic_placement(sequence: str, mafft_cmd: str, iqtree_cmd: str):
|
| 220 |
-
query_fasta = None
|
| 221 |
try:
|
| 222 |
-
# Input validation
|
| 223 |
if len(sequence.strip()) < 100:
|
| 224 |
return False, "Sequence too short (<100 bp).", None, None
|
| 225 |
-
|
| 226 |
-
# Setup file paths
|
| 227 |
query_id = f"QUERY_{uuid.uuid4().hex[:8]}"
|
| 228 |
query_fasta = os.path.join(QUERY_OUTPUT_DIR, f"{query_id}.fa")
|
| 229 |
aligned_with_query = os.path.join(QUERY_OUTPUT_DIR, f"{query_id}_aligned.fa")
|
| 230 |
output_prefix = os.path.join(QUERY_OUTPUT_DIR, f"{query_id}_placed_tree")
|
| 231 |
-
|
| 232 |
-
# Check reference files
|
| 233 |
if not os.path.exists(ALIGNMENT_PATH) or not os.path.exists(TREE_PATH):
|
| 234 |
cleanup_file(query_fasta)
|
| 235 |
return False, "Reference alignment or tree not found.", None, None
|
| 236 |
-
|
| 237 |
-
# Write query FASTA
|
| 238 |
query_record = SeqRecord(Seq(sequence.upper()), id=query_id, description="")
|
| 239 |
SeqIO.write([query_record], query_fasta, "fasta")
|
| 240 |
-
|
| 241 |
-
# Run MAFFT
|
| 242 |
with open(aligned_with_query, "w") as output_file:
|
| 243 |
result = subprocess.run(
|
| 244 |
[mafft_cmd, "--add", query_fasta, "--reorder", ALIGNMENT_PATH],
|
|
@@ -251,8 +242,6 @@ def phylogenetic_placement(sequence: str, mafft_cmd: str, iqtree_cmd: str):
|
|
| 251 |
if not os.path.exists(aligned_with_query) or os.path.getsize(aligned_with_query) == 0:
|
| 252 |
cleanup_file(query_fasta)
|
| 253 |
return False, "MAFFT alignment failed.", None, None
|
| 254 |
-
|
| 255 |
-
# Run IQ-TREE
|
| 256 |
result = subprocess.run(
|
| 257 |
[iqtree_cmd, "-s", aligned_with_query, "-g", TREE_PATH, "-m", "GTR+G", "-pre", output_prefix, "-redo"],
|
| 258 |
capture_output=True,
|
|
@@ -264,16 +253,14 @@ def phylogenetic_placement(sequence: str, mafft_cmd: str, iqtree_cmd: str):
|
|
| 264 |
if not os.path.exists(treefile):
|
| 265 |
cleanup_file(query_fasta)
|
| 266 |
return False, "IQ-TREE placement failed.", aligned_with_query, None
|
| 267 |
-
|
| 268 |
-
# Success
|
| 269 |
success_msg = f"Placement completed!\nQuery ID: {query_id}\nAlignment: {os.path.basename(aligned_with_query)}\nTree: {os.path.basename(treefile)}"
|
| 270 |
cleanup_file(query_fasta)
|
| 271 |
return True, success_msg, aligned_with_query, treefile
|
| 272 |
-
|
| 273 |
except Exception as main_error:
|
| 274 |
logger.error(f"Phylogenetic placement failed: {main_error}", exc_info=True)
|
| 275 |
cleanup_file(query_fasta)
|
| 276 |
return False, f"Error: {str(main_error)}", None, None
|
|
|
|
| 277 |
def analyze_sequence_for_tree(sequence: str, matching_percentage: float):
|
| 278 |
try:
|
| 279 |
logger.debug("Starting tree analysis...")
|
|
@@ -453,11 +440,12 @@ async def run_pipeline_from_file(fasta_file_obj, similarity_score, build_ml_tree
|
|
| 453 |
result = run_pipeline(dna_input, similarity_score, build_ml_tree)
|
| 454 |
cleanup_file(temp_file_path)
|
| 455 |
return result
|
| 456 |
-
except Exception as main_error:
|
| 457 |
logger.error(f"Pipeline from file error: {main_error}", exc_info=True)
|
| 458 |
cleanup_file(temp_file_path)
|
| 459 |
error_msg = f"❌ Error: {str(main_error)}"
|
| 460 |
return error_msg, "", "", "", "", None, None, None, None, error_msg, error_msg, None, None
|
|
|
|
| 461 |
class AnalysisRequest(BaseModel):
|
| 462 |
sequence: str
|
| 463 |
similarity_score: float = 95.0
|
|
@@ -537,7 +525,7 @@ async def analyze_sequence(request: AnalysisRequest):
|
|
| 537 |
success=False, error_message=str(e)
|
| 538 |
)
|
| 539 |
|
| 540 |
-
@app.post("/analyze-file")
|
| 541 |
async def analyze_file(
|
| 542 |
file: UploadFile = File(...),
|
| 543 |
similarity_score: float = Form(95.0),
|
|
@@ -550,11 +538,7 @@ async def analyze_file(
|
|
| 550 |
temp_file.write(content)
|
| 551 |
temp_file_path = temp_file.name
|
| 552 |
result = await run_pipeline_from_file(temp_file_path, similarity_score, build_ml_tree)
|
| 553 |
-
|
| 554 |
-
try:
|
| 555 |
-
os.unlink(temp_file_path)
|
| 556 |
-
except Exception as cleanup_error:
|
| 557 |
-
logger.warning(f"Failed to clean up {temp_file_path}: {cleanup_error}")
|
| 558 |
return AnalysisResponse(
|
| 559 |
boundary_output=result[0] or "",
|
| 560 |
keras_output=result[1] or "",
|
|
@@ -565,20 +549,16 @@ async def analyze_file(
|
|
| 565 |
report_html_path=result[12],
|
| 566 |
success=True
|
| 567 |
)
|
| 568 |
-
except Exception as main_error:
|
| 569 |
logger.error(f"Analyze-file error: {main_error}", exc_info=True)
|
| 570 |
-
|
| 571 |
-
try:
|
| 572 |
-
os.unlink(temp_file_path)
|
| 573 |
-
except Exception as cleanup_error:
|
| 574 |
-
logger.warning(f"Failed to clean up {temp_file_path}: {cleanup_error}")
|
| 575 |
return AnalysisResponse(
|
| 576 |
boundary_output="", keras_output="", ml_tree_output="",
|
| 577 |
tree_analysis_output="", summary_output="",
|
| 578 |
tree_html_path=None, report_html_path=None,
|
| 579 |
success=False, error_message=str(main_error)
|
| 580 |
)
|
| 581 |
-
|
| 582 |
@app.get("/download/{file_type}/{query_id}")
|
| 583 |
async def download_file(file_type: str, query_id: str):
|
| 584 |
try:
|
|
@@ -779,21 +759,48 @@ def create_gradio_interface():
|
|
| 779 |
outputs=gr.Textbox(label="Error"),
|
| 780 |
title="🧬 Gene Analysis Pipeline (Error Mode)"
|
| 781 |
)
|
|
|
|
| 782 |
# --- Application Startup ---
|
| 783 |
def run_application():
|
| 784 |
try:
|
| 785 |
main_gradio_app = create_gradio_interface()
|
| 786 |
main_gradio_app = gr.mount_gradio_app(app, main_gradio_app, path="/gradio")
|
| 787 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 788 |
except Exception as main_error:
|
| 789 |
logger.error(f"Application startup failed: {main_error}", exc_info=True)
|
| 790 |
try:
|
| 791 |
logger.info("🔄 Falling back to Gradio-only mode...")
|
| 792 |
fallback_gradio_app = create_gradio_interface()
|
| 793 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 794 |
except Exception as fallback_error:
|
| 795 |
logger.error(f"Fallback failed: {fallback_error}", exc_info=True)
|
| 796 |
print("❌ Application failed to start. Check logs for details.")
|
|
|
|
| 797 |
if __name__ == "__main__":
|
| 798 |
print("🧬 Gene Analysis Pipeline Starting...")
|
| 799 |
print("=" * 50)
|
|
|
|
| 217 |
logger.warning(f"Failed to clean up {file_path}: {cleanup_error}")
|
| 218 |
|
| 219 |
def phylogenetic_placement(sequence: str, mafft_cmd: str, iqtree_cmd: str):
|
| 220 |
+
query_fasta = None
|
| 221 |
try:
|
|
|
|
| 222 |
if len(sequence.strip()) < 100:
|
| 223 |
return False, "Sequence too short (<100 bp).", None, None
|
|
|
|
|
|
|
| 224 |
query_id = f"QUERY_{uuid.uuid4().hex[:8]}"
|
| 225 |
query_fasta = os.path.join(QUERY_OUTPUT_DIR, f"{query_id}.fa")
|
| 226 |
aligned_with_query = os.path.join(QUERY_OUTPUT_DIR, f"{query_id}_aligned.fa")
|
| 227 |
output_prefix = os.path.join(QUERY_OUTPUT_DIR, f"{query_id}_placed_tree")
|
|
|
|
|
|
|
| 228 |
if not os.path.exists(ALIGNMENT_PATH) or not os.path.exists(TREE_PATH):
|
| 229 |
cleanup_file(query_fasta)
|
| 230 |
return False, "Reference alignment or tree not found.", None, None
|
|
|
|
|
|
|
| 231 |
query_record = SeqRecord(Seq(sequence.upper()), id=query_id, description="")
|
| 232 |
SeqIO.write([query_record], query_fasta, "fasta")
|
|
|
|
|
|
|
| 233 |
with open(aligned_with_query, "w") as output_file:
|
| 234 |
result = subprocess.run(
|
| 235 |
[mafft_cmd, "--add", query_fasta, "--reorder", ALIGNMENT_PATH],
|
|
|
|
| 242 |
if not os.path.exists(aligned_with_query) or os.path.getsize(aligned_with_query) == 0:
|
| 243 |
cleanup_file(query_fasta)
|
| 244 |
return False, "MAFFT alignment failed.", None, None
|
|
|
|
|
|
|
| 245 |
result = subprocess.run(
|
| 246 |
[iqtree_cmd, "-s", aligned_with_query, "-g", TREE_PATH, "-m", "GTR+G", "-pre", output_prefix, "-redo"],
|
| 247 |
capture_output=True,
|
|
|
|
| 253 |
if not os.path.exists(treefile):
|
| 254 |
cleanup_file(query_fasta)
|
| 255 |
return False, "IQ-TREE placement failed.", aligned_with_query, None
|
|
|
|
|
|
|
| 256 |
success_msg = f"Placement completed!\nQuery ID: {query_id}\nAlignment: {os.path.basename(aligned_with_query)}\nTree: {os.path.basename(treefile)}"
|
| 257 |
cleanup_file(query_fasta)
|
| 258 |
return True, success_msg, aligned_with_query, treefile
|
|
|
|
| 259 |
except Exception as main_error:
|
| 260 |
logger.error(f"Phylogenetic placement failed: {main_error}", exc_info=True)
|
| 261 |
cleanup_file(query_fasta)
|
| 262 |
return False, f"Error: {str(main_error)}", None, None
|
| 263 |
+
|
| 264 |
def analyze_sequence_for_tree(sequence: str, matching_percentage: float):
|
| 265 |
try:
|
| 266 |
logger.debug("Starting tree analysis...")
|
|
|
|
| 440 |
result = run_pipeline(dna_input, similarity_score, build_ml_tree)
|
| 441 |
cleanup_file(temp_file_path)
|
| 442 |
return result
|
| 443 |
+
except Exception as main_error:
|
| 444 |
logger.error(f"Pipeline from file error: {main_error}", exc_info=True)
|
| 445 |
cleanup_file(temp_file_path)
|
| 446 |
error_msg = f"❌ Error: {str(main_error)}"
|
| 447 |
return error_msg, "", "", "", "", None, None, None, None, error_msg, error_msg, None, None
|
| 448 |
+
|
| 449 |
class AnalysisRequest(BaseModel):
|
| 450 |
sequence: str
|
| 451 |
similarity_score: float = 95.0
|
|
|
|
| 525 |
success=False, error_message=str(e)
|
| 526 |
)
|
| 527 |
|
| 528 |
+
@app.post("/analyze-file", response_model=AnalysisResponse)
|
| 529 |
async def analyze_file(
|
| 530 |
file: UploadFile = File(...),
|
| 531 |
similarity_score: float = Form(95.0),
|
|
|
|
| 538 |
temp_file.write(content)
|
| 539 |
temp_file_path = temp_file.name
|
| 540 |
result = await run_pipeline_from_file(temp_file_path, similarity_score, build_ml_tree)
|
| 541 |
+
cleanup_file(temp_file_path)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 542 |
return AnalysisResponse(
|
| 543 |
boundary_output=result[0] or "",
|
| 544 |
keras_output=result[1] or "",
|
|
|
|
| 549 |
report_html_path=result[12],
|
| 550 |
success=True
|
| 551 |
)
|
| 552 |
+
except Exception as main_error:
|
| 553 |
logger.error(f"Analyze-file error: {main_error}", exc_info=True)
|
| 554 |
+
cleanup_file(temp_file_path)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 555 |
return AnalysisResponse(
|
| 556 |
boundary_output="", keras_output="", ml_tree_output="",
|
| 557 |
tree_analysis_output="", summary_output="",
|
| 558 |
tree_html_path=None, report_html_path=None,
|
| 559 |
success=False, error_message=str(main_error)
|
| 560 |
)
|
| 561 |
+
|
| 562 |
@app.get("/download/{file_type}/{query_id}")
|
| 563 |
async def download_file(file_type: str, query_id: str):
|
| 564 |
try:
|
|
|
|
| 759 |
outputs=gr.Textbox(label="Error"),
|
| 760 |
title="🧬 Gene Analysis Pipeline (Error Mode)"
|
| 761 |
)
|
| 762 |
+
|
| 763 |
# --- Application Startup ---
|
| 764 |
def run_application():
|
| 765 |
try:
|
| 766 |
main_gradio_app = create_gradio_interface()
|
| 767 |
main_gradio_app = gr.mount_gradio_app(app, main_gradio_app, path="/gradio")
|
| 768 |
+
logger.info("🧬 Gene Analysis Pipeline Starting...")
|
| 769 |
+
logger.info("=" * 50)
|
| 770 |
+
logger.info("🔍 Checking system components...")
|
| 771 |
+
logger.info(f"🤖 Boundary Model: {'✅ Loaded' if boundary_model else '❌ Missing'}")
|
| 772 |
+
logger.info(f"🧠 Keras Model: {'✅ Loaded' if keras_model else '❌ Missing'}")
|
| 773 |
+
logger.info(f"🌳 Tree Analyzer: {'✅ Loaded' if analyzer else '❌ Missing'}")
|
| 774 |
+
mafft_available, iqtree_available, _, _ = check_tool_availability()
|
| 775 |
+
logger.info(f"🧬 MAFFT: {'✅ Available' if mafft_available else '❌ Missing'}")
|
| 776 |
+
logger.info(f"🌲 IQ-TREE: {'✅ Available' if iqtree_available else '❌ Missing'}")
|
| 777 |
+
logger.info("=" * 50)
|
| 778 |
+
logger.info("🚀 Starting Gene Analysis Pipeline...")
|
| 779 |
+
logger.warning("⚠️ Running without request queuing. Concurrent requests may block.")
|
| 780 |
+
logger.info("📊 FastAPI docs available at: http://localhost:7860/docs")
|
| 781 |
+
logger.info("🧬 Gradio interface available at: http://localhost:7860/gradio")
|
| 782 |
+
uvicorn.run(
|
| 783 |
+
app,
|
| 784 |
+
host="0.0.0.0",
|
| 785 |
+
port=7860,
|
| 786 |
+
log_level="info",
|
| 787 |
+
access_log=True
|
| 788 |
+
)
|
| 789 |
except Exception as main_error:
|
| 790 |
logger.error(f"Application startup failed: {main_error}", exc_info=True)
|
| 791 |
try:
|
| 792 |
logger.info("🔄 Falling back to Gradio-only mode...")
|
| 793 |
fallback_gradio_app = create_gradio_interface()
|
| 794 |
+
logger.info("🧬 Gradio interface available at: http://localhost:7860")
|
| 795 |
+
fallback_gradio_app.launch(
|
| 796 |
+
server_name="0.0.0.0",
|
| 797 |
+
server_port=7860,
|
| 798 |
+
prevent_thread_lock=True
|
| 799 |
+
)
|
| 800 |
except Exception as fallback_error:
|
| 801 |
logger.error(f"Fallback failed: {fallback_error}", exc_info=True)
|
| 802 |
print("❌ Application failed to start. Check logs for details.")
|
| 803 |
+
|
| 804 |
if __name__ == "__main__":
|
| 805 |
print("🧬 Gene Analysis Pipeline Starting...")
|
| 806 |
print("=" * 50)
|