Commit 7730f38d by Peter Eastman

Lots of enhancements and bug fixes. Very early beginnings of a UI.

parent af019e18
...@@ -31,6 +31,7 @@ USE OR OTHER DEALINGS IN THE SOFTWARE. ...@@ -31,6 +31,7 @@ USE OR OTHER DEALINGS IN THE SOFTWARE.
__author__ = "Peter Eastman" __author__ = "Peter Eastman"
__version__ = "1.0" __version__ = "1.0"
import ui
import simtk.openmm as mm import simtk.openmm as mm
import simtk.openmm.app as app import simtk.openmm.app as app
import simtk.unit as unit import simtk.unit as unit
...@@ -42,7 +43,7 @@ import os ...@@ -42,7 +43,7 @@ import os
import os.path import os.path
import math import math
_substitutions = { substitutions = {
'2AS':'ASP', '3AH':'HIS', '5HP':'GLU', 'ACL':'ARG', 'AGM':'ARG', 'AIB':'ALA', 'ALM':'ALA', 'ALO':'THR', 'ALY':'LYS', 'ARM':'ARG', '2AS':'ASP', '3AH':'HIS', '5HP':'GLU', 'ACL':'ARG', 'AGM':'ARG', 'AIB':'ALA', 'ALM':'ALA', 'ALO':'THR', 'ALY':'LYS', 'ARM':'ARG',
'ASA':'ASP', 'ASB':'ASP', 'ASK':'ASP', 'ASL':'ASP', 'ASQ':'ASP', 'AYA':'ALA', 'BCS':'CYS', 'BHD':'ASP', 'BMT':'THR', 'BNN':'ALA', 'ASA':'ASP', 'ASB':'ASP', 'ASK':'ASP', 'ASL':'ASP', 'ASQ':'ASP', 'AYA':'ALA', 'BCS':'CYS', 'BHD':'ASP', 'BMT':'THR', 'BNN':'ALA',
'BUC':'CYS', 'BUG':'LEU', 'C5C':'CYS', 'C6C':'CYS', 'CCS':'CYS', 'CEA':'CYS', 'CGU':'GLU', 'CHG':'ALA', 'CLE':'LEU', 'CME':'CYS', 'BUC':'CYS', 'BUG':'LEU', 'C5C':'CYS', 'C6C':'CYS', 'CCS':'CYS', 'CEA':'CYS', 'CGU':'GLU', 'CHG':'ALA', 'CLE':'LEU', 'CME':'CYS',
...@@ -56,7 +57,7 @@ _substitutions = { ...@@ -56,7 +57,7 @@ _substitutions = {
'NLN':'LEU', 'NLP':'LEU', 'NMC':'GLY', 'OAS':'SER', 'OCS':'CYS', 'OMT':'MET', 'PAQ':'TYR', 'PCA':'GLU', 'PEC':'CYS', 'PHI':'PHE', 'NLN':'LEU', 'NLP':'LEU', 'NMC':'GLY', 'OAS':'SER', 'OCS':'CYS', 'OMT':'MET', 'PAQ':'TYR', 'PCA':'GLU', 'PEC':'CYS', 'PHI':'PHE',
'PHL':'PHE', 'PR3':'CYS', 'PRR':'ALA', 'PTR':'TYR', 'PYX':'CYS', 'SAC':'SER', 'SAR':'GLY', 'SCH':'CYS', 'SCS':'CYS', 'SCY':'CYS', 'PHL':'PHE', 'PR3':'CYS', 'PRR':'ALA', 'PTR':'TYR', 'PYX':'CYS', 'SAC':'SER', 'SAR':'GLY', 'SCH':'CYS', 'SCS':'CYS', 'SCY':'CYS',
'SEL':'SER', 'SEP':'SER', 'SET':'SER', 'SHC':'CYS', 'SHR':'LYS', 'SMC':'CYS', 'SOC':'CYS', 'STY':'TYR', 'SVA':'SER', 'TIH':'ALA', 'SEL':'SER', 'SEP':'SER', 'SET':'SER', 'SHC':'CYS', 'SHR':'LYS', 'SMC':'CYS', 'SOC':'CYS', 'STY':'TYR', 'SVA':'SER', 'TIH':'ALA',
'TPL':'TRP', 'TPO':'THR', 'TPQ':'ALA', 'TRG':'LYS', 'TRO':'TRP', 'TYB':'TYR', 'TYQ':'TYR', 'TYS':'TYR', 'TYY':'TYR' 'TPL':'TRP', 'TPO':'THR', 'TPQ':'ALA', 'TRG':'LYS', 'TRO':'TRP', 'TYB':'TYR', 'TYI':'TYR', 'TYQ':'TYR', 'TYS':'TYR', 'TYY':'TYR'
} }
def _overlayPoints(points1, points2): def _overlayPoints(points1, points2):
...@@ -98,12 +99,13 @@ class PDBFixer(object): ...@@ -98,12 +99,13 @@ class PDBFixer(object):
# Load the templates. # Load the templates.
self.templates = {} self.templates = {}
for file in os.listdir('templates'): templatesPath = os.path.join(os.path.dirname(__file__), 'templates')
templatePdb = app.PDBFile(os.path.join('templates', file)) for file in os.listdir(templatesPath):
templatePdb = app.PDBFile(os.path.join(templatesPath, file))
name = templatePdb.topology.residues().next().name name = templatePdb.topology.residues().next().name
self.templates[name] = templatePdb self.templates[name] = templatePdb
def _addAtomsToTopology(self, missingAtoms, heavyAtomsOnly, omitUnknownMolecules): def _addAtomsToTopology(self, missingAtoms, missingTerminals, heavyAtomsOnly, omitUnknownMolecules):
"""Create a new Topology in which missing atoms have been added.""" """Create a new Topology in which missing atoms have been added."""
newTopology = app.Topology() newTopology = app.Topology()
...@@ -116,7 +118,6 @@ class PDBFixer(object): ...@@ -116,7 +118,6 @@ class PDBFixer(object):
if omitUnknownMolecules and not any(residue.name in self.templates for residue in chain.residues()): if omitUnknownMolecules and not any(residue.name in self.templates for residue in chain.residues()):
continue continue
newChain = newTopology.addChain() newChain = newTopology.addChain()
chainResidues = list(chain.residues())
for residue in chain.residues(): for residue in chain.residues():
newResidue = newTopology.addResidue(residue.name, newChain) newResidue = newTopology.addResidue(residue.name, newChain)
...@@ -157,9 +158,9 @@ class PDBFixer(object): ...@@ -157,9 +158,9 @@ class PDBFixer(object):
# If a terminal OXT is missing, add it. # If a terminal OXT is missing, add it.
if residue == chainResidues[-1] and residue.name in self.templates: if residue in missingTerminals:
atomPositions = dict((atom.name, self.positions[atom.index].value_in_unit(unit.nanometer)) for atom in residue.atoms()) atomPositions = dict((atom.name, newPositions[atom.index].value_in_unit(unit.nanometer)) for atom in newResidue.atoms())
if 'OXT' not in atomPositions and all(name in atomPositions for name in ['C', 'O', 'CA']): if 'OXT' in missingTerminals[residue]:
newAtom = newTopology.addAtom('OXT', oxygen, newResidue) newAtom = newTopology.addAtom('OXT', oxygen, newResidue)
newAtoms.append(newAtom) newAtoms.append(newAtom)
addedOXT.append(newAtom) addedOXT.append(newAtom)
...@@ -201,7 +202,7 @@ class PDBFixer(object): ...@@ -201,7 +202,7 @@ class PDBFixer(object):
return (newTopology, newPositions, newAtoms, existingAtomMap) return (newTopology, newPositions, newAtoms, existingAtomMap)
def findNonstandardResidues(self): def findNonstandardResidues(self):
return [r for r in self.topology.residues() if r.name in _substitutions] return [r for r in self.topology.residues() if r.name in substitutions]
def replaceNonstandardResidues(self, replaceResidues): def replaceNonstandardResidues(self, replaceResidues):
if len(replaceResidues) > 0: if len(replaceResidues) > 0:
...@@ -211,7 +212,7 @@ class PDBFixer(object): ...@@ -211,7 +212,7 @@ class PDBFixer(object):
# Find heavy atoms that should be deleted. # Find heavy atoms that should be deleted.
for residue in replaceResidues: for residue in replaceResidues:
residue.name = _substitutions[residue.name] residue.name = substitutions[residue.name]
template = self.templates[residue.name] template = self.templates[residue.name]
standardAtoms = set(atom.name for atom in template.topology.atoms()) standardAtoms = set(atom.name for atom in template.topology.atoms())
for atom in residue.atoms(): for atom in residue.atoms():
...@@ -236,26 +237,47 @@ class PDBFixer(object): ...@@ -236,26 +237,47 @@ class PDBFixer(object):
def findMissingAtoms(self): def findMissingAtoms(self):
missingAtoms = {} missingAtoms = {}
for residue in self.topology.residues(): missingTerminals = {}
# Loop over residues.
for chain in self.topology.chains():
chainResidues = list(chain.residues())
for residue in chain.residues():
if residue.name in self.templates: if residue.name in self.templates:
template = self.templates[residue.name] template = self.templates[residue.name]
atomNames = set(atom.name for atom in residue.atoms()) atomNames = set(atom.name for atom in residue.atoms())
# Add atoms from the template that are missing.
missing = [] missing = []
for atom in template.topology.atoms(): for atom in template.topology.atoms():
if atom.name not in atomNames: if atom.name not in atomNames:
missing.append(atom) missing.append(atom)
if len(missing) > 0: if len(missing) > 0:
missingAtoms[residue] = missing missingAtoms[residue] = missing
return missingAtoms
def addMissingAtoms(self, missingAtoms): # Add missing terminal atoms.
terminals = []
if residue == chainResidues[-1]:
templateNames = set(atom.name for atom in template.topology.atoms())
if 'OXT' not in atomNames and all(name in templateNames for name in ['C', 'O', 'CA']):
terminals.append('OXT')
if len(terminals) > 0:
missingTerminals[residue] = terminals
return (missingAtoms, missingTerminals)
def addMissingAtoms(self, missingAtoms, missingTerminals):
# Create a Topology that 1) adds missing atoms, 2) removes all hydrogens, and 3) removes unknown molecules. # Create a Topology that 1) adds missing atoms, 2) removes all hydrogens, and 3) removes unknown molecules.
(newTopology, newPositions, newAtoms, existingAtomMap) = self._addAtomsToTopology(missingAtoms, True, True) (newTopology, newPositions, newAtoms, existingAtomMap) = self._addAtomsToTopology(missingAtoms, missingTerminals, True, True)
if len(newAtoms) > 0:
# Create a System for energy minimizing it. # Create a System for energy minimizing it.
forcefield = app.ForceField('soft.xml') res = list(newTopology.residues())
forcefield = app.ForceField(os.path.join(os.path.dirname(__file__), 'soft.xml'))
system = forcefield.createSystem(newTopology) system = forcefield.createSystem(newTopology)
# Set any previously existing atoms to be massless, they so won't move. # Set any previously existing atoms to be massless, they so won't move.
...@@ -274,7 +296,7 @@ class PDBFixer(object): ...@@ -274,7 +296,7 @@ class PDBFixer(object):
# For efficiency, only compute interactions that involve a new atom. # For efficiency, only compute interactions that involve a new atom.
nonbonded.addInteractionGroup([atom.index for atom in newTopology.atoms() if atom in newAtoms], range(system.getNumParticles())) nonbonded.addInteractionGroup([atom.index for atom in newAtoms], range(system.getNumParticles()))
# Do an energy minimization. # Do an energy minimization.
...@@ -286,16 +308,22 @@ class PDBFixer(object): ...@@ -286,16 +308,22 @@ class PDBFixer(object):
# Now create a new Topology, including all atoms from the original one and adding the missing atoms. # Now create a new Topology, including all atoms from the original one and adding the missing atoms.
(newTopology2, newPositions2, newAtoms2, existingAtomMap2) = self._addAtomsToTopology(missingAtoms, False, False) (newTopology2, newPositions2, newAtoms2, existingAtomMap2) = self._addAtomsToTopology(missingAtoms, missingTerminals, False, False)
# Copy over the minimized positions for the new atoms. # Copy over the minimized positions for the new atoms.
for a1, a2 in zip(newAtoms, newAtoms2): for a1, a2 in zip(newAtoms, newAtoms2):
newPositions2[a2.index] = state.getPositions()[a1.index] newPositions2[a2.index] = state.getPositions()[a1.index]
app.PDBFile.writeFile(newTopology2, newPositions2, open('output.pdb', 'w')) self.processedTopology = newTopology2
self.processedPositions = newPositions2
if __name__=='__main__': if __name__=='__main__':
if len(sys.argv) < 2:
ui.launchUI()
else:
fixer = PDBFixer(app.PDBFile(sys.argv[1])) fixer = PDBFixer(app.PDBFile(sys.argv[1]))
fixer.replaceNonstandardResidues(fixer.findNonstandardResidues()) fixer.replaceNonstandardResidues(fixer.findNonstandardResidues())
fixer.addMissingAtoms(fixer.findMissingAtoms()) missingAtoms, missingTerminals = fixer.findMissingAtoms()
\ No newline at end of file fixer.addMissingAtoms(missingAtoms, missingTerminals)
app.PDBFile.writeFile(fixer.processedTopology, fixer.processedPositions, open('output.pdb', 'w'))
import simtk.openmm.app as app
from pdbfixer import PDBFixer, substitutions
import uiserver
import webbrowser
from cStringIO import StringIO
def startPageCallback(parameters, handler):
if 'pdbfile' in parameters:
global fixer
pdb = app.PDBFile(parameters['pdbfile'].value.splitlines())
fixer = PDBFixer(pdb)
displayConvertResiduesPage()
def convertResiduesPageCallback(parameters, handler):
global nonstandard
fixer.replaceNonstandardResidues(nonstandard)
nonstandard = []
displayMissingAtomsPage()
def missingAtomsPageCallback(parameters, handler):
fixer.addMissingAtoms(missingAtoms, missingTerminals)
displayDownloadPage()
def downloadPageCallback(parameters, handler):
if 'download' in parameters:
output = StringIO()
app.PDBFile.writeFile(fixer.processedTopology, fixer.processedPositions, output)
handler.sendDownload(output.getvalue(), 'output.pdb')
else:
displayStartPage()
def displayStartPage():
uiserver.setCallback(startPageCallback)
uiserver.setContent("""
<html>
<head><title>PDBFixer</title></head>
<body>
<h1>Welcome To PDBFixer!</h1>
Select a PDB file to load. It will be analyzed for problems.
<p>
<form method="post" action="/" enctype="multipart/form-data">
PDB File: <input type="file" name="pdbfile"/>
<p>
<input type="submit" value="Analyze File"/>
</form>
</body>
</html>
""")
def displayConvertResiduesPage():
uiserver.setCallback(convertResiduesPageCallback)
global nonstandard
nonstandard = fixer.findNonstandardResidues()
if len(nonstandard) == 0:
displayMissingAtomsPage()
return
table = ""
for i, residue in enumerate(nonstandard):
table += ' <tr><td>%d</td><td>%s %d</td><td>%s</td><td><input type="checkbox" name="convert%d" checked></td></tr>\n' % (residue.chain.index+1, residue.name, residue.index+1, substitutions[residue.name], i)
uiserver.setContent("""
<html>
<head><title>PDB Fixer</title></head>
<body>
This PDB file contains non-standard residues. Do you want to convert them to the corresponding standard residues?
<p>
<form method="get" action="/">
<table border="1">
<tr><th>Chain</th><th>Residue</th><th>Convert To</th><th>Convert?</th></tr>
%s
</table>
<p>
<input type="submit" value="Continue"/>
</form>
</body>
<html>
""" % table)
def displayMissingAtomsPage():
uiserver.setCallback(missingAtomsPageCallback)
global missingAtoms
global missingTerminals
missingAtoms, missingTerminals = fixer.findMissingAtoms()
allResidues = list(set(missingAtoms.iterkeys()).union(missingTerminals.iterkeys()))
allResidues.sort(key=lambda x: x.index)
if len(allResidues) == 0:
displayDownloadPage()
return
table = ""
for residue in allResidues:
atoms = []
if residue in missingAtoms:
atoms.extend(atom.name for atom in missingAtoms[residue])
if residue in missingTerminals:
atoms.extend(atom for atom in missingTerminals[residue])
table += ' <tr><td>%d</td><td>%s %d</td><td>%s</td></tr>\n' % (residue.chain.index+1, residue.name, residue.index+1, ', '.join(atoms))
uiserver.setContent("""
<html>
<head><title>PDB Fixer</title></head>
<body>
The following residues are missing heavy atoms, which will be added.
<p>
<form method="get" action="/">
<table border="1">
<tr><th>Chain</th><th>Residue</th><th>Missing Atoms</th></tr>
%s
</table>
<p>
<input type="submit" value="Continue"/>
</form>
</body>
<html>
""" % table)
def displayDownloadPage():
uiserver.setCallback(downloadPageCallback)
uiserver.setContent("""
<html>
<head><title>PDB Fixer</title></head>
<body>
The fixed PDB file is ready to download.
<p>
<form method="get" action="/">
<input type="submit" name="download" value="Download"/>
<input type="submit" name="newfile" value="Process Another File"/>
</form>
</body>
<html>
""")
def launchUI():
uiserver.beginServing()
displayStartPage()
webbrowser.open('http://'+uiserver.serverAddress)
\ No newline at end of file
from threading import Thread
from SocketServer import ThreadingMixIn
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from urlparse import parse_qs
import cgi
class _Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.hasSentResponse = False
if callback is not None:
queryStart = self.path.find('?')
if queryStart > -1:
parameters = parse_qs(self.path[queryStart+1:])
else:
parameters = {}
result = callback(parameters, self)
if not self.hasSentResponse:
self.sendResponse(content)
def do_POST(self):
self.hasSentResponse = False
if callback is not None:
parameters = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD':'POST', 'CONTENT_TYPE':self.headers['Content-Type']})
callback(parameters, self)
if not self.hasSentResponse:
self.sendResponse(content)
def sendResponse(self, response):
self.hasSentResponse = True
self.send_response(200)
self.send_header("Content-type", "text/html")
self.send_header("Content-length", str(len(content)))
self.end_headers()
self.wfile.write(content)
def sendDownload(self, download, filename):
self.hasSentResponse = True
self.send_response(200)
self.send_header("Content-type", "text/plain")
self.send_header("Content-length", str(len(download)))
self.send_header("Content-Disposition", 'attachment; filename="%s"' % filename)
self.end_headers()
self.wfile.write(download)
class _ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
pass
content = ""
callback = None
server = _ThreadingHTTPServer(("localhost", 8000), _Handler)
serverAddress = server.server_address[0]+':'+str(server.server_address[1])
def beginServing():
Thread(target=server.serve_forever).start()
def setContent(newContent):
global content
content = newContent
def setCallback(newCallback):
global callback
callback = newCallback
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment