added single dock functionality, added more information

This commit is contained in:
Navan Chauhan 2020-08-29 19:25:45 +05:30
parent acc43c4818
commit 29798c7351
7 changed files with 348 additions and 14 deletions

235
app/dock-single.py Normal file
View File

@ -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()

View File

@ -34,6 +34,13 @@ class curieForm(FlaskForm):
class statusForm(FlaskForm): class statusForm(FlaskForm):
jobID = StringField('Job ID',validators=[DataRequired()]) 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): class generateSMILES(FlaskForm):
n = IntegerField('Number of Molecules to Generate',default=1,validators=[DataRequired()]) n = IntegerField('Number of Molecules to Generate',default=1,validators=[DataRequired()])
#modelSelection = SelectField('Model',choices=[("alpha","Alpha"),("beta","Beta")]) #modelSelection = SelectField('Model',choices=[("alpha","Alpha"),("beta","Beta")])

View File

@ -68,15 +68,16 @@
<div class="col-sm-6 col-md-3 item"> <div class="col-sm-6 col-md-3 item">
<h3>Features</h3> <h3>Features</h3>
<ul> <ul>
<li><a href="#">Dock and Report</a></li> <li><a href="{{ url_for('dock_upload') }}">Dock and Report (Manual)</a></li>
<li><a href="#">PubMed Search</a></li> <li><a href="{{ url_for('dock_upload_single') }}">Dock and Report (Automatic)</a></li>
<li><a href="{{ url_for('pubmed') }}">PubMed Search</a></li>
</ul> </ul>
</div> </div>
<div class="col-sm-6 col-md-3 item"> <div class="col-sm-6 col-md-3 item">
<h3>Beta Features</h3> <h3>Beta Features</h3>
<ul> <ul>
<li><a href="#">LSTM Generator</a></li> <li><a href="{{ url_for('generate') }}">LSTM Generator</a></li>
<li><a href="#">Visualiser</a></li> <li><a href="{{ url_for('visualise') }}">Visualiser</a></li>
</ul> </ul>
</div> </div>
<div class="col-md-6 item text"> <div class="col-md-6 item text">

View File

@ -0,0 +1,43 @@
{% extends 'base.html' %}
{% block main %}
<h2>Enter Your Configuration</h2>
<form action="{{ url_for('dock_upload_single') }}" method="post" enctype="multipart/form-data">
{% include 'flash_messages.html' %}
{{ form.csrf_token }}
<div class="form-group">
{{ form.description.label }}
{{ form.description(class="form-control") }}
</div>
<div class="form-row">
<div class="col">
{{ form.pdbID.label }}
{{ form.pdbID(class="form-control")}}
</div>
<div class="col">
{{ form.smiles.label }}
{{ form.smiles(class="form-control")}}
</div>
<div class="col">
{{ form.name.label }}
{{ form.name(class="form-control")}}
</div>
</div>
<div class="form-group">
{{ form.email.label }}
{{ form.email(class="form-control") }}
</div>
<button class="btn btn-primary">Upload</button>
</form>
<br>
<section>
<style>
#discovery{
height: 40vh;
}
</style>
<div id="discovery"></div>
<script src="{{url_for('static',filename='js/discovery.js')}}"></script>
</section>
{% endblock %}

View File

@ -34,7 +34,7 @@
<script> <script>
SmilesDrawer.parse('{{result[0]}}', function(tree) { SmilesDrawer.parse('{{result[0]}}', function(tree) {
smilesDrawer.draw(tree, "canvas-{{x}}", "dark", false); smilesDrawer.draw(tree, "canvas-{{x}}", "dark", false);
console.log(smilesDrawer.draw(tree, "canvas-{{x}}", "light", false)) console.log(smilesDrawer.draw(tree, "canvas-{{x}}", "dark", false))
}); });
</script> </script>
{% endfor %} {% endfor %}

View File

@ -2,12 +2,29 @@
{% block main %} {% block main %}
<h2>Curie Web Demo</h2> <h2>Curie Web Demo</h2>
<p>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.</p> <p>Curie-Web is a part of The Curie Project which aims to make the process of Computer-Aided Drug Design as fast as possible.</p>
<p>The following are the currently active modules</p>
<h3>Docking</h3>
<ul> <ul>
<li><a href="{{ url_for('dock_upload') }}">Dock and Report</a></li> <li><a href="{{ url_for('dock_upload') }}">Dock and Report (Manual)</a> - You can enter your AutoDock Vina configuration, upload the PDBQT files and it will perform the molecular docking and generate a PDF with proper visualisations and protein-interaction profillings (Using PLIP) </li>
<li><a href="{{ url_for('status')}}">Job Status</a></li> <li><a href="{{ url_for('dock_upload_single') }}">Dock and Report (Automatic)</a> - You just enter in the PDB Code, target compound's SMILES structure and name, it will automatically find a binding location and then perform docking and report generation</li>
<li><a href="{{ url_for('generate') }}">Generate</a></li>
</ul> </ul>
<h3>Drug Designing</h3>
<ul>
<li><a href="{{ url_for('generate') }}">Generate</a> - You can use this to generate completely new compounds</li>
</ul>
<h3>Researching</h3>
<ul>
<li><a href="{{ url_for('pubmed') }}">PubMed Search</a> - Handy PubMed search with direct download links</li>
<li>Qrious App - You can enter a question for a set of papers (e.g. ChemRxiv preprints) and it uses AI to answer it for each individual paper based on their abstract</li>
</ul>
<h3>Misc.</h3>
<ul>
<li><a href="{{ url_for('status')}}">Job Status</a> - Check the job status </li>
<li><a href="{{ url_for('visualise')}}">Visualise</a> - Molecular Viewer </li>
</ul>
<img src="{{url_for('static',filename='assets/workingInALaboratory.svg')}}" /> <img src="{{url_for('static',filename='assets/workingInALaboratory.svg')}}" />
{% endblock %} {% endblock %}

View File

@ -12,10 +12,11 @@ from string import digits, ascii_lowercase
from pymed import PubMed from pymed import PubMed
from datetime import datetime from datetime import datetime
import json import json
import subprocess
# Note: that when using Flask-WTF we need to import the Form Class that we created # Note: that when using Flask-WTF we need to import the Form Class that we created
# in forms.py # 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): def gen_word(N, min_N_dig, min_N_low):
choose_from = [digits]*min_N_dig + [ascii_lowercase]*min_N_low choose_from = [digits]*min_N_dig + [ascii_lowercase]*min_N_low
@ -30,7 +31,6 @@ def convertToBinaryData(filename):
binaryData = file.read() binaryData = file.read()
return binaryData return binaryData
### ###
# Routing for your application. # Routing for your application.
### ###
@ -99,7 +99,6 @@ def status():
flash_errors(taskStatusForm) flash_errors(taskStatusForm)
return render_template('job_status_form.html',form=taskStatusForm) return render_template('job_status_form.html',form=taskStatusForm)
@app.route('/basic-form', methods=['GET', 'POST']) @app.route('/basic-form', methods=['GET', 'POST'])
def basic_form(): def basic_form():
if request.method == 'POST': if request.method == 'POST':
@ -114,7 +113,6 @@ def basic_form():
return render_template('form.html') return render_template('form.html')
@app.route('/wtform', methods=['GET', 'POST']) @app.route('/wtform', methods=['GET', 'POST'])
def wtform(): def wtform():
myform = MyForm() myform = MyForm()
@ -133,7 +131,7 @@ def wtform():
flash_errors(myform) flash_errors(myform)
return render_template('wtform.html', form=myform) return render_template('wtform.html', form=myform)
tfWorking = -1 tfWorking = 0
if tfWorking == -1: if tfWorking == -1:
try: try:
@ -218,6 +216,39 @@ def dock_upload():
flash_errors(form) flash_errors(form)
return render_template('dock_upload.html', form=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. # The functions below should be applicable to all Flask apps.
### ###