def default_command_library_name(self): available_libs = self.library_names() for lib_name in available_libs: if "Dunbrack" in lib_name: lib = lib_name break else: if available_libs: lib = list(available_libs)[0] else: raise LimitationError("No rotamer libraries installed") return lib
def __init__(self, session, frames): Drawing.__init__(self, 'cross fade') self.frames = frames self.frame = 0 self.rgba = None has_graphics = session.main_view.render is not None if not has_graphics: raise LimitationError( "Unable to do crossfade without rendering images") self.capture_image(session)
def label_listfonts(session): '''Report available fonts.''' has_graphics = session.main_view.render is not None if not has_graphics: from chimerax.core.errors import LimitationError raise LimitationError( "Unable to do list fonts without being able to render images") from PyQt5.QtGui import QFontDatabase fdb = QFontDatabase() fnames = list(fdb.families()) fnames.sort() session.logger.info('%d fonts available:\n%s' % (len(fnames), '\n'.join(fnames)))
def _menu_show_cb(self, menu, installed_only): menu.clear() names = self.library_names(installed_only=installed_only) if not names: raise LimitationError( "No rotamer libraries %s!" % ("installed" if installed_only else "available")) names.sort() installed = set(self.library_names(installed_only=True)) for name in names: if name in installed: menu.addAction(name) else: menu.addAction(name + self._uninstalled_suffix)
def cmd_save(session, file_name, rest_of_line, *, log=True): tokens = [] remainder = rest_of_line while remainder: token, token_log, remainder = next_token(remainder) remainder = remainder.lstrip() tokens.append(token) format_name = None for i in range(len(tokens)-2, -1, -2): test_token = tokens[i].lower() if "format".startswith(test_token): format_name = tokens[i+1] provider_cmd_text = "save " + " ".join([FileNameArg.unparse(file_name)] + [StringArg.unparse(token) for token in tokens]) try: from .manager import NoSaverError mgr = session.save_command data_format= file_format(session, file_name, format_name) try: provider_args = mgr.save_args(data_format) except NoSaverError as e: raise LimitationError(str(e)) # register a private 'save' command that handles the provider's keywords registry = RegisteredCommandInfo() keywords = { 'format': DynamicEnum(lambda ses=session: format_names(ses)), } for keyword, annotation in provider_args.items(): if keyword in keywords: raise ValueError("Save-provider keyword '%s' conflicts with builtin arg" " of same name" % keyword) keywords[keyword] = annotation # for convenience, allow 'models' to be a second positional argument instead of a keyword if 'models' in keywords: optional = [('models', keywords['models'])] del keywords['models'] else: optional = [] desc = CmdDesc(required=[('file_name', SaveFileNameArg)], optional=optional, keyword=keywords.items(), hidden=mgr.hidden_args(data_format), synopsis="unnecessary") register("save", desc, provider_save, registry=registry) except BaseException as e: # want to log command even for keyboard interrupts log_command(session, "save", provider_cmd_text, url=_main_save_CmdDesc.url) raise Command(session, registry=registry).run(provider_cmd_text, log=log)
def register_attr(session, class_obj, attr_name, attr_type): if hasattr(class_obj, 'register_attr'): from chimerax.atomic.attr_registration import RegistrationConflict try: class_obj.register_attr(session, attr_name, "setattr command", attr_type=attr_type) except RegistrationConflict as e: from chimerax.core.errors import LimitationError raise LimitationError(str(e)) else: session.logger.warning( "Class %s does not support attribute registration; '%s' attribute" " will not be preserved in sessions." % (class_obj.__name__, attr_name))
def unused_chain_id(structure): from string import ascii_uppercase as uppercase, ascii_lowercase as lowercase, digits existing_ids = set([chain.chain_id for chain in structure.chains]) for chain_characters in [uppercase, uppercase + digits + lowercase]: for id_length in range(1, 5): chain_id = _gen_chain_id(existing_ids, "", chain_characters, id_length - 1) if chain_id: break else: continue break if chain_id is None: from chimerax.core.errors import LimitationError raise LimitationError( "Could not find unused legal chain ID for peptide!") return chain_id
def __init__(self, viewer): Drawing.__init__(self, 'motion blur') self.viewer = viewer self.rgba = None self.decay_factor = 0.9 ''' The Nth previous rendered frame is dimmed by the decay factor to the Nth power. The dimming is achieved by fading to the current background color. ''' self.attenuate = 0.5 "All preceding frames are additionally dimmed by this factor." self.changed = True has_graphics = viewer.render is not None if not has_graphics: raise LimitationError( "Unable to do motion blur without rendering images") self.capture_image()
def fetch_info(mgr, file_arg, format_name, database_name): if not database_name and exists_locally(file_arg, format_name): return None if ':' in file_arg: db_name, ident = file_arg.split(':', maxsplit=1) if len(db_name) < 2: return None elif database_name: db_name = database_name ident = file_arg elif likely_pdb_id(file_arg, format_name): db_name = "pdb" ident = file_arg else: return None from .manager import NoOpenerError try: db_formats = list(mgr.database_info(db_name).keys()) except NoOpenerError as e: raise LimitationError(str(e)) if format_name and format_name not in db_formats: # for backwards compatibiity, accept formal format name or nicknames try: df = mgr.session.data_formats[format_name] except KeyError: nicks = [] else: nicks = df.nicknames + [df.name] for nick in nicks: if nick in db_formats: format_name = nick break else: from chimerax.core.commands import commas raise UserError("Format '%s' not supported for database '%s'. Supported" " formats are: %s" % (format_name, db_name, commas([dbf for dbf in db_formats]))) return (ident, db_name, format_name)
def run(self, port, use_ssl): from http.server import HTTPServer import sys if port is None: # Defaults to any available port port = 0 if use_ssl is None: # Defaults to cleartext use_ssl = False self.httpd = HTTPServer(("localhost", port), RESTHandler) self.httpd.chimerax_restserver = self if not use_ssl: proto = "http" else: proto = "https" try: import os.path, ssl except ImportError: from chimerax.core.errors import LimitationError raise LimitationError("SSL is not supported") context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) cert = os.path.join(os.path.dirname(__file__), "server.pem") context.load_cert_chain(cert) # self.httpd.socket = ssl.wrap_socket(self.httpd.socket, # certfile=cert) self.httpd.socket = context.wrap_socket(self.httpd.socket, server_side=True) self.run_increment() # To match decrement in terminate() host, port = self.httpd.server_address msg = ("REST server started on host %s port %d" % (host, port)) self.session.ui.thread_safe(print, msg, file=sys.__stdout__, flush=True) msg += ('\nVisit %s://%s:%d/cmdline.html for CLI interface' % (proto, host, port)) self.session.ui.thread_safe(self.session.logger.info, msg) self.httpd.serve_forever()
def save_image(session, path, format_name, width=None, height=None, supersample=3, pixel_size=None, transparent_background=False, quality=95): ''' Save an image of the current graphics window contents. ''' from chimerax.core.errors import UserError, LimitationError has_graphics = session.main_view.render is not None if not has_graphics: raise LimitationError( "Unable to save images because OpenGL rendering is not available") from os.path import dirname, exists dir = dirname(path) if dir and not exists(dir): raise UserError('Directory "%s" does not exist' % dir) if pixel_size is not None: if width is not None or height is not None: raise UserError( 'Cannot specify width or height if pixel_size is given') v = session.main_view b = v.drawing_bounds() if b is None: raise UserError( 'Cannot specify use pixel_size option when nothing is shown') psize = v.pixel_size(b.center()) if psize > 0 and pixel_size > 0: f = psize / pixel_size w, h = v.window_size from math import ceil width, height = int(ceil(f * w)), int(ceil(f * h)) else: raise UserError( 'Pixel size option (%g) and screen pixel size (%g) must be positive' % (pixel_size, psize)) from chimerax.core.session import standard_metadata std_metadata = standard_metadata() metadata = {} if format_name == 'PNG': metadata['optimize'] = True # if dpi is not None: # metadata['dpi'] = (dpi, dpi) if session.main_view.render.opengl_context.pixel_scale() == 2: metadata['dpi'] = (144, 144) from PIL import PngImagePlugin pnginfo = PngImagePlugin.PngInfo() # tags are from <https://www.w3.org/TR/PNG/#11textinfo> def add_text(keyword, value): try: b = value.encode('latin-1') except UnicodeEncodeError: pnginfo.add_itxt(keyword, value) else: pnginfo.add_text(keyword, b) # add_text('Title', description) add_text('Creation Time', std_metadata['created']) add_text('Software', std_metadata['generator']) add_text('Author', std_metadata['creator']) add_text('Copy' 'right', std_metadata['dateCopyrighted']) metadata['pnginfo'] = pnginfo elif format_name == 'TIFF': # metadata['compression'] = 'lzw:2' # metadata['description'] = description metadata['software'] = std_metadata['generator'] # TIFF dates are YYYY:MM:DD HH:MM:SS (local timezone) import datetime as dt metadata['date_time'] = dt.datetime.now().strftime('%Y:%m:%d %H:%M:%S') metadata['artist'] = std_metadata['creator'] # TIFF copy right is ASCII, so no Unicode symbols cp = std_metadata['dateCopyrighted'] if cp[0] == '\N{COPYRIGHT SIGN}': cp = 'Copy' 'right' + cp[1:] metadata['copy' 'right'] = cp # if units == 'pixels': # dpi = None # elif units in ('points', 'inches'): # metadata['resolution unit'] = 'inch' # metadata['x resolution'] = dpi # metadata['y resolution'] = dpi # elif units in ('millimeters', 'centimeters'): # adjust = convert['centimeters'] / convert['inches'] # dpcm = dpi * adjust # metadata['resolution unit'] = 'cm' # metadata['x resolution'] = dpcm # metadata['y resolution'] = dpcm elif format_name == 'JPEG': metadata['quality'] = quality # if dpi is not None: # # PIL's jpeg_encoder requires integer dpi values # metadata['dpi'] = (int(dpi), int(dpi)) # TODO: create exif with metadata using piexif package? # metadata['exif'] = exif view = session.main_view view.render.make_current() max_size = view.render.max_framebuffer_size() if max_size and ((width is not None and width > max_size) or (height is not None and height > max_size)): raise UserError( 'Image size %d x %d too large, exceeds maximum OpenGL render buffer size %d' % (width, height, max_size)) i = view.image(width, height, supersample=supersample, transparent_background=transparent_background) if i is not None: try: i.save(path, format_name, **metadata) except PermissionError: from chimerax.core.errors import UserError raise UserError('Permission denied writing file %s' % path) else: msg = "Unable to save image" if width is not None: msg += ', width %d' % width if height is not None: msg += ', height %d' % height session.logger.warning(msg)
def use_rotamer(session, res, rots, retain=False, log=False, bfactor=None): """Takes a Residue instance and either a list or dictionary of rotamers (as returned by get_rotamers, i.e. with backbone already matched) and swaps the Residue's side chain with the given rotamers. If the rotamers are a dictionary, then the keys should match the alt locs of the CA atom, and the corresponding rotamer will be used for that alt loc. If the alt locs are a list, if the list has only one rotamer then that rotamer will be used for each CA alt loc. If the list has multiple rotamers, then the CA must have only one alt loc (namely ' ') and all the rotamers will be attached, using different alt loc characters for each. If 'retain' is True, existing side chains will be retained. If 'bfactor' is None, then the current highest existing bfactor in the residue will be used. """ N = res.find_atom("N") CA = res.find_atom("CA") C = res.find_atom("C") if not N or not C or not CA: raise LimitationError( "N, CA, or C missing from %s: needed for side-chain pruning algorithm" % res) import string alt_locs = string.ascii_uppercase + string.ascii_lowercase + string.digits + string.punctuation if retain and CA.alt_locs: raise LimitationError( "Cannot retain side chains if multiple CA alt locs") ca_alt_locs = [' '] if not CA.alt_locs else CA.alt_locs if not isinstance(rots, dict): # reformat as dictionary if CA.alt_locs and len(rots) > 1: raise LimitationError( "Cannot add multiple rotamers to multi-position backbone") retained_alt_locs = side_chain_locs(res) if retain else [] num_retained = len(retained_alt_locs) if len(rots) + num_retained > len(alt_locs): raise LimitationError("Don't have enough unique alternate " "location characters to place %d rotamers." % len(rots)) if len(rots) + num_retained > 1: rots = { loc: rot for loc, rot in zip( [c for c in alt_locs if c not in retained_alt_locs][:len(rots)], rots) } else: rots = {alt_loc: rots[0] for alt_loc in ca_alt_locs} swap_type = list(rots.values())[0].residues[0].name if retain and res.name != swap_type: raise LimitationError( "Cannot retain side chains if rotamers are a different residue type" ) rot_anchors = {} for rot in rots.values(): rot_res = rot.residues[0] rot_N, rot_CA = rot_res.find_atom("N"), rot_res.find_atom("CA") if not rot_N or not rot_CA: raise LimitationError( "N or CA missing from rotamer: cannot matchup with original residue" ) rot_anchors[rot] = (rot_N, rot_CA) color_by_element = N.color != CA.color if color_by_element: carbon_color = CA.color else: uniform_color = N.color # prune old side chain bfactor = bfactor_for_res(res, bfactor) if not retain: res_atoms = res.atoms side_atoms = res_atoms.filter(res_atoms.is_side_onlys) serials = {a.name: a.serial_number for a in side_atoms} side_atoms.delete() else: serials = {} # for proline, also prune amide hydrogens if swap_type == "PRO": for nnb in N.neighbors[:]: if nnb.element.number == 1: N.structure.delete_atom(nnb) tot_prob = sum([r.rotamer_prob for r in rots.values()]) with CA.suppress_alt_loc_change_notifications(): res.name = swap_type from chimerax.atomic.struct_edit import add_atom, add_bond for alt_loc, rot in rots.items(): if CA.alt_locs: CA.alt_loc = alt_loc if log: extra = " using alt loc %s" % alt_loc if alt_loc != ' ' else "" session.logger.info( "Applying %s rotamer (chi angles: %s) to %s%s" % (rot_res.name, " ".join(["%.1f" % c for c in rot.chis]), res, extra)) # add new side chain rot_N, rot_CA = rot_anchors[rot] visited = set([N, CA, C]) sprouts = [rot_CA] while sprouts: sprout = sprouts.pop() built_sprout = res.find_atom(sprout.name) for nb in sprout.neighbors: built_nb = res.find_atom(nb.name) if tot_prob == 0.0: # some rotamers in Dunbrack are zero prob! occupancy = 1.0 / len(rots) else: occupancy = rot.rotamer_prob / tot_prob if not built_nb: serial = serials.get(nb.name, None) built_nb = add_atom(nb.name, nb.element, res, nb.coord, serial_number=serial, bonded_to=built_sprout, alt_loc=alt_loc) built_nb.occupancy = occupancy built_nb.bfactor = bfactor if color_by_element: if built_nb.element.name == "C": built_nb.color = carbon_color else: from chimerax.atomic.colors import element_color built_nb.color = element_color( built_nb.element.number) else: built_nb.color = uniform_color elif built_nb not in visited: built_nb.set_alt_loc(alt_loc, True) built_nb.coord = nb.coord built_nb.occupancy = occupancy built_nb.bfactor = bfactor if built_nb not in visited: sprouts.append(nb) visited.add(built_nb) if built_nb not in built_sprout.neighbors: add_bond(built_sprout, built_nb)
def get_rotamers(session, res, phi=None, psi=None, cis=False, res_type=None, rot_lib="Dunbrack", log=False): """Takes a Residue instance and optionally phi/psi angles (if different from the Residue), residue type (e.g. "TYR"), and/or rotamer library name. Returns a list of AtomicStructure instances (sublass of AtomicStructure). The AtomicStructure are each a single residue (a rotamer) and are in descending probability order. Each has an attribute "rotamer_prob" for the probability and "chis" for the chi angles. """ res_type = res_type or res.name if res_type == "ALA" or res_type == "GLY": raise NoResidueRotamersError("No rotamers for %s" % res_type) if not isinstance(rot_lib, RotamerLibrary): rot_lib = session.rotamers.library(rot_lib) # check that the residue has the n/c/ca atoms needed to position the rotamer # and to ensure that it is an amino acid from chimerax.atomic import Residue match_atoms = {} for bb_name in Residue.aa_min_backbone_names: match_atoms[bb_name] = a = res.find_atom(bb_name) if a is None: raise LimitationError("%s missing from %s; needed to position CB" % (bb_name, res)) match_atoms["CB"] = res.find_atom("CB") if not phi and not psi: phi, psi = res.phi, res.psi omega = res.omega cis = False if omega is None or abs(omega) > 90 else True if log: def _info(ang): if ang is None: return "none" return "%.1f" % ang if match_atoms["CA"].alt_locs: al_info = " (alt loc %s)" % match_atoms["CA"].alt_loc else: al_info = "" session.logger.info("%s%s: phi %s, psi %s %s" % (res, al_info, _info(phi), _info(psi), "cis" if cis else "trans")) session.logger.status("Retrieving rotamers from %s library" % rot_lib.display_name) res_template_func = rot_lib.res_template_func params = rot_lib.rotamer_params(res_type, phi, psi, cis=cis) session.logger.status("Rotamers retrieved from %s library" % rot_lib.display_name) mapped_res_type = rot_lib.res_name_mapping.get(res_type, res_type) template = rot_lib.res_template_func(mapped_res_type) tmpl_N = template.find_atom("N") tmpl_CA = template.find_atom("CA") tmpl_C = template.find_atom("C") tmpl_CB = template.find_atom("CB") if match_atoms['CB']: res_match_atoms, tmpl_match_atoms = [ match_atoms[x] for x in ("C", "CA", "CB") ], [tmpl_C, tmpl_CA, tmpl_CB] else: res_match_atoms, tmpl_match_atoms = [ match_atoms[x] for x in ("N", "CA", "C") ], [tmpl_N, tmpl_CA, tmpl_C] from chimerax.geometry import align_points from numpy import array xform, rmsd = align_points(array([fa.coord for fa in tmpl_match_atoms]), array([ta.coord for ta in res_match_atoms])) n_coord = xform * tmpl_N.coord ca_coord = xform * tmpl_CA.coord cb_coord = xform * tmpl_CB.coord info = Residue.chi_info[mapped_res_type] bond_cache = {} angle_cache = {} from chimerax.atomic.struct_edit import add_atom, add_dihedral_atom, add_bond structs = [] middles = {} ends = {} for i, rp in enumerate(params): s = AtomicStructure(session, name="rotamer %d" % (i + 1)) structs.append(s) r = s.new_residue(mapped_res_type, 'A', 1) registerer = "swap_res get_rotamers" AtomicStructure.register_attr(session, "rotamer_prob", registerer, attr_type=float) s.rotamer_prob = rp.p AtomicStructure.register_attr(session, "chis", registerer) s.chis = rp.chis rot_N = add_atom("N", tmpl_N.element, r, n_coord) rot_CA = add_atom("CA", tmpl_CA.element, r, ca_coord, bonded_to=rot_N) rot_CB = add_atom("CB", tmpl_CB.element, r, cb_coord, bonded_to=rot_CA) todo = [] for j, chi in enumerate(rp.chis): n3, n2, n1, new = info[j] b_len, angle = _len_angle(new, n1, n2, template, bond_cache, angle_cache) n3 = r.find_atom(n3) n2 = r.find_atom(n2) n1 = r.find_atom(n1) new = template.find_atom(new) a = add_dihedral_atom(new.name, new.element, n1, n2, n3, b_len, angle, chi, bonded=True) todo.append(a) middles[n1] = [a, n1, n2] ends[a] = [a, n1, n2] # if there are any heavy non-backbone atoms bonded to template # N and they haven't been added by the above (which is the # case for Richardson proline parameters) place them now for tnnb in tmpl_N.neighbors: if r.find_atom(tnnb.name) or tnnb.element.number == 1: continue tnnb_coord = xform * tnnb.coord add_atom(tnnb.name, tnnb.element, r, tnnb_coord, bonded_to=rot_N) # fill out bonds and remaining heavy atoms from chimerax.geometry import distance, align_points done = set([rot_N, rot_CA]) while todo: a = todo.pop(0) if a in done: continue tmpl_A = template.find_atom(a.name) for bonded, bond in zip(tmpl_A.neighbors, tmpl_A.bonds): if bonded.element.number == 1: continue rbonded = r.find_atom(bonded.name) if rbonded is None: # use middles if possible... try: p1, p2, p3 = middles[a] conn = p3 except KeyError: p1, p2, p3 = ends[a] conn = p2 t1 = template.find_atom(p1.name) t2 = template.find_atom(p2.name) t3 = template.find_atom(p3.name) xform = align_points( array([t.coord for t in [t1, t2, t3]]), array([p.coord for p in [p1, p2, p3]]))[0] pos = xform * template.find_atom(bonded.name).coord rbonded = add_atom(bonded.name, bonded.element, r, pos, bonded_to=a) middles[a] = [rbonded, a, conn] ends[rbonded] = [rbonded, a, conn] if a not in rbonded.neighbors: add_bond(a, rbonded) if rbonded not in done: todo.append(rbonded) done.add(a) return structs
def swap_aa(session, residues, res_type, *, bfactor=None, clash_hbond_allowance=None, clash_score_method="sum", clash_overlap_cutoff=None, criteria=default_criteria, density=None, hbond_angle_slop=None, hbond_dist_slop=None, hbond_relax=True, ignore_other_models=False, rot_lib=defaults['library'], log=True, preserve=None, retain=False): """backend implementation of "swapaa" command.""" rotamers = {} destroy_list = [] for res in residues: if res_type == "same": r_type = res.name else: r_type = res_type.upper() CA = res.find_atom("CA") if not CA: raise LimitationError("Residue %s is missing CA atom" % res) alt_locs = [' '] if CA.alt_loc == ' ' else CA.alt_locs with CA.suppress_alt_loc_change_notifications(): rotamers[res] = by_alt_loc = {} for alt_loc in alt_locs: CA.alt_loc = alt_loc try: rots = get_rotamers(session, res, res_type=r_type, rot_lib=rot_lib, log=log) except UnsupportedResTypeError: raise LimitationError( "%s rotamer library does not support %s" % (rot_lib, r_type)) except NoResidueRotamersError: if log: session.logger.info("Swapping %s to %s\n" % (res, r_type)) try: template_swap_res(res, r_type, bfactor=bfactor) except TemplateSwapError as e: raise UserError(str(e)) continue except NoRotamerLibraryError: raise UserError("No rotamer library named '%s'" % rot_lib) if preserve is not None: rots = prune_by_chis(session, rots, res, preserve, log=log) by_alt_loc[alt_loc] = rots destroy_list.extend(rots) if not by_alt_loc: del rotamers[res] if not rotamers: return if isinstance(criteria, str): # this implementation allows tie-breaking criteria to be skipped if # there are no ties cmp = lambda p1, p2: 1 if p1 > p2 else (0 if p1 == p2 else -1) for char in criteria: if char == "d": # density from chimerax.map import Volume maps = [m for m in session.models if isinstance(m, Volume)] if not maps: if criteria is default_criteria: continue raise UserError( "Density criteria requested but no volume models are open" ) elif len(maps) > 1: if density is None: raise UserError( "Density criteria with multiple volume models open;\n" "Need to specify one to use via 'density' keyword." ) map = density else: map = maps[0] for res, by_alt_loc in rotamers.items(): process_volume(session, res, by_alt_loc, map) fetch = lambda r: r.volume_score test = cmp elif char == "c": # clash if clash_hbond_allowance is None or clash_overlap_cutoff is None: from chimerax.clashes.settings import defaults if clash_hbond_allowance is None: clash_hbond_allowance = defaults[ 'clash_hbond_allowance'] if clash_overlap_cutoff is None: clash_overlap_cutoff = defaults['clash_threshold'] for res, by_alt_loc in rotamers.items(): process_clashes(session, res, by_alt_loc, clash_overlap_cutoff, clash_hbond_allowance, clash_score_method, False, None, None, ignore_other_models) fetch = lambda r: r.clash_score test = lambda s1, s2: cmp(s2, s1) # _lowest_ clash score elif char == 'h': # H bonds if hbond_angle_slop is None or hbond_dist_slop is None: from chimerax.hbonds import rec_angle_slop, rec_dist_slop if hbond_angle_slop is None: hbond_angle_slop = rec_angle_slop if hbond_dist_slop is None: hbond_dist_slop = rec_dist_slop session.logger.status("Processing H-bonds for %s" % res) for res, by_alt_loc in rotamers.items(): process_hbonds(session, res, by_alt_loc, False, None, None, hbond_relax, hbond_dist_slop, hbond_angle_slop, False, None, ignore_other_models, cache_da=True) session.logger.status("") from chimerax.hbonds import flush_cache flush_cache() fetch = lambda r: r.num_hbonds test = cmp elif char == 'p': # most probable fetch = lambda r: r.rotamer_prob test = cmp elif isinstance(criteria, int): # Nth most probable index = criteria - 1 for res, by_alt_loc in rotamers.items(): for alt_loc, rots in list(by_alt_loc.items()): if index >= len(rots): if log: session.logger.status( "Residue %s does not have %d %s" " rotamers; skipping" % (res, criteria, r_type), log=True, color="red") return by_alt_loc[alt_loc] = [rots[index]] fetch = lambda r: 1 test = lambda v1, v2: 1 still_multiple_choices = False for res, by_alt_loc in rotamers.items(): for alt_loc, rots in list(by_alt_loc.items()): if len(rots) == 1: continue best = None for rot in rots: val = fetch(rot) if best == None or test(val, best_val) > 0: best = [rot] best_val = val elif test(val, best_val) == 0: best.append(rot) by_alt_loc[alt_loc] = best if len(best) > 1: still_multiple_choices = True if not still_multiple_choices: break for res, by_alt_loc in rotamers.items(): for alt_loc, rots in list(by_alt_loc.items()): if len(rots) > 1: if log: session.logger.info("%s has %d equal-value rotamers;" " choosing one arbitrarily." % (res, len(rots))) by_alt_loc[alt_loc] = rots[0] use_rotamer(session, res, rotamers[res], retain=retain, log=log, bfactor=bfactor) else: # Nth-most-probable rotamer(s) for res, by_alt_loc in list(rotamers.items()): if len(by_alt_loc) > 1: if len(critera) > 1: raise LimitationError( "Cannot assign multiple rotamers to multiple alt locs") for alt_loc, rots in list(by_alt_loc.items()): try: by_alt_loc[alt_loc] = rots[criteria[0] - 1] except IndexError: raise UserError("Less that %d rotamers for %s" % (criteria[0], res)) else: rots = list(by_alt_loc.values())[0] try: p_rots = [rots[i - 1] for i in criteria] except IndexError: raise UserError("Only %d rotamers for %s" % (len(rots), res)) rotamers[res] = p_rots for res in rotamers: use_rotamer(session, res, rotamers[res], retain=retain, log=log, bfactor=bfactor) for rot in destroy_list: rot.delete()
def add_dimensions(name, info, session=None): from chimerax.core.errors import LimitationError raise LimitationError("Custom dimensions are not supported at this time") # TODO: rest of this """
def remove_dimensions(name): from chimerax.core.errors import LimitationError raise LimitationError("Custom dimensions are not supported at this time") # TODO: rest of this """
def label(session, objects=None, object_type=None, text=None, offset=None, color=None, bg_color=None, attribute=None, size=None, height=None, default_height=None, font=None, on_top=None): '''Create atom labels. The belong to a child model named "labels" of the structure. Parameters ---------- objects : Objects or None Create labels on specified atoms, residues, pseudobonds, or bonds. If None then adjust settings of all existing labels. object_type : 'atoms', 'residues', 'pseudobonds', 'bonds' What type of object to label. text : string or "default" Displayed text of the label. offset : float 3-tuple or "default" Offset of label from atom center in screen coordinates in physical units (Angstroms) color : Color or "default" Color of the label text. If no color is specified black is used on light backgrounds and white is used on dark backgrounds. bg_color : Color or "none" Draw rectangular label background in this color, or if "none", background is transparent. attribute : string Attribute name whose value to display as text size : int or "default" Font size in points (1/72 inch). Default 48. height : float or "fixed" Text height in scene units. Or if "fixed" use fixed pixel height on screen. Initial value 0.7. default_height : float Default height value if not specified. Initial value 0.7. font : string or "default" Font name. This must be a true type font installed on Mac in /Library/Fonts and is the name of the font file without the ".ttf" suffix. Default "Arial". on_top : bool Whether labels always appear on top of other graphics (cannot be occluded). This is a per-structure attribute. Default True. ''' if object_type is None: if objects is None: otypes = ['atoms', 'residues', 'pseudobonds', 'bonds'] elif len(objects.atoms) == 0: otypes = ['pseudobonds'] else: otypes = ['residues'] else: otypes = [object_type] from chimerax.core.errors import UserError if text is not None and attribute is not None: raise UserError("Cannot specify both 'text' and 'attribute' keywords") has_graphics = session.main_view.render is not None if not has_graphics: from chimerax.core.errors import LimitationError raise LimitationError( "Unable to draw 3D labels without rendering images") settings = {} if text == 'default': settings['text'] = None elif text is not None: settings['text'] = text if offset == 'default': settings['offset'] = None elif offset is not None: settings['offset'] = offset from chimerax.core.colors import Color if isinstance(color, Color): settings['color'] = color.uint8x4() elif color == 'default': settings['color'] = None if isinstance(bg_color, Color): settings['background'] = bg_color.uint8x4() elif bg_color == 'none': settings['background'] = None if size == 'default': settings['size'] = 48 elif size is not None: settings['size'] = size if height == 'fixed': settings['height'] = None elif height is not None: settings['height'] = height if default_height is not None: from .settings import settings as prefs prefs.label_height = default_height if font == 'default': settings['font'] = 'Arial' elif font is not None: settings['font'] = font if 'text' in settings: settings['attribute'] = False elif attribute is not None: settings['text'] = False settings['attribute'] = attribute if objects is None and len(settings) == 0 and on_top is None: return # Get this when setting default height. view = session.main_view lcount = 0 for otype in otypes: if objects is None: mo = labeled_objects_by_model(session, otype) else: mo = objects_by_model(objects, otype) object_class = label_object_class(otype) for m, mobjects in mo: lm = labels_model(m, create=True) lm.add_labels(mobjects, object_class, view, settings, on_top) lcount += len(mobjects) if objects is None and lcount == 0 and default_height is None: raise UserError( 'Label command requires an atom specifier to create labels.')
def label_create(session, name, text='', color=None, bg_color=None, size=24, font='Arial', bold=None, italic=None, xpos=0.5, ypos=0.5, visibility=True, margin=0, outline=0): '''Create a label at a fixed position in the graphics window. Parameters ---------- name : string Identifier for the label used to change or delete label. text : string Displayed text of the label. color : Color Color of the label text. If no color is specified black is used on light backgrounds and white is used on dark backgrounds. bg_color : Color Draw rectangular label background in this color. If omitted, background is transparent. size : int Font size in points. font : string Font name. This must be a true type font installed on Mac in /Library/Fonts and is the name of the font file without the ".ttf" suffix. xpos : float Placement of left edge of text. Range 0 - 1 covers full width of graphics window. ypos : float Placement of bottom edge of text. Range 0 - 1 covers full height of graphics window. visibility : bool Whether or not to display the label. margin : float Amount of padding to add around text outline : float width of contrasting outline to place around background/margin ''' if name == 'all': from chimerax.core.errors import UserError raise UserError("'all' is reserved to refer to all labels") elif name: lm = session_labels(session) if lm and lm.named_label(name) is not None: from chimerax.core.errors import UserError raise UserError('Label "%s" already exists' % name) kw = { 'text': text, 'color': color, 'size': size, 'font': font, 'bold': bold, 'italic': italic, 'xpos': xpos, 'ypos': ypos, 'visibility': visibility, 'margin': margin, 'outline_width': outline } from chimerax.core.colors import Color if isinstance(color, Color): kw['color'] = color.uint8x4() elif color == 'default': kw['color'] = None if isinstance(bg_color, Color): kw['background'] = bg_color.uint8x4() elif bg_color == 'none': kw['background'] = None has_graphics = session.main_view.render is not None if not has_graphics: from chimerax.core.errors import LimitationError raise LimitationError( "Unable to draw 2D labels without rendering images") return Label(session, name, **kw)
def match(session, chain_pairing, match_items, matrix, alg, gap_open, gap_extend, *, cutoff_distance=None, show_alignment=defaults['show_alignment'], align=align, domain_residues=(None, None), bring=None, verbose=defaults['verbose_logging'], always_raise_errors=False, keep_computed_ss=defaults['overwrite_ss'], **align_kw): """Superimpose structures based on sequence alignment Returns a list of tuples, one per chain pairing. The tuples are: (ref-Atoms-used, match-Atoms-used, paired-RMSD, overall-RMSD, transformation-matrix) "Atoms-used" means after any pruning due to iteration. 'chain_pairing' is the method of pairing chains to match: CP_SPECIFIC_SPECIFIC -- Each reference chain is paired with a specified match chain ('match_items' is sequence of (ref_chain, match_chain) tuples) CP_SPECIFIC_BEST -- Single reference chain is paired with best seq-aligning chain from one or more structures ('match_items' is (reference_chain, [match_structures])) CP_BEST_BEST -- Best seq-aligning pair of chains from reference structure and match structure(s) is used ('match_items' is (ref_structure, [match_structures])) 'matrix' is name of similarity matrix 'alg' is the alignment algorithm: AA_NEEDLEMAN_WUNSCH or AA_SMITH_WATERMAN 'gap_open' and 'gap_extend' are the gap open/extend penalties used for the initial sequence alignment 'cutoff_distance' is the cutoff used for iterative superposition -- iteration stops when all remaining distances are below the cutoff. If None, no iteration. 'show_alignment' controls whether the sequence alignment is also shown in a sequence viewer. 'align' allows specification of the actual function align/score one chain to another. See the align() function above. 'domain_residues' allows matching to be restricted to a subset of the chain(s). If given, should be (ref_Residues_collection, match_Residues_collection) 'bring' specifies other structures that should be transformed along with the match structure (so, there must be only one match structure in such a case). 'verbose', if True, produces additional output to the log, If None, the parameter table will not be logged. If 'always_raise_errors' is True, then an iteration that goes to too few matched atoms will immediately raise an error instead of noting the failure in the log and continuing on to other pairings. """ dssp_cache = {} try: alg = alg.lower() if alg == "nw" or alg.startswith("needle"): alg = "nw" alg_name = "Needleman-Wunsch" elif alg == "sw" or alg.startswith("smith"): alg = "sw" alg_name = "Smith-Waterman" else: raise ValueError("Unknown sequence alignment algorithm: %s" % alg) pairings = {} small_mol_err_msg = "Reference and/or match model contains no nucleic or"\ " amino acid chains.\nUse the command-line 'align' command" \ " to superimpose small molecules/ligands." rd_res, md_res = domain_residues from chimerax.sim_matrices import matrix_compatible if chain_pairing == CP_SPECIFIC_SPECIFIC: # specific chain(s) in each # various sanity checks # # (1) can't have same chain matched to multiple refs # (2) reference structure can't be a match structure match_chains = {} match_mols = {} ref_mols = {} for ref, match in match_items: if not matrix_compatible(ref, matrix, session.logger): raise UserError("Reference chain (%s) not" " compatible with %s similarity" " matrix" % (ref.full_name, matrix)) if not matrix_compatible(match, matrix, session.logger): raise UserError("Match chain (%s) not" " compatible with %s similarity" " matrix" % (match.full_name, matrix)) if match in match_chains: raise UserError("Cannot match the same chain" " to multiple reference chains") match_chains[match] = ref if match.structure in ref_mols \ or ref.structure in match_mols \ or match.structure == ref.structure: raise UserError("Cannot have same molecule" " model provide both reference and" " match chains") match_mols[match.structure] = ref ref_mols[ref.structure] = match if not match_chains: raise UserError("Must select at least one reference" " chain.\n") for match, ref in match_chains.items(): match, ref = [ check_domain_matching([ch], dr)[0] for ch, dr in ((match, md_res), (ref, rd_res)) ] score, s1, s2 = align(session, ref, match, matrix, alg, gap_open, gap_extend, dssp_cache, **align_kw) pairings.setdefault(s2.structure, []).append((score, s1, s2)) elif chain_pairing == CP_SPECIFIC_BEST: # specific chain in reference; # best seq-aligning chain in match model(s) ref, matches = match_items if not ref or not matches: raise UserError( "Must select at least one reference and match item.\n") if not matrix_compatible(ref, matrix, session.logger): raise UserError("Reference chain (%s) not compatible" " with %s similarity matrix" % (ref.full_name, matrix)) ref = check_domain_matching([ref], rd_res)[0] for match in matches: best_score = None seqs = [ s for s in match.chains if matrix_compatible(s, matrix, session.logger) ] if not seqs and match.chains: raise UserError("No chains in match structure" " %s compatible with %s similarity" " matrix" % (match, matrix)) seqs = check_domain_matching(seqs, md_res) for seq in seqs: score, s1, s2 = align(session, ref, seq, matrix, alg, gap_open, gap_extend, dssp_cache, **align_kw) if best_score is None or score > best_score: best_score = score pairing = (score, s1, s2) if best_score is None: raise LimitationError(small_mol_err_msg) pairings[match] = [pairing] elif chain_pairing == CP_BEST_BEST: # best seq-aligning pair of chains between # reference and match structure(s) ref, matches = match_items if not ref or not matches: raise UserError("Must select at least one reference" " and match item in different models.\n") rseqs = [ s for s in check_domain_matching(ref.chains, rd_res) if matrix_compatible(s, matrix, session.logger) ] if not rseqs and ref.chains: raise UserError("No chains in reference structure" " %s compatible with %s similarity" " matrix" % (ref, matrix)) for match in matches: best_score = None mseqs = [ s for s in check_domain_matching(match.chains, md_res) if matrix_compatible(s, matrix, session.logger) ] if not mseqs and match.chains: raise UserError("No chains in match structure" " %s compatible with %s similarity" " matrix" % (match, matrix)) for mseq in mseqs: for rseq in rseqs: score, s1, s2 = align(session, rseq, mseq, matrix, alg, gap_open, gap_extend, dssp_cache, **align_kw) if best_score is None or score > best_score: best_score = score pairing = (score, s1, s2) if best_score is None: raise LimitationError(small_mol_err_msg) pairings[match] = [pairing] else: raise ValueError("No such chain-pairing method") finally: if not keep_computed_ss: for s, ss_info in dssp_cache.items(): ss_ids, ss_types = ss_info s.residues.ss_ids = ss_ids s.residues.ss_types = ss_types logger = session.logger ret_vals = [] logged_params = False for match_mol, pairs in pairings.items(): ref_atoms = [] match_atoms = [] region_info = {} if verbose: seq_pairings = [] for score, s1, s2 in pairs: try: ss_matrix = align_kw['ss_matrix'] except KeyError: ss_matrix = default_ss_matrix try: ss_fraction = align_kw['ss_fraction'] except KeyError: ss_fraction = defaults["ss_mixture"] if not logged_params and verbose is not None: if ss_fraction is None or ss_fraction is False: ss_rows = """ <tr> <td colspan="2" align="center">No secondary-structure guidance used</td> </tr> <tr> <td>Gap open</td> <td>%g</td> </tr> <tr> <td>Gap extend</td> <td>%g</td> </tr> """ % (gap_open, gap_extend) else: if 'gap_open_helix' in align_kw: gh = align_kw['gap_open_helix'] else: gh = defaults["helix_open"] if 'gap_open_strand' in align_kw: gs = align_kw['gap_open_strand'] else: gs = defaults["strand_open"] if 'gap_open_other' in align_kw: go = align_kw['gap_open_other'] else: go = defaults["other_open"] ss_rows = """ <tr> <td>SS fraction</td> <td>%g</td> </tr> <tr> <td>Gap open (HH/SS/other)</td> <td>%g/%g/%g</td> </tr> <tr> <td>Gap extend</td> <td>%g</td> </tr> <tr> <td>SS matrix</td> <td> <table> <tr> <th></th> <th>H</th> <th>S</th> <th>O</th> </tr> <tr> <th>H</th> <td align="right">%g</td> <td align="right">%g</td> <td align="right">%g</td> </tr> <tr> <th>S</th> <td></td> <td align="right">%g</td> <td align="right">%g</td> </tr> <tr> <th>O</th> <td></td> <td></td> <td align="right">%g</td> </tr> </table> </td> </tr> """ % (ss_fraction, gh, gs, go, gap_extend, ss_matrix[('H', 'H')], ss_matrix[('H', 'S')], ss_matrix[('H', 'O')], ss_matrix[('S', 'S')], ss_matrix[('S', 'O')], ss_matrix[('O', 'O')]) if cutoff_distance is None: iterate_row = """<tr> <td colspan="2" align="center">No iteration</td> </tr>""" else: iterate_row = """<tr> <td>Iteration cutoff</td> <td>%g</td></tr>""" % cutoff_distance from chimerax.core.logger import html_table_params param_table = """ <table %s> <tr> <th colspan="2">Parameters</th> </tr> <tr> <td>Chain pairing</td> <td>%s</td> </tr> <tr> <td>Alignment algorithm</td> <td>%s</td> </tr> <tr> <td>Similarity matrix</td> <td>%s</td> </tr> %s %s </table> """ % (html_table_params, chain_pairing, alg_name, matrix, ss_rows, iterate_row) logger.info(param_table, is_html=True) logged_params = True logger.status("Matchmaker %s (#%s) with %s (#%s)," " sequence alignment score = %g" % (s1.name, s1.structure.id_string, s2.name, s2.structure.id_string, score), log=True) skip = set() viewer = None if show_alignment: for s in [s1, s2]: if hasattr(s, '_dm_rebuild_info'): residues = s.residues characters = list(s.characters) for i, c, r in s._dm_rebuild_info: g = s.ungapped_to_gapped(i) characters[g] = c residues[i] = r skip.add(r) s.bulk_set(residues, characters) alignment = session.alignments.new_alignment( [s1, s2], None, auto_associate=None, name="MatchMaker alignment") alignment.auto_associate = True for hdr in alignment.headers: hdr.shown = hdr.ident == "rmsd" for i in range(len(s1)): if s1[i] == "." or s2[i] == ".": continue ref_res = s1.residues[s1.gapped_to_ungapped(i)] match_res = s2.residues[s2.gapped_to_ungapped(i)] if not ref_res: continue ref_atom = ref_res.principal_atom if not ref_atom: continue if not match_res: continue match_atom = match_res.principal_atom if not match_atom: continue if ref_res in skip or match_res in skip: continue if ref_atom.name != match_atom.name: # nucleic P-only trace vs. full nucleic if ref_atom.name != "P": ref_atom = ref_atom.residue.find_atom("P") if not ref_atom: continue else: match_atom = match_atom.residue.find_atom("P") if not match_atom: continue ref_atoms.append(ref_atom) match_atoms.append(match_atom) if viewer and cutoff_distance is not None: region_info[ref_atom] = (viewer, i) if verbose: seq_pairings.append((s1, s2)) from chimerax.std_commands import align if len(match_atoms) < 3: msg = "Fewer than 3 residues aligned; cannot match %s with %s" % ( s1.name, s2.name) if always_raise_errors: raise align.IterationError(msg) logger.error(msg) continue from chimerax.atomic import Atoms try: ret_vals.append( align.align(session, Atoms(match_atoms), Atoms(ref_atoms), cutoff_distance=cutoff_distance)) except align.IterationError: if always_raise_errors: raise logger.error("Iteration produces fewer than 3" " residues aligned.\nCannot match %s with %s" " satisfying iteration threshold." % (s1.name, s2.name)) continue if bring is not None: xf = ret_vals[-1][-1] for m in bring: m.scene_position = xf * m.scene_position logger.info("") # separate matches with whitespace if region_info: by_viewer = {} for ra in ret_vals[-1][1]: viewer, index = region_info[ra] by_viewer.setdefault(viewer, []).append(index) for viewer, indices in by_viewer.items(): indices.sort() name, fill, outline = viewer.MATCHED_REGION_INFO viewer.new_region(name=name, columns=indices, fill=fill, outline=outline) viewer.status( "Residues used in final fit iteration are highlighted") if verbose: for s1, s2 in seq_pairings: logger.info("Sequences:") for s in [s1, s2]: logger.info(s.name + "\t" + s.characters) logger.info("Residues:") for s in [s1, s2]: logger.info(", ".join([str(r) for r in s.residues])) logger.info("Residue usage in match (1=used, 0=unused):") match_atoms1, match_atoms2 = ret_vals[-1][:2] match_residues = set([ a.residue for matched in ret_vals[-1][:2] for a in matched ]) for s in [s1, s2]: logger.info(", ".join( [str(int(r in match_residues)) for r in s.residues])) global _dm_cleanup for seq in _dm_cleanup: delattr(seq, '_dm_rebuild_info') _dm_cleanup = [] return ret_vals
def value(self, val): if self.group_identical: from chimerax.core.errors import LimitationError raise LimitationError("Cannot set grouped Chain list") self.set_value(val)
def model(session, targets, *, block=True, multichain=True, custom_script=None, dist_restraints=None, executable_location=None, fast=False, het_preserve=False, hydrogens=False, license_key=None, num_models=5, show_gui=True, temp_path=None, thorough_opt=False, water_preserve=False): """ Generate comparative models for the target sequences. Arguments: session current session targets list of (alignment, sequence) tuples. Each sequence will be modelled. block If True, wait for modelling job to finish before returning and return list of (opened) models. Otherwise return immediately. Also see 'show_gui' option. multichain If True, the associated chains of each structure are used individually to generate chains in the resulting models (i.e. the models will be multimers). If False, all associated chains are used together as templates to generate a single-chain model for the target sequence. custom_script If provided, the location of a custom Modeller script to use instead of the one we would otherwise generate. Only used when executing locally. dist_restraints If provided, the location of a file containing additional distance restraints executable_location If provided, the path to the locally installed Modeller executable. If not provided, use the web service. fast Whether to use fast but crude generation of models het_preserve Whether to preserve HET atoms in generated models hydrogens Whether to generate models with hydrogen atoms license_key Modeller license key. If not provided, try to use settings to find one. num_models Number of models to generate for each template sequence show_gui If True, show user interface for Modeller results (if ChimeraX is in gui mode). temp_path If provided, folder to use for temporary files thorough_opt Whether to perform thorough optimization water_preserve Whether to preserve water in generated models """ from chimerax.core.errors import LimitationError, UserError from .common import modeller_copy if multichain: # So, first find structure with most associated chains and least non-associated chains. # That structure is used as the multimer template. Chains from other structures are used # as "standalone" templates -- each such chain will be on its own line. Need to allow # space on the left and right of the target sequence so that the largest chains can be # accomodated. # Find the structure we will use as the multimer template by_structure = {} chain_info = {} for alignment, orig_target in targets: # Copy the target sequence, changing name to conform to Modeller limitations target = modeller_copy(orig_target) if not alignment.associations: raise UserError("Alignment %s has no associated chains" % alignment.ident) for chain, aseq in alignment.associations.items(): if len(chain.chain_id) > 1: raise LimitationError( "Modeller cannot handle templates with multi-character chain IDs" ) by_structure.setdefault(chain.structure, []).append(chain) chain_info[chain] = (aseq, target) max_matched = min_unmatched = None for s, match_info in by_structure.items(): matched = len(match_info) unmatched = s.num_chains - len(match_info) if max_matched is None or matched > max_matched or ( matched == max_matched and (unmatched < min_unmatched)): multimer_template = s max_matched = matched min_unmatched = unmatched mm_targets = [] mm_chains = [] match_chains = [] for chain in multimer_template.chains: mm_chains.append(chain) try: aseq, target = chain_info[chain] except KeyError: mm_targets.append(None) else: mm_targets.append(target) match_chains.append(chain) # okay, now form single-chain lines for the other structure associations, that eventually will # be handled column by column in exactly the same way as the non-multichain method. single_template_lines = [] for chain, info in chain_info.items(): if chain.structure == multimer_template: continue aseq, target = info for i, mm_target in enumerate(mm_targets): if mm_target != target: continue template_line = [None] * len(mm_targets) template_line[i] = chain single_template_lines.append(template_line) # AFAIK, the multimer template chain sequences need to have complete PDB sequence, so may need # to prefix and suffix he corresponding alignment sequence with characters for residues # outside of the alignment sequence. For other templates/targets, affix a corresponding number # of '-' characters prefixes, suffixes = find_affixes(mm_chains, chain_info) target_strings = [] for prefix, suffix, mm_target in zip(prefixes, suffixes, mm_targets): if mm_target is None: target_strings.append('-') continue target_strings.append('-' * len(prefix) + mm_target.characters + '-' * len(suffix)) templates_strings = [] templates_info = [] mm_template_strings = [] for prefix, suffix, chain in zip(prefixes, suffixes, mm_chains): try: aseq, target = chain_info[chain] except KeyError: mm_template_strings.append('-') continue mm_template_strings.append( prefix + regularized_seq(aseq, chain).characters + suffix) templates_strings.append(mm_template_strings) templates_info.append(None) for template_line in single_template_lines: template_strings = [] for prefix, suffix, chain, target in zip(prefixes, suffixes, template_line, mm_targets): if target is None: template_strings.append('-') elif chain is None: template_strings.append( '-' * (len(prefix) + len(target) + len(suffix))) else: aseq, target = chain_info[chain] template_strings.append( '-' * len(prefix) + regularized_seq(aseq, chain).characters + '-' * len(suffix)) templates_info.append((chain, aseq.match_maps[chain])) templates_strings.append(template_strings) target_name = "target" if len(targets) > 1 else target.name else: if len(targets) > 1: raise LimitationError( "Cannot have multiple targets(/alignments) unless creating multimeric model" ) alignment, orig_target = targets[0] # Copy the target sequence, changing name to conform to Modeller limitations target = modeller_copy(orig_target) target_strings = [target.characters] templates_strings = [] templates_info = [] match_chains = [] for chain, aseq in alignment.associations.items(): if len(chain.chain_id) > 1: raise LimitationError( "Modeller cannot handle templates with multi-character chain IDs" ) templates_strings.append([regularized_seq(aseq, chain).characters]) templates_info.append((chain, aseq.match_maps[chain])) if not match_chains: match_chains.append(chain) target_name = target.name from .common import write_modeller_scripts, get_license_key script_path, config_path, temp_dir = write_modeller_scripts( get_license_key(session, license_key), num_models, het_preserve, water_preserve, hydrogens, fast, None, custom_script, temp_path, thorough_opt, dist_restraints) input_file_map = [] # form the sequences to be written out as a PIR from chimerax.atomic import Sequence pir_target = Sequence(name=target_name) pir_target.description = "sequence:%s:.:.:.:.::::" % pir_target.name pir_target.characters = '/'.join(target_strings) pir_seqs = [pir_target] structures_to_save = set() for strings, info in zip(templates_strings, templates_info): if info is None: # multimer template pir_template = Sequence( name=structure_save_name(multimer_template)) pir_template.description = "structure:%s:FIRST:%s::::::" % ( pir_template.name, multimer_template.chains[0].chain_id) structures_to_save.add(multimer_template) else: # single-chain template chain, match_map = info first_assoc_pos = 0 while first_assoc_pos not in match_map: first_assoc_pos += 1 first_assoc_res = match_map[first_assoc_pos] pir_template = Sequence(name=chain_save_name(chain)) pir_template.description = "structure:%s:%d%s:%s:+%d:%s::::" % ( structure_save_name(chain.structure), first_assoc_res.number, first_assoc_res.insertion_code, chain.chain_id, len(match_map), chain.chain_id) structures_to_save.add(chain.structure) pir_template.characters = '/'.join(strings) pir_seqs.append(pir_template) import os.path pir_file = os.path.join(temp_dir.name, "alignment.ali") aln = session.alignments.new_alignment(pir_seqs, False, auto_associate=False, create_headers=False) aln.save(pir_file, format_name="pir") session.alignments.destroy_alignment(aln) input_file_map.append(("alignment.ali", "text_file", pir_file)) # write the namelist.dat file, target seq name on first line, templates on remaining lines name_file = os.path.join(temp_dir.name, "namelist.dat") input_file_map.append(("namelist.dat", "text_file", name_file)) with open(name_file, 'w') as f: for template_seq in pir_seqs: print(template_seq.name, file=f) config_name = os.path.basename(config_path) input_file_map.append((config_name, "text_file", config_path)) # save structure files import os struct_dir = os.path.join(temp_dir.name, "template_struc") if not os.path.exists(struct_dir): try: os.mkdir(struct_dir, mode=0o755) except FileExistsError: pass from chimerax.pdb import save_pdb, standard_polymeric_res_names as std_res_names for structure in structures_to_save: base_name = structure_save_name(structure) + '.pdb' pdb_file_name = os.path.join(struct_dir, base_name) input_file_map.append((base_name, "text_file", pdb_file_name)) ATOM_res_names = structure.in_seq_hets ATOM_res_names.update(std_res_names) save_pdb(session, pdb_file_name, models=[structure], polymeric_res_names=ATOM_res_names) delattr(structure, 'in_seq_hets') from chimerax.atomic import Chains match_chains = Chains(match_chains) if executable_location is None: if custom_script is not None: raise LimitationError( "Custom Modeller scripts only supported when executing locally" ) if dist_restraints is not None: raise LimitationError( "Distance restraints only supported when executing locally") if thorough_opt: session.logger.warning( "Thorough optimization only supported when executing locally") job_runner = ModellerWebService(session, match_chains, num_models, pir_target.name, input_file_map, config_name, targets, show_gui) else: #TODO: job_runner = ModellerLocal(...) from chimerax.core.errors import LimitationError raise LimitationError("Local Modeller execution not yet implemented") # a custom script [only used when executing locally] needs to be copied into the tmp dir... if os.path.exists(script_path) \ and os.path.normpath(temp_dir.name) != os.path.normpath(os.path.dirname(script_path)): import shutil shutil.copy(script_path, temp_dir.name) return job_runner.run(block=block)
def cmd_open(session, file_names, rest_of_line, *, log=True): tokens = [] remainder = rest_of_line while remainder: token, token_log, remainder = next_token(remainder) remainder = remainder.lstrip() tokens.append(token) provider_cmd_text = "open " + " ".join([FileNameArg.unparse(fn) for fn in file_names] + [StringArg.unparse(token) for token in tokens]) try: database_name = format_name = None for i in range(len(tokens)-2, -1, -2): test_token = tokens[i].lower() if "format".startswith(test_token): format_name = tokens[i+1] elif "fromdatabase".startswith(test_token): database_name = tokens[i+1] from .manager import NoOpenerError mgr = session.open_command fetches, files = fetches_vs_files(mgr, file_names, format_name, database_name) if fetches: try: provider_args = mgr.fetch_args(fetches[0][1], format_name=fetches[0][2]) except NoOpenerError as e: raise LimitationError(str(e)) else: data_format = file_format(session, files[0], format_name) if data_format is None: # let provider_open raise the error, which will show the command provider_args = {} else: try: provider_args = mgr.open_args(data_format) except NoOpenerError as e: raise LimitationError(str(e)) # register a private 'open' command that handles the provider's keywords registry = RegisteredCommandInfo() def database_names(mgr=mgr): return mgr.database_names keywords = { 'format': DynamicEnum(lambda ses=session:format_names(ses)), 'from_database': DynamicEnum(database_names), 'ignore_cache': BoolArg, 'name': StringArg } for keyword, annotation in provider_args.items(): if keyword in keywords: raise ValueError("Open-provider keyword '%s' conflicts with builtin arg of" " same name" % keyword) keywords[keyword] = annotation desc = CmdDesc(required=[('names', OpenFileNamesArg)], keyword=keywords.items(), synopsis="read and display data") register("open", desc, provider_open, registry=registry) except BaseException as e: # want to log command even for keyboard interrupts log_command(session, "open", provider_cmd_text, url=_main_open_CmdDesc.url) raise return Command(session, registry=registry).run(provider_cmd_text, log=log)
def add_atom(name, element, residue, loc, serial_number=None, bonded_to=None, occupancy=None, info_from=None, alt_loc=None, bfactor=None): """Add an atom at the Point 'loc' 'element' can be a string (atomic symbol), integer (atomic number), or an Element instance. The atom is added to the given residue (and its molecule). 'loc' can be an array of xyzs if there are multiple coordinate sets. If no 'serial_number' is given, then the atom will be given a serial number one greater than the largest serial number of the other atoms in the structure. 'bonded_to' is None or an Atom. If an Atom, then the new atom inherits various attributes [display, altloc, style, occupancy] from that atom and a bond to that Atom is created. If 'info_from' is supplied then the information normally garnered from the 'bonded_to' atom will be obtained from the 'info_from' atom instead. Typically used when there is no 'bonded_to' atom. If 'occupancy' is not None or the 'bonded_to' atom is not None, the new atom will be given the corresponding occupancy. If 'bfactor' is not None or the 'bonded_to' atom is not None, the new atom will be given the corresponding bfactor. If 'alt_loc' is specified (must be a single-character string), then the new atom will be given that alt loc, otherwise the alt loc will be ' '. Returns the new atom. """ if not info_from: info_from = bonded_to struct = residue.structure new_atom = struct.new_atom(name, element) residue.add_atom(new_atom) if alt_loc is not None: new_atom.set_alt_loc(alt_loc, True) from numpy import array if len(loc.shape) == 1: locs = array([loc]) else: locs = loc if struct.num_coordsets == 0: if len(locs) > 1: from chimerax.core.errors import LimitationError raise LimitationError( "Cannot add_atom() multi-position atom to empty structure") new_atom.coord = locs[0] else: for xyz, cs_id in zip(locs, struct.coordset_ids): new_atom.set_coord(xyz, cs_id) if serial_number is None: import numpy serial_number = numpy.max(struct.atoms.serial_numbers) + 1 new_atom.serial_number = serial_number if occupancy is not None or info_from and hasattr(info_from, 'occupancy'): new_atom.occupancy = getattr(info_from, 'occupancy', occupancy) if bfactor is not None or info_from and hasattr(info_from, 'bfactor'): new_atom.bfactor = getattr(info_from, 'bfactor', bfactor) if info_from: new_atom.display = info_from.display new_atom.draw_mode = info_from.draw_mode if bonded_to: add_bond(new_atom, bonded_to) return new_atom
def findclash(session, spec=None, make_pseudobonds=False, log=True, naming_style="command", overlap_cutoff=0.6, hbond_allowance=0.4, bond_separation=4, test="other", intra_residue=False): from chimerax.core.errors import LimitationError from chimerax.atomic import Atoms from chimerax.core.commands import atomspec from chimerax.atomic.settings import settings if test != "self": raise LimitationError("findclash test \"%s\" not implemented" % test) if hbond_allowance != 0: session.logger.warning("Hydrogen bond finding is not implemented. " "Setting hbond_allowance to 0.0.") hbond_allowance = 0 if naming_style != "command" and naming_style != "command-line": raise LimitationError("findclash naming style \"%s\" not implemented" % naming_style) if make_pseudobonds: raise LimitationError("findclash make pseudobonds not implemented") if spec is None: spec = atomspec.everything(session) results = spec.evaluate(session) atoms = results.atoms neighbors = _find_neighbors(atoms, bond_separation) clashes = [] for i in range(len(atoms) - 1): a = atoms[i] if intra_residue: # Want intra-residue contacts, so check all atoms others = atoms[i + 1:] else: # Do not want intra-residue contacts, so remove atoms from residue from numpy import logical_not others = atoms[i + 1:] residues = others.residues # Generate mask for atoms from same residue mask = others.residues.mask(Atoms([a.residue])) others = others.filter(logical_not(mask)) # Remove atoms within "separation" bonds of this atom others -= neighbors[a] if len(others) > 0: clashes.extend( _find_clash_self(a, others, overlap_cutoff, hbond_allowance)) if log: session.logger.info("Allowed overlap: %g" % overlap_cutoff) session.logger.info("H-bond overlap reduction: %g" % hbond_allowance) session.logger.info("Ignored contact between atoms separated by %d " "bonds or less" % bond_separation) session.logger.info("%d contacts" % len(clashes)) session.logger.info("atom1\tatom2\toverlap\tdistance") save = settings.atomspec_contents settings.atomspec_contents = "command" msgs = ["%s\t%s\t%.3f\t%.3f" % c for c in clashes] settings.atomspec_contents = save session.logger.info('\n'.join(msgs)) session.logger.info("%d contacts" % len(clashes))
def distance(session, objects, *, color=None, dashes=None, decimal_places=None, radius=None, symbol=None, signed=False): ''' Show/report distance between two objects. ''' from chimerax.core.errors import UserError, LimitationError measurables = [m for m in objects.models if isinstance(m, (SimpleMeasurable, ComplexMeasurable))] measurables.extend(objects.atoms) if len(measurables) != 2: raise UserError("Expected exactly two atoms and/or measurable objects (e.g. axes, planes), got %d" % len(measurables)) if len(objects.atoms) != 2: # just report the distance -- no distance monitor if len([m for m in measurables if isinstance(m, SimpleMeasurable)]) == 2: from chimerax.geometry import distance dist = distance(measurables[0].scene_coord, measurables[1].scene_coord) else: dist = NotImplemented if isinstance(measurables[0], ComplexMeasurable): dist = measurables[0].distance(measurables[1], signed=signed) if dist is NotImplemented and isinstance(measurables[1], ComplexMeasurable): dist = measurables[1].distance(measurables[0], signed=signed) if dist is NotImplemented: raise LimitationError("Don't know how to measure distance between %s and %s" % tuple(measurables)) session.logger.info(("Distance between %s and %s: " + session.pb_dist_monitor.distance_format) % (measurables[0], measurables[1], dist)) return dist a1, a2 = measurables grp = session.pb_manager.get_group("distances", create=False) from .settings import settings if not grp: # create group and add to DistMonitor grp = session.pb_manager.get_group("distances") if color is not None: grp.color = color.uint8x4() else: grp.color = settings.color.uint8x4() if radius is not None: grp.radius = radius else: grp.radius = settings.radius grp.dashes = settings.dashes session.models.add([grp]) session.pb_dist_monitor.add_group(grp, update_callback=_notify_updates) for pb in grp.pseudobonds: pa1, pa2 = pb.atoms if (pa1 == a1 and pa2 == a2) or (pa1 == a2 and pa2 == a1): raise UserError("Distance already exists;" " modify distance properties with 'distance style'") pb = grp.new_pseudobond(a1, a2) if color is not None: pb.color = color.uint8x4() if dashes is not None: grp.dashes = dashes if radius is not None: pb.radius = radius if decimal_places is not None or symbol is not None: if decimal_places is not None: session.pb_dist_monitor.decimal_places = decimal_places if symbol is not None: session.pb_dist_monitor.show_units = symbol session.logger.info(("Distance between %s and %s: " + session.pb_dist_monitor.distance_format) % (a1, a2.string(relative_to=a1), pb.length))