From 29798c7351ec3e3c6a13f40b4f80e804ec803006 Mon Sep 17 00:00:00 2001 From: Navan Chauhan Date: Sat, 29 Aug 2020 19:25:45 +0530 Subject: added single dock functionality, added more information --- app/dock-single.py | 235 ++++++++++++++++++++++++++++++++++ app/forms.py | 7 + app/templates/base.html | 9 +- app/templates/dock_upload_single.html | 43 +++++++ app/templates/generate.html | 2 +- app/templates/home.html | 25 +++- app/views.py | 41 +++++- 7 files changed, 348 insertions(+), 14 deletions(-) create mode 100644 app/dock-single.py create mode 100644 app/templates/dock_upload_single.html diff --git a/app/dock-single.py b/app/dock-single.py new file mode 100644 index 0000000..be623f7 --- /dev/null +++ b/app/dock-single.py @@ -0,0 +1,235 @@ +import argparse +import logging +import multiprocessing +import os +import sys +from argparse import ArgumentParser +from collections import namedtuple + +import mysql.connector as con + +mycon = con.connect(host='192.168.1.6',user="curieweb",password="curie-web-russian-54",port=3306,database="curie") +mycursor = mycon.cursor() + +sql_select_Query = "SELECT id,email,pdb,ligand_smile,ligand_name,description,date FROM curieweb WHERE pdb IS NOT NULL AND done=0 LIMIT 1" +mycursor.execute(sql_select_Query) + +records = mycursor.fetchall() +if records == []: + print("Empty Set 😳") + print("No active task, exitting gracefully") + exit(0) + +records = records[0] + + + +print("Importing PLIP..",end="") + +from plip.basic import config, logger + +from plip.basic.config import __version__ +from plip.basic.parallel import parallel_fn +from plip.basic.remote import VisualizerData +from plip.exchange.webservices import fetch_pdb +from plip.structure.preparation import create_folder_if_not_exists, extract_pdbid +from plip.structure.preparation import tilde_expansion, PDBComplex + +print(".Done") + +def download_structure(inputpdbid): + """Given a PDB ID, downloads the corresponding PDB structure. + Checks for validity of ID and handles error while downloading. + Returns the path of the downloaded file.""" + try: + if len(inputpdbid) != 4 or extract_pdbid(inputpdbid.lower()) == 'UnknownProtein': + logger.error(f'invalid PDB-ID (wrong format): {inputpdbid}') + sys.exit(1) + pdbfile, pdbid = fetch_pdb(inputpdbid.lower()) + pdbpath = tilde_expansion('%s/%s.pdb' % (config.BASEPATH.rstrip('/'), pdbid)) + create_folder_if_not_exists(config.BASEPATH) + with open(pdbpath, 'w') as g: + g.write(pdbfile) + return pdbpath, pdbid + except ValueError: # Invalid PDB ID, cannot fetch from RCBS server + logger.error(f'PDB-ID does not exist: {inputpdbid}') + sys.exit(1) + +def bounding_box(receptor, residues): + try: + import pymol2 + except ImportError: + raise ImportError("Failed to import PyMOL") + + session = pymol2.PyMOL() + session.start() + + cmd = session.cmd + cmd.load(pdbpath,"target") + cmd.select("box",(selectionResidues)) + + extent = 5 + + ([minX, minY, minZ],[maxX, maxY, maxZ]) = cmd.get_extent("box") + + minX = minX - float(extent) + minY = minY - float(extent) + minZ = minZ - float(extent) + maxX = maxX + float(extent) + maxY = maxY + float(extent) + maxZ = maxZ + float(extent) + + SizeX = maxX - minX + SizeY = maxY - minY + SizeZ = maxZ - minZ + CenterX = (maxX + minX)/2 + CenterY = (maxY + minY)/2 + CenterZ = (maxZ + minZ)/2 + + session.stop() + + return {"size_x": SizeX, "size_y": SizeY, "size_z": SizeZ, "center_x": CenterX, "center_y": CenterY, "center_z": CenterZ} + +def removeWater(pdbpath): + import pymol2 + session = pymol2.PyMOL() + session.start() + cmd = session.cmd + cmd.load(pdbpath,"target") + cmd.remove('resn HOH') + cmd.save(pdbpath,"target") + session.stop() + +def getResidues(pdbpath): + mol = PDBComplex() + mol.load_pdb(pdbpath) + for ligand in mol.ligands: + mol.characterize_complex(ligand) + + residues = [] + + for x in range(len(mol.interaction_sets)): + if len(mol.interaction_sets[list(mol.interaction_sets.keys())[x]].interacting_res) != 0: + residues.append(mol.interaction_sets[list(mol.interaction_sets.keys())[x]].interacting_res) + + print(residues) + return residues + +def get_select_command(residues,allResidues=False): + residues.sort(key=len,reverse=True) + selectionResidues = "" + allRes = [] + if len(residues) == 0: + #print("what the frick, no interacting ligands???") + print("We could not find any binding sites within the structure.") + else: + for x in residues: + selectionResidues = "" + for y in x: + selectionResidues += 'resi ' + y.replace("A","") + ' + ' + allRes.append(selectionResidues.strip()[:-1].strip()) + + if allResidues == False: + return allRes[0] + return allRes + +def convert_pdb_pdbqt(pdbpath): + import oddt + from oddt.docking.AutodockVina import write_vina_pdbqt + print(pdbpath) + try: + receptor = next(oddt.toolkit.readfile("pdb",pdbpath.split("./")[1])) + """ + # remove zero order bonds from metals + for atom in receptor: + if atom.atomicnum == 30: # Atomic num of treated metals + for bond in atom.bonds: + print("del") + receptor.OBMol.DeleteBond(bond.OBBond) + """ + receptor.calccharges() + except Exception: + print("Molecule failed to charge, falling back to RDKit") + receptor = next(oddt.toolkits.rdk.readfile("pdb",pdbpath.split("./")[1])) + receptor.calccharges() + + path = write_vina_pdbqt(receptor,'./',flexible=False) + return path + + +def email(zipArchive): + import smtplib + from email.mime.multipart import MIMEMultipart + from email.mime.text import MIMEText + from email.mime.base import MIMEBase + from email import encoders + + fromaddr = "navanchauhan@gmail.com" + + msg = MIMEMultipart() + msg['From'] = fromaddr + msg['To'] = toaddr + msg['Subject'] = "Curie Web Results for Job ID " + str(jobID) + body = "Attached Zip contains the docked files, PLIP report and PyMOL Visualisations. If the ZIP file does not contain these files, please report this issue by replying to this email. Job was submitted on {} with the description {}".format(date, description) + + msg.attach(MIMEText(body, 'plain')) + filename = "Curie_Web_Results_Job_ID_" + str(jobID) + ".zip" + p = MIMEBase('application', 'octet-stream') + with open((str(zipArchive) + ".zip"), "rb") as attachment: + p.set_payload((attachment).read()) + encoders.encode_base64(p) + p.add_header('Content-Disposition', "attachment; filename= %s" % filename) + msg.attach(p) + + s = smtplib.SMTP('smtp.gmail.com', 587) + s.starttls() + s.login(fromaddr, 'okrs shoc ahtk idui') + text = msg.as_string() + + s.sendmail(fromaddr, toaddr, text) + s.quit() + + +inPDB = records[2] +jobID = records[0] +toaddr = records[1] +description = records[5] +date = records[6] + +#pdb_file_name = pdbpath.split('/')[-1] +#pdbpath="./6lu7.pdb" + +import os +cd = os.getcwd() +f = os.path.join(cd,"static/uploads") +#t = os.path.join(f,"receptor",target) +#r = os.path.join(f,"ligands",ligand) +#c = os.path.join(f,"configs",config) +import tempfile +from shutil import make_archive +import time + +with tempfile.TemporaryDirectory() as directory: + print('The created temporary directory is %s' % directory) + os.chdir(directory) + pdbpath, pdbid = download_structure(inPDB) + residues = getResidues(pdbpath) + selectionResidues = get_select_command(residues,allResidues=False) + #print(selectionResidues) + removeWater(pdbpath) + config = bounding_box(pdbpath,selectionResidues) + print("Configuration:",config) + pdbqt = convert_pdb_pdbqt(pdbpath) + configuration = "size_x={}\nsize_y={}\nsize_z={}\ncenter_x={}\ncenter_y={}\ncenter_z={}".format(config["size_x"],config["size_y"],config["size_z"],config["center_x"],config["center_y"],config["center_z"]) + with open("config.txt","w") as file: + file.write(configuration) + os.system('obabel -:"%s" --gen3d -opdbqt -O%s.pdbqt' % (records[3],records[4])) + os.system("docker run --rm -v ${PWD}:/results -w /results -u $(id -u ${USER}):$(id -g ${USER}) navanchauhan/curie-cli -r %s -l %s -c config.txt -dpi" % (pdbqt,str(records[4]+".pdbqt"))) + z = "Curie_Web_Result_"+str(jobID) + zi = os.path.join(f,z) + make_archive(zi, 'zip', directory) + #copy(("Curie_Web_Result_"+str(jobID)),f) + email(zi) + #print((str(zi) + ".zip")) + mycursor.execute('UPDATE curieweb set done=1 where id="%s"' % (jobID)) + mycon.commit() \ No newline at end of file diff --git a/app/forms.py b/app/forms.py index 975b3ad..4a3f880 100644 --- a/app/forms.py +++ b/app/forms.py @@ -34,6 +34,13 @@ class curieForm(FlaskForm): class statusForm(FlaskForm): jobID = StringField('Job ID',validators=[DataRequired()]) +class dockSingleForm(FlaskForm): + description = StringField('Description',default="Curie Web Task") + pdbID = StringField('PDB ID',validators=[DataRequired()]) + smiles = StringField('SMILES',validators=[DataRequired()]) + name = StringField('Ligand Name',validators=[DataRequired()]) + email = StringField('Email', validators=[DataRequired(), Email()]) + class generateSMILES(FlaskForm): n = IntegerField('Number of Molecules to Generate',default=1,validators=[DataRequired()]) #modelSelection = SelectField('Model',choices=[("alpha","Alpha"),("beta","Beta")]) diff --git a/app/templates/base.html b/app/templates/base.html index d116ee3..dcae79c 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -68,15 +68,16 @@

Features

Beta Features

diff --git a/app/templates/dock_upload_single.html b/app/templates/dock_upload_single.html new file mode 100644 index 0000000..882675c --- /dev/null +++ b/app/templates/dock_upload_single.html @@ -0,0 +1,43 @@ +{% extends 'base.html' %} + +{% block main %} +

Enter Your Configuration

+
+ {% include 'flash_messages.html' %} + {{ form.csrf_token }} +
+ {{ form.description.label }} + {{ form.description(class="form-control") }} +
+
+
+ {{ form.pdbID.label }} + {{ form.pdbID(class="form-control")}} +
+
+ {{ form.smiles.label }} + {{ form.smiles(class="form-control")}} +
+
+ {{ form.name.label }} + {{ form.name(class="form-control")}} +
+
+
+ {{ form.email.label }} + {{ form.email(class="form-control") }} +
+ + +
+
+
+ +
+ +
+{% endblock %} \ No newline at end of file diff --git a/app/templates/generate.html b/app/templates/generate.html index 45bbb3b..629a2a3 100644 --- a/app/templates/generate.html +++ b/app/templates/generate.html @@ -34,7 +34,7 @@ {% endfor %} diff --git a/app/templates/home.html b/app/templates/home.html index 541c313..43a84cc 100644 --- a/app/templates/home.html +++ b/app/templates/home.html @@ -2,12 +2,29 @@ {% block main %}

Curie Web Demo

-

Dock and Report performs molecular docking using AutoDock Vina, generates visualisations using PyMOL and then finds protein-ligand interactions using PLIP. It then compiles all of this into a PDF report and emails it to you.

+

Curie-Web is a part of The Curie Project which aims to make the process of Computer-Aided Drug Design as fast as possible.

+

The following are the currently active modules

+

Docking

+

Drug Designing

+ + +

Researching

+ + +

Misc.

+ {% endblock %} \ No newline at end of file diff --git a/app/views.py b/app/views.py index 03e74f4..39db678 100644 --- a/app/views.py +++ b/app/views.py @@ -12,10 +12,11 @@ from string import digits, ascii_lowercase from pymed import PubMed from datetime import datetime import json +import subprocess # Note: that when using Flask-WTF we need to import the Form Class that we created # in forms.py -from .forms import MyForm, curieForm, statusForm, generateSMILES, PyMedSearch +from .forms import MyForm, curieForm, statusForm, generateSMILES, PyMedSearch, dockSingleForm def gen_word(N, min_N_dig, min_N_low): choose_from = [digits]*min_N_dig + [ascii_lowercase]*min_N_low @@ -30,7 +31,6 @@ def convertToBinaryData(filename): binaryData = file.read() return binaryData - ### # Routing for your application. ### @@ -99,7 +99,6 @@ def status(): flash_errors(taskStatusForm) return render_template('job_status_form.html',form=taskStatusForm) - @app.route('/basic-form', methods=['GET', 'POST']) def basic_form(): if request.method == 'POST': @@ -114,7 +113,6 @@ def basic_form(): return render_template('form.html') - @app.route('/wtform', methods=['GET', 'POST']) def wtform(): myform = MyForm() @@ -133,7 +131,7 @@ def wtform(): flash_errors(myform) return render_template('wtform.html', form=myform) -tfWorking = -1 +tfWorking = 0 if tfWorking == -1: try: @@ -218,6 +216,39 @@ def dock_upload(): flash_errors(form) return render_template('dock_upload.html', form=form) +@app.route('/Dock-Single', methods=['GET', 'POST']) +def dock_upload_single(): + form = dockSingleForm() + + if request.method == 'POST' and form.validate_on_submit(): + print("Recieved task: ",form.description.data) + description = form.description.data + pdb = form.pdbID.data + smile = form.smiles.data + name = form.name.data + email = form.email.data + + import mysql.connector as con + mycon = con.connect(host=app.config['DB_HOST'],user=app.config['DB_USER'],password=app.config['DB_PASSWORD'],port=app.config['DB_PORT'],database=app.config['DB_NAME']) + mycursor = mycon.cursor() + + sqlQuery = "insert into curieweb (id, email, pdb, ligand_smile, ligand_name, date, description) values (%s,%s,%s,%s,%s,CURDATE(),%s) " + jobID = gen_word(16, 1, 1) + + insert_tuple = (jobID,email,pdb,smile,name,description) + mycursor.execute(sqlQuery,insert_tuple) + mycon.commit() + + print("Description",description) + + cwd = os.path.join(os.getcwd(),"app") + subprocess.Popen(['python3', 'dock-single.py'],cwd=cwd) + + return render_template('display_result.html', filename="OwO", description=description,job=jobID) + + flash_errors(form) + return render_template('dock_upload_single.html', form=form) + ### # The functions below should be applicable to all Flask apps. ### -- cgit v1.2.3