00001 '''
00002 Naccess module
00003
00004 Author: Florian Kiefer
00005
00006 This module is for calculating surface areas
00007 from OpenStructure using the external program naccess
00008
00009 How To Use This Module:
00010 1. Import it (e.g. as "from ost.bindings import naccess")
00011 2. Use it (e.g. as "sasa = naccess.CalculateSurfaceArea(entity)")
00012
00013 Requirement:
00014 - naccess installed
00015 '''
00016
00017 import tempfile
00018 import subprocess
00019 import os
00020 import re
00021 from ost import io
00022 from ost import mol
00023 from ost import settings
00024 from ost import geom
00025
00026 def _GetExecutable(naccess_exe):
00027 """
00028 Method to check if naccess executable is present
00029
00030 :param naccess: Explicit path to msms executable
00031 :returns: Path to the executable
00032 :exception: FileNotFound if executable is not found
00033 """
00034 return settings.Locate('naccess', explicit_file_name=naccess_exe)
00035
00036 def _SetupFiles(entity, selection, scratch_dir, max_number_of_atoms):
00037 """
00038 Setup files for naccess calculation in temporary directory
00039
00040 :param entity: EntityHandle or EntityView to calculate surface
00041 :param selection: Calculate surface for subset of entity
00042 :param scratch_dir: Directory for temporary files (NACCESS is sensitive to "." in directory names
00043 :param max_number_of_atoms: Max Number of atoms in the entity (i.e. is limited in the default NACCESS version to 50 000)
00044 :returns: array containing temporary directory, input filename for naccess and directory of the input file
00045 :exception: RuntimeError if selection is not valid
00046 """
00047
00048
00049 tmp_dir_name=""
00050 if scratch_dir!=None:
00051 tmp_dir_name=tempfile.mkdtemp(dir=scratch_dir)
00052 else:
00053 tmp_dir_name=tempfile.mkdtemp()
00054
00055
00056 entity_view=entity.Select(selection)
00057 if len(entity_view.atoms) > max_number_of_atoms:
00058 raise RuntimeError, "Too much atoms for NACCESS (> %s)" % max_number_of_atoms
00059 if not entity_view.IsValid():
00060 raise RuntimeError, "Could not create view for selection (%s)"%(selection)
00061
00062
00063 tmp_file_name=os.path.join(tmp_dir_name,"entity.pdb")
00064 tmp_file_base = os.path.join(tmp_dir_name,"entity")
00065 io.SavePDB(entity_view, tmp_file_name)
00066 return (tmp_dir_name, tmp_file_name, tmp_file_base)
00067
00068
00069 def _ParseAsaFile(entity, file, asa_atom):
00070 """
00071 Reads Area file (.asa) and attach asa per atom to an entitiy
00072
00073 :param entity: EntityHandle or EntityView for attaching sasa on atom level
00074 :param file: Filename of area file
00075 :param asa_atom: Name of the float property for SASA
00076 """
00077
00078 asa_fh = open(file)
00079 asa_lines = asa_fh.readlines()
00080 asa_fh.close()
00081
00082 for l in asa_lines:
00083 if l.startswith("ATOM"):
00084
00085 atom_name = l[12:16]
00086 chain_id = l[21]
00087 res_number = l[22:27]
00088 asa = l[54:63]
00089 atom_name = atom_name.strip()
00090 chain_id = chain_id
00091 res_number = res_number.strip()
00092 asa = asa.strip()
00093
00094 m=re.match(r'(?P<num>-?\d+)(?P<ins>\w)?', res_number)
00095 di = m.groupdict()
00096
00097 if di["ins"] == None:
00098 resNum = mol.ResNum(int(di["num"]))
00099 else:
00100 resNum = mol.ResNum(int(di["num"]), di["ins"])
00101
00102 a = entity.FindAtom(chain_id, resNum, atom_name)
00103 if(a.IsValid()):
00104 a.SetFloatProp(asa_atom, float(asa))
00105 else:
00106 print chain_id, resNum, atom_name
00107
00108 def _ParseRsaFile(enti,file, asa_abs, asa_rel):
00109 """
00110 Reads Area file (.rsa) and attach asa (absolute + relative) per residue to an entitiy
00111
00112 :param entity: EntityHandle or EntityView for attaching sasa on atom level
00113 :param file: Filename of .rsa file
00114 :param asa_atom: Name of the float property for absolute SASA
00115 :param asa_atom: Name of the float property for relative SASA
00116 :exception: RuntimeError if residue names are not the same
00117 """
00118 area_fh = open(file)
00119 area_lines = area_fh.readlines()
00120 area_fh.close()
00121
00122 area_lines = area_lines[4:]
00123
00124
00125 for l in area_lines:
00126 if l.startswith("RES"):
00127 p = re.compile(r'\s+')
00128 t = p.split(l)
00129
00130 res_name = l[3:8]
00131 res_name = res_name.strip()
00132 chain_id = l[8:9]
00133 res_number = l[9:14]
00134 res_number= res_number.strip()
00135
00136 abs_all, rel_all =l[15:28].strip().split()
00137 m=re.match(r'(?P<num>-?\d+)(?P<ins>\w)?', res_number)
00138 di = m.groupdict()
00139 if di["ins"] == None:
00140 resNum = mol.ResNum(int(di["num"]))
00141 else:
00142 resNum = mol.ResNum(int(di["num"]), di["ins"])
00143
00144 res = enti.handle.FindResidue(chain_id, resNum)
00145
00146
00147 if res_name == res.name:
00148 res.SetFloatProp(asa_rel, float(rel_all) )
00149 res.SetFloatProp(asa_abs, float(abs_all) )
00150 else:
00151 raise RuntimeError, "Residue Names are not the same for ResNumb: %s (%s vs %s)" % (res_number, res.name, res_name)
00152
00153
00154 def __CleanupFiles(dir_name):
00155 """
00156 Method which recursively deletes a directory
00157
00158 :warning: This method removes also non-empty directories without asking, so
00159 be careful!
00160 """
00161 import shutil
00162 shutil.rmtree(dir_name)
00163
00164 def _RunNACCESS(command, temp_dir):
00165 """
00166 Method to run the MSMS surface calculation
00167
00168 This method starts the external MSMS executable and returns the stdout of MSMS
00169
00170 :param command: Command to execute
00171 :returns: stdout of MSMS
00172 :exception: CalledProcessError for non-zero return value
00173 """
00174 proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, cwd = temp_dir)
00175 stdout_value, stderr_value = proc.communicate()
00176
00177
00178 if proc.returncode!=0:
00179 print "WARNING: naccess error\n", stdout_value
00180 raise subprocess.CalledProcessError(proc.returncode, command)
00181
00182 return stdout_value
00183
00184
00185 def CalculateSurfaceArea(entity, radius=1.4,
00186 include_hydrogens=False, include_hetatm = False,
00187 include_water = False, selection="",
00188 naccess_exe=None, keep_files=False , asa_abs= "asaAbs", asa_rel="asaRel", asa_atom="asaAtom", scratch_dir = None, max_number_of_atoms=50000):
00189 """
00190 Calculates analytical the solvent accessible surface area by using the
00191 external naccess program
00192
00193 This method calculates the molecular surface areas by invoking the external
00194 program naccess. First, it is checked if the naccess executable is present, then,
00195 the necessary files are prepared in a temporary directory and naccess is
00196 executed. The last step is to remove the temporary directory.
00197
00198
00199 :param entity: OST entity to calculate surface
00200 :param radius: Surface probe radius
00201 :param include_hydrogens: Calculate surface including hydrogens
00202 :param include_hetatm: Calculate surface including hetatms
00203 :param include_water: Calculate surface including water
00204 :param selection: Calculate surface for subset of entity
00205 :param naccess_exe: naccess executable (full path to executable)
00206 :param keep_files: Do not delete temporary files
00207 :param asa_abs: Attaches per residue absolute SASA to specified FloatProp on residue level
00208 :param asa_rel: Attaches per residue relative SASA to specified FloatProp on residue level
00209 :param asa_atom: Attaches per atom SASA to specified FloatProp at atom level
00210 :param scratch_dir: Directory for temporary files (NACCESS is sensitive to "." in directory names
00211 :param max_number_of_atoms: Max Number of atoms in the entity (i.e. is limited in the default NACCESS version to 50 000)
00212
00213 :returns: absolute SASA calculated using asa_atom
00214 """
00215
00216 import re
00217
00218
00219 naccess_executable=_GetExecutable(naccess_exe)
00220
00221
00222
00223 (naccess_data_dir, naccess_data_file,naccess_data_base )=_SetupFiles(entity, selection, scratch_dir, max_number_of_atoms)
00224
00225
00226 command="%s %s -p %f " % \
00227 (naccess_executable, naccess_data_file, radius)
00228 if include_hydrogens:
00229 command = "%s -w" % command
00230 if include_water:
00231 command = "%s -y" % command
00232 if include_hetatm:
00233 command = "%s -h" % command
00234
00235 stdout_value=_RunNACCESS(command, naccess_data_dir)
00236
00237 new_asa= os.path.join(naccess_data_dir, "%s.asa" % naccess_data_base)
00238 _ParseAsaFile(entity, new_asa, asa_atom)
00239
00240 new_rsa = os.path.join(naccess_data_dir, "%s.rsa" % naccess_data_base)
00241 _ParseRsaFile(entity, new_rsa, asa_abs, asa_rel)
00242
00243
00244 sasa = 0.0
00245 for a in entity.atoms:
00246 sasa += a.GetFloatProp(asa_atom, 0.0)
00247
00248
00249
00250
00251
00252
00253
00254 if not keep_files:
00255 __CleanupFiles(naccess_data_dir)
00256
00257 return sasa
00258