OpenStructure
naccess.py
Go to the documentation of this file.
1 '''
2 Naccess module
3 
4 Author: Florian Kiefer
5 
6 This module is for calculating surface areas
7 from OpenStructure using the external program naccess
8 
9 How To Use This Module:
10  1. Import it (e.g. as "from ost.bindings import naccess")
11  2. Use it (e.g. as "sasa = naccess.CalculateSurfaceArea(entity)")
12 
13 Requirement:
14  - naccess installed
15 '''
16 
17 import tempfile
18 import subprocess
19 import os
20 import re
21 from ost import io
22 from ost import mol
23 from ost import settings
24 from ost import geom
25 
26 ## \brief Method to check if naccess executable is present
27 #
28 # \param naccess Explicit path to msms executable
29 # \return Path to the executable
30 # \exception FileNotFound if executable is not found
31 def _GetExecutable(naccess_exe):
32  return settings.Locate('naccess', explicit_file_name=naccess_exe)
33 
34 ## \brief Setup files for naccess calculation in temporary directory
35 #
36 # \param entity EntityHandle or EntityView to calculate surface
37 # \param selection Calculate surface for subset of entity
38 # \return array containing temporary directory, input filename for naccess and directory of the input file
39 # \exception RuntimeError if selection is not valid
40 def _SetupFiles(entity, selection):
41  # create temporary directory
42  tmp_dir_name=tempfile.mkdtemp()
43 
44  # select only heavy atoms if no_hydrogens is true
45  entity_view=entity.Select(selection)
46  if len(entity_view.atoms) > 50000:
47  raise RuntimeError, "Too much atoms for NACCESS (> 50 000)"
48  if not entity_view.IsValid():
49  raise RuntimeError, "Could not create view for selection (%s)"%(selection)
50 
51  # write entity to tmp file
52  tmp_file_name=os.path.join(tmp_dir_name,"entity.pdb")
53  tmp_file_base = os.path.join(tmp_dir_name,"entity")
54  io.SavePDB(entity_view, tmp_file_name)
55  return (tmp_dir_name, tmp_file_name, tmp_file_base)
56 
57 ## \brief Reads Area file (.asa) and attach asa per atom to an entitiy
58 #
59 # \param entity EntityHandle or EntityView for attaching sasa on atom level
60 # \param file Filename of area file
61 # \param asa_atom Name of the float property for SASA
62 
63 
64 def _ParseAsaFile(entity, file, asa_atom):
65 
66  asa_fh = open(file)
67  asa_lines = asa_fh.readlines()
68  asa_fh.close()
69 
70  for l in asa_lines:
71  if l.startswith("ATOM"):
72  # get res_number, chain_id and atom name
73  atom_name = l[12:16]
74  chain_id = l[21]
75  res_number = l[22:27]
76  asa = l[54:63]
77  atom_name = atom_name.strip()
78  chain_id = chain_id
79  res_number = res_number.strip()
80  asa = asa.strip()
81  #print "res_number:", res_number
82  m=re.match(r'(?P<num>-?\d+)(?P<ins>\w)?', res_number)
83  di = m.groupdict()
84 
85  if di["ins"] == None:
86  resNum = mol.ResNum(int(di["num"]))
87  else:
88  resNum = mol.ResNum(int(di["num"]), di["ins"])
89  #print res_number, resNum.num, resNum.ins
90  a = entity.FindAtom(chain_id, resNum, atom_name)
91  if(a.IsValid()):
92  a.SetFloatProp(asa_atom, float(asa))
93  else:
94  print chain_id, resNum, atom_name
95 
96 ## \brief Reads Area file (.rsa) and attach asa (absolute + relative) per residue to an entitiy
97 #
98 # \param entity EntityHandle or EntityView for attaching sasa on atom level
99 # \param file Filename of .rsa file
100 # \param asa_atom Name of the float property for absolute SASA
101 # \param asa_atom Name of the float property for relative SASA
102 # \exception RuntimeError if residue names are not the same
103 
104 def _ParseRsaFile(enti,file, asa_abs, asa_rel):
105  area_fh = open(file)
106  area_lines = area_fh.readlines()
107  area_fh.close()
108  # shift first line
109  area_lines = area_lines[4:]
110 
111 
112  for l in area_lines:
113  if l.startswith("RES"):
114  p = re.compile(r'\s+')
115  t = p.split(l)
116  #res_name, chain_id , res_number, abs_all, rel_all = t[1:6]
117  res_name = l[3:8]
118  res_name = res_name.strip()
119  chain_id = l[8:9]
120  res_number = l[9:14]
121  res_number= res_number.strip()
122  #print l[15:30]
123  abs_all, rel_all =l[15:28].strip().split()
124  m=re.match(r'(?P<num>-?\d+)(?P<ins>\w)?', res_number)
125  di = m.groupdict()
126  if di["ins"] == None:
127  resNum = mol.ResNum(int(di["num"]))
128  else:
129  resNum = mol.ResNum(int(di["num"]), di["ins"])
130 
131  res = enti.handle.FindResidue(chain_id, resNum)
132  #res = entity.FindResidue(chain_id, mol.ResNum(int(res_number)))
133  #print res_name, chain_id, res_number
134  if res_name == res.name:
135  res.SetFloatProp(asa_rel, float(rel_all) )
136  res.SetFloatProp(asa_abs, float(abs_all) )
137  else:
138  raise RuntimeError, "Residue Names are not the same for ResNumb: %s (%s vs %s)" % (res_number, res.name, res_name)
139 
140 ## \brief Method which recursively deletes a directory
141 #
142 # \warning This method removes also non-empty directories without asking, so
143 # be careful!
144 def __CleanupFiles(dir_name):
145  import shutil
146  shutil.rmtree(dir_name)
147 
148 ## \brief Method to run the MSMS surface calculation
149 #
150 # This method starts the external MSMS executable and returns the stdout of MSMS
151 #
152 # \param command Command to execute
153 # \return stdout of MSMS
154 # \exception CalledProcessError for non-zero return value
155 def _RunNACCESS(command, temp_dir):
156  proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, cwd = temp_dir)
157  stdout_value, stderr_value = proc.communicate()
158 
159  #check for successful completion of msms
160  if proc.returncode!=0:
161  print "WARNING: naccess error\n", stdout_value
162  raise subprocess.CalledProcessError(proc.returncode, command)
163 
164  return stdout_value
165 
166 
167 ## \brief Calculates analytical the solvent accessible surface
168 # area by using the external naccess program
169 #
170 # This method calculates the molecular surface areas by invoking the external
171 # program naccess. First, it is checked if the naccess executable is present, then,
172 # the necessary files are prepared in a temporary directory and naccess is
173 # executed. The last step is to remove the temporary directory.
174 #
175 #
176 # \param entity OST entity to calculate surface
177 # \param radius Surface probe radius
178 # \param include_hydrogens Calculate surface including hydrogens
179 # \param include_hetatm Calculate surface including hetatms
180 # \param include_water Calculate surface including water
181 # \param selection Calculate surface for subset of entity
182 # \param naccess _exe msms executable (full path to executable)
183 # \param keep_files Do not delete temporary files
184 # \param asa_abs Attaches per residue absolute SASA to specified FloatProp on residue level
185 # \param asa_rel Attaches per residue relative SASA to specified FloatProp on residue level
186 # \param asa_atom Attaches per atom SASA to specified FloatProp at atom level
187 # \return absolute SASA calculated using asa_atom
188 def CalculateSurfaceArea(entity, radius=1.4,
189  include_hydrogens=False, include_hetatm = False,
190  include_water = False, selection="",
191  naccess_exe=None, keep_files=False , asa_abs= "asaAbs", asa_rel="asaRel", asa_atom="asaAtom"):
192  import re
193 
194  # check if msms executable is specified
195  naccess_executable=_GetExecutable(naccess_exe)
196  # parse selection
197 
198  # setup files for msms
199  (naccess_data_dir, naccess_data_file,naccess_data_base )=_SetupFiles(entity, selection)
200 
201  # set command line
202  command="%s %s -p %f " % \
203  (naccess_executable, naccess_data_file, radius)
204  if include_hydrogens:
205  command = "%s -w" % command
206  if include_water:
207  command = "%s -y" % command
208  if include_hetatm:
209  command = "%s -h" % command
210  #print command
211  stdout_value=_RunNACCESS(command, naccess_data_dir)
212 
213  new_asa= os.path.join(naccess_data_dir, "%s.asa" % naccess_data_base)
214  _ParseAsaFile(entity, new_asa, asa_atom)
215 
216  new_rsa = os.path.join(naccess_data_dir, "%s.rsa" % naccess_data_base)
217  _ParseRsaFile(entity, new_rsa, asa_abs, asa_rel)
218 
219  # Calculate Asa for all atoms:
220  sasa = 0.0
221  for a in entity.atoms:
222  sasa += a.GetFloatProp(asa_atom, 0.0)
223 
224 
225  # first read in result_file
226 
227  # parse MSMS output
228 
229  # clean up
230  if not keep_files:
231  __CleanupFiles(naccess_data_dir)
232 
233  return sasa
234