|
|
(3 intermediate revisions by 2 users not shown) |
Line 1: |
Line 1: |
| {{Infobox script-repo | | {{Infobox script-repo |
| |type = script | | |type = script |
− | |download = Submitted to the [[Git_intro|Pymol-script-repo]] as flatten_obj.py | + | |filename = flatten_obj.py |
| |author = [[User:sbliven|Spencer Bliven]] | | |author = [[User:sbliven|Spencer Bliven]] |
| |license = Public Domain | | |license = Public Domain |
Line 59: |
Line 59: |
| flatten_obj flat, nmrObj | | flatten_obj flat, nmrObj |
| flatten_obj ( obj1 or obj2 ) | | flatten_obj ( obj1 or obj2 ) |
− |
| |
− | = PyMol API =
| |
− |
| |
− | cmd.flatten_obj(string name, string selection)
| |
− |
| |
− |
| |
− |
| |
− | = Code =
| |
− |
| |
− | <syntaxhighlight lang="python">
| |
− | """
| |
− | flatten_obj.py - Flatten multi-state pymol objects into a single state.
| |
− |
| |
− | This is particularly useful for dealing with biological assemblies, which are
| |
− | loaded as multi-state objects when fetched using `fetch PDBID, type=pdb1`. It
| |
− | can also be used as a quick way to combine multiple objects without causing
| |
− | collisions between chain identifiers.
| |
− |
| |
− | The command re-letters chains to avoid collisions. Older versions of PyMOL
| |
− | restrict the chain id to a single character, so the script will fail for
| |
− | assemblies with >62 chains. With more recent versions, this problem is solved
| |
− | with multi-character chain IDs. Several options are available for how
| |
− | re-lettering should occur.
| |
− |
| |
− | Author: Spencer Bliven <spencer.bliven@gmail.com>
| |
− | Date: October 30, 2015
| |
− | Version: 1.0
| |
− | License: Public Domain
| |
− | """
| |
− | from pymol import cmd, stored
| |
− | import re
| |
− | try:
| |
− | from collections import OrderedDict
| |
− | _orderedDict = True
| |
− | except ImportError:
| |
− | _orderedDict = False
| |
− |
| |
− | # PyMOL 1.7.4 introduces support for multi-letter chains, so we can afford to
| |
− | # use a smaller alphabet. In earlier versions, use lower-case letters if needed
| |
− | # (requires running `set ignore_case, 0`)
| |
− | _long_chains = cmd.get_version()[1] >= 1.74
| |
− | _default_base = 36 if _long_chains else 62
| |
− |
| |
− | class OutOfChainsError(Exception):
| |
− | def __init__(self,msg):
| |
− | self.msg=msg
| |
− | def __str__(self):
| |
− | return str(self.msg)
| |
− |
| |
− | class ChainSet(object):
| |
− | """
| |
− | Base class for various methods to rename chains
| |
− |
| |
− | Contains _chains, which maps from the renamed chain to a tuple with the
| |
− | original (object,state,chain). All dict-like accessors work on ChainSets,
| |
− | e.g.
| |
− | chain_set["A"] -> ("obj",1,"A")
| |
− |
| |
− | """
| |
− | def __init__(self):
| |
− | # Use an OrderedDict in Python >= 1.7 for better printing
| |
− | if _orderedDict:
| |
− | self._chains = OrderedDict()
| |
− | else:
| |
− | self._chains = dict()
| |
− |
| |
− | def map_chain(self, obj, state, origChain ):
| |
− | """
| |
− | map_chain(string obj,int state, string chain]]) -> string
| |
− |
| |
− | Maps a chain letter to a unique chainID. Results are unique within each
| |
− | instance, and can be used as keys on this chain set.
| |
− | """
| |
− | raise NotImplementedError("Base class")
| |
− |
| |
− | # delegate most methods to _chains
| |
− | def __getattr__(self,at):
| |
− | if at in "pop popitem update setdefault".split():
| |
− | raise AttributeError("type object '%s' has no attribute '%s'"%(type(self),at))
| |
− | return getattr(self._chains,at)
| |
− | def __cmp__(self,other): return self._chains.__cmp__(other)
| |
− | def __eq__(self,other): return self._chains.__eq__(other)
| |
− | def __ge__(self,other): return self._chains.__ge__(other)
| |
− | def __gt__(self,other): return self._chains.__gt__(other)
| |
− | def __le__(self,other): return self._chains.__le__(other)
| |
− | def __lt__(self,other): return self._chains.__lt__(other)
| |
− | def __ne__(self,other): return self._chains.__ne__(other)
| |
− | def __len__(self): return self._chains.__len__()
| |
− | def __contains__(self,key): return self._chains.__contains__(key)
| |
− | def __getitem__(self,key): return self._chains.__getitem__(key)
| |
− | def __iter__(self): return self._chains.__iter__()
| |
− | def __str__(self): return str(self._chains)
| |
− |
| |
− | @staticmethod
| |
− | def _int_to_chain(i,base=_default_base):
| |
− | """
| |
− | _int_to_chain(int,int) -> str
| |
− |
| |
− | Converts a positive integer to a chain ID. Chain IDs include uppercase
| |
− | characters, numbers, and optionally lowercase letters.
| |
− |
| |
− | i = a positive integer to convert
| |
− | base = the alphabet size to include. Typically 36 or 62.
| |
− | """
| |
− | if i < 0:
| |
− | raise ValueError("positive integers only")
| |
− | if base < 0 or 62 < base:
| |
− | raise ValueError("Invalid base")
| |
− |
| |
− | quot = int(i)/base
| |
− | rem = i%base
| |
− | if rem < 26:
| |
− | letter = chr( ord("A") + rem)
| |
− | elif rem < 36:
| |
− | letter = str( rem-26)
| |
− | else:
| |
− | letter = chr( ord("a") + rem - 36)
| |
− | if quot == 0:
| |
− | return letter
| |
− | else:
| |
− | return ChainSet._int_to_chain(quot-1,base) + letter
| |
− |
| |
− |
| |
− | class DefaultChainSet(ChainSet):
| |
− | """
| |
− | Avoids relettering chains if possible. If a chain has been used, uses the
| |
− | next available chain letter. Note that this can potentially lead to
| |
− | cascading renames, e.g. if chains are sorted alphabetically rather than by
| |
− | object.
| |
− |
| |
− | Used for rename = 0.
| |
− | """
| |
− | def __init__(self):
| |
− | super(DefaultChainSet,self).__init__()
| |
− | self._next_chain = 0
| |
− | def map_chain(self, obj, state, origChain ):
| |
− | # Keep _next_chain up-to-date
| |
− | while ChainSet._int_to_chain(self._next_chain) in self:
| |
− | self._next_chain += 1
| |
− | # Map this chain
| |
− | if origChain in self:
| |
− | # Rename
| |
− | next_chain = ChainSet._int_to_chain(self._next_chain)
| |
− | self._next_chain += 1
| |
− | else:
| |
− | next_chain = origChain
| |
− | self._chains[next_chain] = (obj,state,origChain)
| |
− | return next_chain
| |
− |
| |
− | class SequentialChainSet(ChainSet):
| |
− | """
| |
− | Renumbers all chains starting at A, continuing through the capital letters
| |
− | and numbers, and then adding additional letters through 9999 (the last
| |
− | valid chain for mmCIF) and beyond.
| |
− |
| |
− | Used for rename=1
| |
− | """
| |
− | def __init__(self):
| |
− | super(SequentialChainSet,self).__init__()
| |
− | self._next_chain = 0
| |
− |
| |
− | def map_chain(self, obj, state, origChain ):
| |
− | next_chain = ChainSet._int_to_chain(self._next_chain)
| |
− | self._chains[next_chain] = (obj,state,origChain)
| |
− | self._next_chain += 1
| |
− | return next_chain
| |
− |
| |
− | class LongChainSet(ChainSet):
| |
− | """
| |
− | Uses long strings for the chain names. Chains are renamed like
| |
− | "%s_%s_%04d"%(original_chainid,objectname,state).
| |
− |
| |
− | Used for rename=2
| |
− | """
| |
− | def map_chain(self, obj, state, origChain ):
| |
− | ch = "%s_%s_%04d"%(origChain,obj,state)
| |
− | if self.has_key(ch):
| |
− | raise ValueError("Duplicate chain %s"%(ch))
| |
− | self._chains[ch] = (obj,state,origChain)
| |
− | return ch
| |
− |
| |
− |
| |
− |
| |
− |
| |
− | def flatten_obj(name="",selection="",state=0,rename=0,quiet=1,chain_map=""):
| |
− | """
| |
− | DESCRIPTION
| |
− |
| |
− | "flatten_obj" combines multiple objects or states into a single object,
| |
− | renaming chains where required
| |
− |
| |
− | USAGE
| |
− |
| |
− | flatten_obj name, selection[, state[, rename[, quiet[, chain_map]]]]
| |
− |
| |
− | ARGUMENTS
| |
− |
| |
− | name = a unique name for the flattened object {default: flat}
| |
− |
| |
− | selection = the set of objects to include in the flattening. The selection
| |
− | will be expanded to include all atoms of objects. {default: all}
| |
− |
| |
− | state = the source state to select. Use 0 or -1 to flatten all states {default: 0}
| |
− |
| |
− | rename = The scheme to use for renaming chains: {default: 0}
| |
− | (0) preserve chains IDs where possible, rename other chains
| |
− | alphabetically
| |
− | (1) rename all chains alphabetically
| |
− | (2) rename chains using the original chain letter, object name, and state
| |
− |
| |
− | quiet = If set to 0, print some additional information about progress and
| |
− | chain renaming {default: 1}
| |
− |
| |
− | chain_map = An attribute name for the 'stored' scratch object. If
| |
− | specified, `stored.<chain_map>` will be populated with a dictionary
| |
− | mapping the new chain names to a tuple giving the originated object,
| |
− | state, and chainID. {default: ""}
| |
− |
| |
− | NOTES
| |
− |
| |
− | Like the select command, if name is omitted then the default object name
| |
− | ("flat") is used as the name argument.
| |
− |
| |
− | Chain renaming is tricky. PDB files originally limited chains to single
| |
− | letter identifiers containing [A-Za-z0-9]. When this was found to be
| |
− | limiting, multi-letter chains (ideally < 4 chars) were allowed. This is
| |
− | supported as of PyMOL 1.7. Earlier versions do not accept rename=2, and
| |
− | will raise an exception when flattening a structure with more than 62
| |
− | chains.
| |
− |
| |
− | EXAMPLES
| |
− |
| |
− | flatten_obj flat, nmrObj
| |
− | flatten_obj ( obj1 or obj2 )
| |
− |
| |
− | PYMOL API
| |
− |
| |
− | cmd.flatten_obj(string name, string selection)
| |
− |
| |
− | SEE ALSO
| |
− |
| |
− | split_states
| |
− |
| |
− | """
| |
− |
| |
− | # arguments
| |
− |
| |
− | # Single argument; treat as selection
| |
− | if name and not selection:
| |
− | selection = name
| |
− | name = ""
| |
− | # default name and selection
| |
− | if not name:
| |
− | name = "flat"
| |
− | if not selection:
| |
− | selection = "(all)"
| |
− |
| |
− | state = int(state)
| |
− | rename = int(rename)
| |
− | quiet = int(quiet)
| |
− |
| |
− | # Wrap in extra parantheses for get_object_list
| |
− | selection = "( %s )" % selection
| |
− |
| |
− | if rename == 0:
| |
− | chainSet = DefaultChainSet()
| |
− | elif rename == 1:
| |
− | chainSet = SequentialChainSet()
| |
− | elif rename == 2:
| |
− | chainSet = LongChainSet()
| |
− | else:
| |
− | raise ValueError("Unrecognized rename option (Valid: 0,1,2)")
| |
− |
| |
− | metaprefix = "temp" #TODO unique prefix
| |
− |
| |
− | # store original value of retain_order, which causes weird interleaving of
| |
− | # structures if enabled.
| |
− | retain_order = cmd.get("retain_order")
| |
− | try:
| |
− | cmd.set("retain_order",0)
| |
− |
| |
− | # create new object for each state
| |
− | for obj in cmd.get_object_list(selection):
| |
− |
| |
− | if state <= 0:
| |
− | # all states
| |
− | prefix = "%s_%s_"%(metaprefix,obj)
| |
− | cmd.split_states(obj,prefix=prefix)
| |
− | else:
| |
− | prefix = "%s_%s_%04d"%(metaprefix,obj,state)
| |
− | cmd.create(prefix, obj, state, 1)
| |
− |
| |
− | # renumber all states
| |
− | statere = re.compile("^%s_(.*)_(\d+)$" % metaprefix) # matches split object names
| |
− |
| |
− | warn_lowercase = False
| |
− |
| |
− | # Iterate over all objects with metaprefix
| |
− | try:
| |
− | for obj in cmd.get_object_list("(%s_*)"%(metaprefix) ):
| |
− | m = statere.match(obj)
| |
− | if m is None:
| |
− | print("Failed to match object %s" %obj)
| |
− | continue
| |
− | origobj = m.group(1)
| |
− | statenum = int(m.group(2))
| |
− |
| |
− | chains = set([atom.chain for atom in cmd.get_model(obj).atom])
| |
− |
| |
− | rev_chain_map = {} #old -> new, for this obj only
| |
− | for chain in sorted(chains):
| |
− | new_chain = chainSet.map_chain(origobj,statenum,chain)
| |
− | rev_chain_map[chain] = new_chain
| |
− | if not quiet:
| |
− | print(" %s state %d chain %s -> %s"%(origobj,statenum,chain, new_chain) )
| |
− | if not _long_chains:
| |
− | if len(new_chain) > 1:
| |
− | raise OutOfChainsError("No additional chains available (max 62).")
| |
− |
| |
− | try:
| |
− | stored_name = stored.get_unused_name()
| |
− | except AttributeError:
| |
− | # backup solution for PyMOL < 1.6
| |
− | stored_name = "_flatten_obj_chainMapping"
| |
− |
| |
− | setattr(stored,stored_name,rev_chain_map)
| |
− | cmd.alter(obj,"chain = stored.%s[chain]"%stored_name)
| |
− | delattr(stored,stored_name)
| |
− |
| |
− | print("Creating object from %s_*"%metaprefix)
| |
− | # Recombine into a single object
| |
− | cmd.create(name,"%s_*"%metaprefix)
| |
− |
| |
− | # Set chain_map
| |
− | if chain_map:
| |
− | setattr(stored,chain_map,chainSet)
| |
− |
| |
− | # Warn if lowercase chains were generated
| |
− | if cmd.get("ignore_case") == "on" and any([c.upper() != c for c in chainSet.keys()]):
| |
− | print("Warning: using lower-case chain IDs. Consider running the "
| |
− | "following command:\n set ignore_case, 0" )
| |
− |
| |
− | finally:
| |
− | # Clean up
| |
− | print("Cleaning up intermediates")
| |
− | cmd.delete("%s_*"%metaprefix)
| |
− | finally:
| |
− | # restore original parameters
| |
− | print("Resetting variables")
| |
− | cmd.set("retain_order",retain_order)
| |
− |
| |
− |
| |
− | cmd.extend('flatten_obj', flatten_obj)
| |
− |
| |
− | # tab-completion of arguments
| |
− | cmd.auto_arg[0]['flatten_obj'] = [ cmd.object_sc, 'name or selection', '']
| |
− | cmd.auto_arg[1]['flatten_obj'] = [ cmd.object_sc, 'selection', '']
| |
− | </syntaxhighlight>
| |
| | | |
| = See Also = | | = See Also = |