def _process_subdialog(self, sd_type): sd = self.subdialogs[sd_type] from chimerax.core.commands import run if sd_type == "H-Bonds": cmd_name, spec, args = sd.hbonds_gui.get_command() res = self.mgr.base_residue base_spec = "#!%s & ~%s" % (res.structure.id_string, res.string(style="command")) if self.ignore_solvent_button.isChecked(): base_spec += " & ~solvent" hbs = run( self.session, "%s %s %s restrict #%s & ~@c,ca,n" % (cmd_name, base_spec, args, self.mgr.group.id_string)) AtomicStructure.register_attr(self.session, "num_hbonds", self.registerer, attr_type=int) for rotamer in self.mgr.rotamers: rotamer.num_hbonds = 0 for d, a in hbs: if d.structure == res.structure: a.structure.num_hbonds += 1 else: d.structure.num_hbonds += 1 if sd_type in self.opt_columns: self.table.update_column(self.opt_columns[sd_type], data=True) else: self.opt_columns[sd_type] = self.table.add_column(sd_type, "num_hbonds", format="%d") elif sd_type == "Clashes": cmd_name, spec, args = sd.clashes_gui.get_command() res = self.mgr.base_residue base_spec = "#!%s & ~%s" % (res.structure.id_string, res.string(style="command")) if self.ignore_solvent_button.isChecked(): base_spec += " & ~solvent" clashes = run( self.session, "%s %s %s restrict #%s & ~@c,ca,n" % (cmd_name, base_spec, args, self.mgr.group.id_string)) AtomicStructure.register_attr(self.session, "num_clashes", self.registerer, attr_type=int) for rotamer in self.mgr.rotamers: rotamer.num_clashes = 0 for a, clashing in clashes.items(): if a.structure != res.structure: a.structure.num_clashes += len(clashing) if sd_type in self.opt_columns: self.table.update_column(self.opt_columns[sd_type], data=True) else: self.opt_columns[sd_type] = self.table.add_column( sd_type, "num_clashes", format="%d") else: # Density vol = sd.vol_list.value if not vol: return self._eval_vol(vol) self._update_button_text()
def take_snapshot(self, session, flags): data = { 'version': 1, 'atomic structure state': AtomicStructure.take_snapshot(self, session, flags) } return data
def duplicate(session, selection, newModel=True): # dict for unique models, residues, and atoms models = {} # dict for just models and atoms atom_list = {} for atom in selection: if atom.structure not in models: models[atom.structure] = {} atom_list[atom.structure] = [] if atom.residue not in models[atom.structure]: models[atom.structure][atom.residue] = [] models[atom.structure][atom.residue].append(atom) atom_list[atom.structure].append(atom) for model in models: if newModel: struc = AtomicStructure(session, name="copy of %s" % model.atomspec) else: struc = model offset = len(model.atoms) for res in models[model]: residue = struc.new_residue(res.name, res.chain_id, res.number) for atom in models[model][res]: dup_atom = struc.new_atom(atom.name, atom.element) dup_atom.coord = atom.coord dup_atom.serial_number = len(struc.atoms) res.add_atom(dup_atom) for i, atom1 in enumerate(atom_list[model]): atom1.selected = False if newModel: struc.atoms[i].selected = True struc.atoms[i].color = atom1.color struc.atoms[i].radius = atom1.radius struc.atoms[i].draw_mode = atom1.draw_mode else: struc.atoms[i + offset].selected = True struc.atoms[i + offset].color = atom1.color struc.atoms[i + offset].radius = atom1.radius struc.atoms[i + offset].draw_mode = atom1.draw_mode for j, atom2 in enumerate(atom_list[model][:i]): if atom2 in atom1.neighbors: if newModel: struc.new_bond(struc.atoms[i], struc.atoms[j]) else: struc.new_bond(struc.atoms[i + offset], struc.atoms[j + offset]) if newModel: session.models.add([struc]) session.triggers.activate_trigger(SELECTION_CHANGED, None)
def _read_block(session, stream): # First line should be an integer count of the number of # atoms in the block. Each block gets turned into an # AtomicStructure instance. count_line = stream.readline() if not count_line: return None try: count = int(count_line) except ValueError: # XXX: Should emit an error message return None from chimerax.atomic import AtomicStructure s = AtomicStructure(session) # Next line is a comment line s.comment = stream.readline().strip() # There should be "count" lines of atoms. from numpy import array, float64 residue = s.new_residue("UNK", 'A', 1) element_count = {} for n in range(count): atom_line = stream.readline() if not atom_line: # XXX: Should emit an error message return None parts = atom_line.split() if len(parts) != 4: # XXX: Should emit an error message return None # Extract available data element = parts[0] xyz = [float(v) for v in parts[1:]] # Convert to required initializers # XXX: May need to convert element to usable form n = element_count.get(element, 0) + 1 name = element + str(n) element_count[element] = n # Create atom atom = s.new_atom(name, element) atom.coord = array(xyz, dtype=float64) residue.add_atom(atom) s.connect_structure() s.new_atoms() # tell structure it needs to update return s
def _eval_vol(self, vol): AtomicStructure.register_attr(self.session, "sum_density", self.registerer, attr_type=float) for rot in self.mgr.rotamers: values = vol.interpolated_values(rot.atoms.coords, point_xform=rot.scene_position) total = 0 for a, val in zip(rot.atoms, values): # 'is_side_chain' only works for actual polymers if a.name not in a.residue.aa_max_backbone_names: total += val rot.sum_density = total sd_type = "Density" if sd_type in self.opt_columns: self.table.update_column(self.opt_columns[sd_type], data=True) else: self.opt_columns[sd_type] = self.table.add_column(sd_type, "sum_density", format="%g")
def open_pdb(session, stream, file_name, *, auto_style=True, coordsets=False, atomic=True, max_models=None, log_info=True, combine_sym_atoms=True): from chimerax.atomic import AtomicStructure return [AtomicStructure(session, name="test model")], 'message'
def open_mmcif(session, path, file_name=None, auto_style=True, coordsets=False, atomic=True, max_models=None, log_info=True, extra_categories=(), combine_sym_atoms=True): from chimerax.atomic import AtomicStructure return [AtomicStructure(session, name="test model")], 'message'
def make_gaussian_cube_atoms(session): from chimerax.map import Volume from chimerax.map_data.gaussian.gaussian_grid import GaussianGrid glist = [m.data for m in session.models if isinstance(m, Volume) and isinstance(m.data, GaussianGrid)] slist = [] for g in glist: atoms = g.gc.atoms if atoms: from chimerax.atomic import AtomicStructure, Element s = AtomicStructure(session) r = s.new_residue('UNK', 'A', 1) for i, (n,q,x,y,z) in enumerate(atoms): e = Element.get_element(n) a = s.new_atom(e.name, e) b = bohr_radius = 0.5291772108 # Angstroms a.coord = (b*x,b*y,b*z) a.serial_number = i r.add_atom(a) s.connect_structure() slist.append(s) session.models.add(slist) session.logger.info('Created %d atomic models for %d Gaussian Cube files' % (len(slist), len(glist)))
def get_chimera(self, session, coordsets=False, filereader=None): """returns a chimerax equivalent of self""" struc = AtomicStructure(session, name=self.name) struc.comment = self.comment self.update_chix(struc) if coordsets and filereader is not None and filereader.all_geom is not None: #make a trajectory #each list of atoms in filereader.all_geom is a frame in the trajectory #replace previous coordinates #this matters when a filereader is given because the #geometry created from a filereader (which was probably passed as geom) #is the last geometry in the log or xyz file xyzs = self.all_geom_coordsets(filereader) struc.add_coordsets(xyzs, replace=True) struc.active_coordset_id = len(xyzs) # if it's a frequency file, draw TS bonds for imaginary modes if filereader is not None and "frequency" in filereader.other: for mode in filereader.other["frequency"].data: if mode.frequency < 0: max_disp = max(np.linalg.norm(x) for x in mode.vector) cur_coords = self.coords coord_forward = self.coords + (0.2 / max_disp) * mode.vector coord_reverse = self.coords - (0.2 / max_disp) * mode.vector forward_connectivity = np.zeros( (len(self.atoms), len(self.atoms))) reverse_connectivity = np.zeros( (len(self.atoms), len(self.atoms))) self.update_geometry(coord_forward) self.refresh_connected() for i, atom1 in enumerate(self.atoms): for j, atom2 in enumerate(self.atoms[:i]): if atom1 in atom2.connected: forward_connectivity[i, j] = 1 forward_connectivity[j, i] = 1 self.update_geometry(coord_reverse) self.refresh_connected() for i, atom1 in enumerate(self.atoms): for j, atom2 in enumerate(self.atoms[:i]): if atom1 in atom2.connected: reverse_connectivity[i, j] = 1 reverse_connectivity[j, i] = 1 changes = forward_connectivity - reverse_connectivity for i, atom1 in enumerate(self.atoms): for j, atom1 in enumerate(self.atoms[:i]): if changes[i, j] != 0: sel = _FauxAtomSelection( atoms=(struc.atoms[i], struc.atoms[j])) tsbond(session, sel) self.update_geometry(cur_coords) return struc
def place_residues(session, coords, sequence, residue_templates, status_interval=1000): ''' Use placements specified by coords (map of index to Place instance) to position nucleotides with given sequence. The nucleotide single letter code (A,C,G,U) maps to a Residue given by map residue_templates. Index i corresponds to sequence position i-1. ''' from chimerax.atomic import AtomicStructure m = AtomicStructure(session, name='RNA', auto_style=False) n = len(sequence) rlist = [] for p, tf in sorted(coords.items()): if p <= n: t = sequence[p - 1].upper() if t in residue_templates: rt = residue_templates[t] r = copy_residue(rt, p, tf, m) rlist.append(r) if status_interval and len(rlist) % status_interval == 0: session.logger.status('Created %d of %d residues' % (len(rlist), len(coords))) # Join consecutive residues rprev = rlist[0] for r in rlist[1:]: if r.number == 1 + rprev.number: a2 = r.find_atom('P') a1 = rprev.find_atom("O3'") if a1 and a2: m.new_bond(a1, a2) rprev = r session.models.add([m]) return m
def cmd_start_structure(session, method, model_info, subargs): from .manager import manager if manager.is_indirect(method): raise UserError("No command support for '%s' start-structure method" % method) if isinstance(model_info, str): from chimerax.atomic import AtomicStructure model = AtomicStructure(session, name=model_info) else: model = model_info try: ret_val = manager.execute_command(method, model, subargs) except BaseException: if isinstance(model_info, str): model.delete() raise if model.num_atoms == 0: model.delete() elif isinstance(model_info, str): session.models.add([model]) return ret_val
def _prep_add(session, structures, unknowns_info, template, need_all=False, **prot_schemes): global _serial _serial = None atoms = [] type_info_for_atom = {} naming_schemas = {} idatm_type = {} # need this later; don't want a recomp hydrogen_totals = {} # add missing OXTs of "real" C termini; # delete hydrogens of "fake" N termini after protonation # and add a single "HN" back on, using same dihedral as preceding residue; # delete extra hydrogen of "fake" C termini after protonation logger = session.logger real_N, real_C, fake_N, fake_C = determine_termini(session, structures) logger.info("Chain-initial residues that are actual N" " termini: %s" % ", ".join([str(r) for r in real_N])) logger.info("Chain-initial residues that are not actual N" " termini: %s" % ", ".join([str(r) for r in fake_N])) logger.info("Chain-final residues that are actual C" " termini: %s" % ", ".join([str(r) for r in real_C])) logger.info("Chain-final residues that are not actual C" " termini: %s" % ", ".join([str(r) for r in fake_C])) for rc in real_C: complete_terminal_carboxylate(session, rc) # ensure that N termini are protonated as N3+ (since Npl will fail) from chimerax.atomic import Sequence for nter in real_N + fake_N: n = nter.find_atom("N") if not n: continue # if residue wasn't templated, leave atom typing alone if Sequence.protein3to1(n.residue.name) == 'X': continue if not (n.residue.name == "PRO" and n.num_bonds >= 2): n.idatm_type = "N3+" coordinations = {} for struct in structures: pbg = struct.pseudobond_group(struct.PBG_METAL_COORDINATION, create_type=None) if not pbg: continue for pb in pbg.pseudobonds: for a in pb.atoms: if not need_all and a.structure not in structures: continue if not a.element.is_metal: coordinations.setdefault(a, []).append(pb.other_atom(a)) remaining_unknowns = {} type_info_class = type_info['H'].__class__ from chimerax.atomic import Residue for struct in structures: for atom in struct.atoms: if atom.element.number == 0: res = atom.residue struct.delete_atom(atom) idatm_lookup = {} if template: template_lookup = {} from chimerax.atomic import TmplResidue get_template = TmplResidue.get_template for res in struct.residues: if get_template(res.name): continue try: exemplar = template_lookup[res.name] except KeyError: from chimerax.mmcif import find_template_residue tmpl = find_template_residue(session, res.name) if not tmpl: continue from chimerax.atomic import AtomicStructure s = AtomicStructure(session) r = exemplar = template_lookup[res.name] = s.new_residue( res.name, 'A', 1) atom_map = {} for ta in tmpl.atoms: if ta.element.number > 1: a = s.new_atom(ta.name, ta.element) a.coord = ta.coord r.add_atom(a) atom_map[ta] = a for tnb in ta.neighbors: if tnb in atom_map: s.new_bond(a, atom_map[tnb]) for a in res.atoms: ea = exemplar.find_atom(a.name) if ea: a.idatm_type = ea.idatm_type for r in template_lookup.values(): r.structure.delete() template_lookup.clear() for atom in struct.atoms: atom_type = atom.idatm_type idatm_type[atom] = atom_type if atom_type in type_info: # don't want to ask for idatm_type in middle # of hydrogen-adding loop (since that will # force a recomp), so remember here type_info_for_atom[atom] = type_info[atom_type] # if atom is in standard residue but has missing bonds to # heavy atoms, skip it instead of incorrectly protonating # (or possibly throwing an error if e.g. it's planar) # also # UNK/N residues will be missing some or all of their side-chain atoms, so # skip atoms that would otherwise be incorrectly protonated due to their # missing neighbors truncated = \ atom.is_missing_heavy_template_neighbors(no_template_okay=True) \ or \ (atom.residue.name in ["UNK", "N"] and atom.residue.polymer_type != Residue.PT_NONE and unk_atom_truncated(atom)) \ or \ (atom.residue.polymer_type == Residue.PT_NUCLEIC and atom.name == "P" and atom.num_explicit_bonds < 4) if truncated: session.logger.warning( "Not adding hydrogens to %s because it is missing heavy-atom" " bond partners" % atom) type_info_for_atom[atom] = type_info_class( 4, atom.num_bonds, atom.name) else: atoms.append(atom) # sulfonamide nitrogens coordinating a metal # get an additional hydrogen stripped if coordinations.get(atom, []) and atom.element.name == "N": if "Son" in [nb.idatm_type for nb in atom.neighbors]: orig_ti = type_info[atom_type] type_info_for_atom[atom] = orig_ti.__class__( orig_ti.geometry, orig_ti.substituents - 1, orig_ti.description) continue if atom in unknowns_info: type_info_for_atom[atom] = unknowns_info[atom] atoms.append(atom) continue remaining_unknowns.setdefault(atom.residue.name, set()).add(atom.name) # leave remaining unknown atoms alone type_info_for_atom[atom] = type_info_class(4, atom.num_bonds, atom.name) for rname, atom_names in remaining_unknowns.items(): names_text = ", ".join([nm for nm in atom_names]) atom_text, obj_text = ("atoms", "them") if len(atom_names) > 1 else ("atom", "it") logger.warning( "Unknown hybridization for %s (%s) of residue type %s;" " not adding hydrogens to %s" % (atom_text, names_text, rname, obj_text)) naming_schemas.update( determine_naming_schemas(struct, type_info_for_atom)) if need_all: from chimerax.atomic import AtomicStructure for struct in [ m for m in session.models if isinstance(m, AtomicStructure) ]: if struct in structures: continue for atom in struct.atoms: idatm_type[atom] = atom.idatm_type if atom.idatm_type in type_info: type_info_for_atom[atom] = type_info[atom.idatm_type] for atom in atoms: if atom not in type_info_for_atom: continue bonding_info = type_info_for_atom[atom] total_hydrogens = bonding_info.substituents - atom.num_bonds for bonded in atom.neighbors: if bonded.element.number == 1: total_hydrogens += 1 hydrogen_totals[atom] = total_hydrogens schemes = {} # HIS and CYS treated as 'unspecified'; use built-in typing for scheme_type, res_names, res_check, typed_atoms in [ ('his', ["HID", "HIE", "HIP"], None, []), ('asp', asp_res_names, _asp_check, asp_prot_names), ('glu', glu_res_names, _glu_check, glu_prot_names), ('lys', ["LYS", "LYN"], _lys_check, ["NZ"]), ('cys', ["CYM"], _cys_check, ["SG"]) ]: scheme = prot_schemes.get(scheme_type + '_scheme', None) if scheme is None: by_name = True scheme = {} else: by_name = False if not scheme: for s in structures: for r in s.residues: if r.name in res_names and res_check and res_check(r): if by_name: scheme[r] = r.name elif scheme_type != 'his': scheme[r] = res_names[0] # unset any explicit typing... for ta in typed_atoms: a = r.find_atom(ta) if a: a.idatm_type = None else: for r in scheme.keys(): if res_check and not res_check(r, scheme[r]): del scheme[r] schemes[scheme_type] = scheme # create dictionary keyed on histidine residue with value of another # dictionary keyed on the nitrogen atoms with boolean values: True # equals should be protonated his_Ns = {} for r, protonation in schemes["his"].items(): delta = r.find_atom("ND1") epsilon = r.find_atom("NE2") if delta is None or epsilon is None: # find the ring, etc. rings = r.structure.rings() for ring in rings: if r in rings.atoms.residues: break else: continue # find CG by locating CB-CG bond ring_bonds = ring.bonds for ra in ring.atoms: if ra.element.name != "C": continue for ba, b in zip(ra.neighbors, ra.bonds): if ba.element.name == "C" and b not in ring_bonds: break else: continue break else: continue nitrogens = [a for a in ring.atoms if a.element.name == "N"] if len(nitrogens) != 2: continue if ra in nitrogens[0].neighbors: delta, epsilon = nitrogens else: epsilon, delta = nitrogens if protonation == "HID": his_Ns.update({delta: True, epsilon: False}) elif protonation == "HIE": his_Ns.update({delta: False, epsilon: True}) elif protonation == "HIP": his_Ns.update({delta: True, epsilon: True}) else: continue for n, do_prot in his_Ns.items(): if do_prot: type_info_for_atom[n] = type_info["Npl"] n.idatm_type = idatm_type[n] = "Npl" else: type_info_for_atom[n] = type_info["N2"] n.idatm_type = idatm_type[n] = "N2" for r, protonation in schemes["asp"].items(): _handle_acid_protonation_scheme_item(r, protonation, asp_res_names, asp_prot_names, type_info, type_info_for_atom) for r, protonation in schemes["glu"].items(): _handle_acid_protonation_scheme_item(r, protonation, glu_res_names, glu_prot_names, type_info, type_info_for_atom) for r, protonation in schemes["lys"].items(): nz = r.find_atom("NZ") if protonation == "LYS": it = 'N3+' else: it = 'N3' ti = type_info[it] if nz is not None: type_info_for_atom[nz] = ti # avoid explicitly setting type if possible if nz.idatm_type != it: nz.idatm_type = it for r, protonation in schemes["cys"].items(): sg = r.find_atom("SG") if protonation == "CYS": it = 'S3' else: it = 'S3-' ti = type_info[it] if sg is not None: type_info_for_atom[sg] = ti # avoid explicitly setting type if possible if sg.idatm_type != it: sg.idatm_type = it return atoms, type_info_for_atom, naming_schemas, idatm_type, \ hydrogen_totals, his_Ns, coordinations, fake_N, fake_C
def openFile(self): fileName, filt = QFileDialog.getOpenFileName(caption="Open Trajectory File", filter="PDB File (*.pdb);;" \ "H5 Trajectory File (*.h5);;" \ "HDF5 Trajectory File (*.hdf5)" \ "GRO Topology File (*.gro)" ) print(fileName) print(filt) if fileName == "": return if filt == "(*.gro)": gro_fileName = fileName fileName, filt = QFileDialog.getOpenFileName(caption="Open Trajectory File", filter="XTC Trajectory File (*.xtc);;" \ "TRR Trajectory File (*.trr);;" \ ) print(fileName) print(filt) else: gro_fileName = None if self.atomStruct != None: self.deleteModel() # This create a simple Oxygen atom # Need to get this to display self.atomStruct = AtomicStructure(self.session) if filt in ["(*.xtc)", "(*.trr)"]: trj = mdtraj.load(fileName, topology=gro_fileName) else: trj = mdtraj.load(fileName) trj.atom_slice(trj.topology.select("not element H")) table, bond_table = trj.topology.to_dataframe() table.rename(columns={ "name": "atomname", "resName": "residue_name", "chainID": "chain_id", "resSeq": "pos" }, inplace=True) table["pos"] = table["pos"].astype(int) xyz = trj.xyz[0] table["loc"] = [array(x, dtype=float64) for x in xyz] res_infos = table[["residue_name", "chain_id", "pos" ]].drop_duplicates(keep="first").to_dict("records") for res in res_infos: residue = self.atomStruct.new_residue(**res) qry = ' and '.join( ['{} == {}'.format(k, v) for k, v in res.items()]) res_atoms = table.query(qry)[["name", "element", "loc"]].to_dict("records") for a in res_atoms: atom = self.atomStruct.new_atom({ "name": a["name"], "element": a["element"] }) atom.coord = a["loc"] residue.add_atom(atom) for frame in range(1, trj.n_frames): xyz = trj.xyz[frame] self.atomLocation.append(array(xyz, dtype=float64)) self.atomStruct.connect_structure() self.session.models.add([self.atomStruct]) for i in range(len(self.atomLocation)): self.surfacePositions.append(None) self.atomStruct.atoms.coords = self.atomLocation[0] fileName = fileName.split("/")[-1] js = "modelName = '" + fileName + "'; frameCount = " + str( len(self.atomLocation)) + "; reset();" self.html_view.runJavaScript(js) run(self.session, "lighting full", log=False)
def read_sdf(session, stream, file_name): path = stream.name if hasattr(stream, 'name') else None structures = [] nonblank = False state = "init" from chimerax.core.errors import UserError from chimerax.atomic.struct_edit import add_atom from chimerax.atomic import AtomicStructure, Element, Bond, Atom, AtomicStructure from numpy import array Bond.register_attr(session, "order", "SDF format", attr_type=float) Atom.register_attr(session, "charge", "SDF format", attr_type=float) AtomicStructure.register_attr(session, "charge_model", "SDF format", attr_type=str) try: for l in stream: line = l.strip() nonblank = nonblank or line if state == "init": state = "post header 1" mol_name = line elif state == "post header 1": state = "post header 2" elif state == "post header 2": state = "counts" elif state == "counts": if not line: break state = "atoms" serial = 1 anums = {} atoms = [] try: num_atoms = int(l[:3].strip()) num_bonds = int(l[3:6].strip()) except ValueError: raise UserError("Atom/bond counts line of MOL/SDF file '%s' is botched" % file_name) from chimerax.atomic.structure import is_informative_name name = mol_name if is_informative_name(mol_name) else file_name s = AtomicStructure(session, name=name) structures.append(s) r = s.new_residue("UNL", " ", 1) elif state == "atoms": num_atoms -= 1 if num_atoms == 0: if num_bonds: state = "bonds" else: state = "properties" try: x = float(l[:10].strip()) y = float(l[10:20].strip()) z = float(l[20:30].strip()) elem = l[31:34].strip() except ValueError: s.delete() raise UserError("Atom line of MOL/SDF file '%s' is not x y z element...: '%s'" % (file_name, l)) element = Element.get_element(elem) if element.number == 0: # lone pair of somesuch atoms.append(None) continue anum = anums.get(element.name, 0) + 1 anums[element.name] = anum a = add_atom("%s%d" % (element.name, anum), element, r, array([x,y,z]), serial_number=serial) serial += 1 atoms.append(a) elif state == "bonds": num_bonds -= 1 if num_bonds == 0: state = "properties" try: a1_index = int(l[:3].strip()) a2_index = int(l[3:6].strip()) order = float(l[6:9].strip()) except ValueError: raise UserError("Bond line of MOL/SDF file '%s' is not a1 a2 order...: '%s'" % (file_name, 1)) a1 = atoms[a1_index-1] a2 = atoms[a2_index-1] if not a1 or not a2: continue s.new_bond(a1, a2).order = order elif state == "properties": if not s.atoms: raise UserError("No atoms found for compound '%s' in MOL/SDF file '%s'" % (name, file_name)) if line.split() == ["M", "END"]: state = "data" reading_data = None elif state == "data": if line == "$$$$": nonblank = False state = "init" elif reading_data == "charges": data_item = line.strip() if data_item: try: data.append(float(data_item)) except ValueError: try: index, charge = data_item.split() index = int(index) - 1 charge = float(charge) except ValueError: raise UserError("Charge data (%s) in %s data is not either a floating-point" " number or an atom index and a floating-point number" % (data_item, orig_data_name)) else: if not indexed_charges: # for indexed charges, the first thing is a count data.pop() indexed_charges = True data.append((index, charge)) else: if not indexed_charges and len(atoms) != len(data): raise UserError("Number of charges (%d) in %s data not equal to number of atoms" " (%d)" % (len(data), orig_data_name, len(atoms))) if indexed_charges: for a in atoms: # charge defaults to 0.0, so don't need to set non-indexed for index, charge in data: atoms[index].charge = charge else: for a, charge in zip(atoms, data): a.charge = charge if "mmff94" in data_name: s.charge_model = "MMFF94" reading_data = None elif reading_data == "cid": data_item = line.strip() if data_item: try: cid = int(data_item) except ValueError: raise UserError("PubChem CID (%s) is %s data is not an integer" % (data_item, orid_data_name)) s.name = "pubchem:%d" % cid s.prefix_html_title = False s.get_html_title = lambda *args, cid=cid: 'PubChem entry <a href="https://pubchem.ncbi.nlm.nih.gov/compound/%d">%d</a>' % (cid, cid) s.has_formatted_metadata = lambda *args: False reading_data = None elif line.startswith('>'): try: lp = line.index('<') rp = line[lp+1:].index('>') + lp + 1 except (IndexError, ValueError): continue orig_data_name = line[lp+1:rp] data_name = orig_data_name.lower() if data_name.endswith("charges") and "partial" in data_name: reading_data = "charges" indexed_charges = False data = [] elif data_name == "pubchem_compound_cid": reading_data = "cid" except BaseException: for s in structures: s.delete() raise finally: stream.close() if nonblank and state not in ["data", "init"]: if structures: session.logger.warning("Extraneous text after final $$$$ in MOL/SDF file '%s'" % file_name) else: raise UserError("Unexpected end of file (parser state: %s) in MOL/SDF file '%s'" % (state, file_name)) return structures, ""
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
class MolecularDynamicsTool(HtmlToolInstance): ''' # Properties for the tool ''' SESSION_ENDURING = False SESSION_SAVE = False CUSTOM_SCHEME = "kmd" def __init__(self, session, tool_name): super().__init__(session, tool_name, size_hint=(500, 500), show_http_in_help=False) self.display_name = DISPLAY_NAME self._build_ui() self.atomStruct = None self.atomLocation = [] self.surfacePositions = [] self.attributeFile = False # Loads the html view to be shown def _build_ui(self): html_file = os.path.join(os.path.dirname(__file__), "dynamics.html") self.html_view.setUrl(pathlib.Path(html_file).as_uri()) # This is to delete the model from the view def delete(self): super(HtmlToolInstance, self).delete() self.deleteModel() ''' # This handles when a window.location becomes kmd:<command> # # @param url - The command ''' def handle_scheme(self, url): command = url.path() if command.startswith("log"): self.session.logger.info(command[len("log_"):]) elif command == "Open": self.openFile() elif command.startswith("FrameChange"): frame = int(command[len("FrameChange_T_"):]) self.changeModel(frame, command[12] == 'T') elif command == "MovieDone": movie_encode(self.session, output=[self.movieName], format="h264") elif command == "MovieStart": fileName, filt = QFileDialog.getSaveFileName( caption="Save Movie", filter="MP4 File (*.mp4)") if fileName == "": return if fileName[-4:] != ".mp4": fileName = fileName + ".mp4" self.movieName = fileName movie_record(self.session) self.html_view.runJavaScript("startMovie()") elif command == "Attribute": self.loadAttributeFile() else: self.session.logger.info("Unknown Command: " + str(url)) def loadAttributeFile(self): fileName, filt = QFileDialog.getOpenFileName( caption="Open Attribute File", filter="Attribute File (*.dat)") if fileName == "": return fileObj = open(fileName) lines = fileObj.readlines()[3:] fileObj.close() mapVals = {} for line in lines: parts = line.split('\t')[1:] mapVals[int(parts[0][1:])] = float(parts[1]) maxValue = max(mapVals.values()) minValue = min(mapVals.values()) midValue = (maxValue + minValue) / 2 # Color from min (blue) to mid (white) to max (red) for key in mapVals.keys(): value = mapVals[key] per = (value - minValue) / (midValue - minValue) - 1 color = abs(per) * 255 colorSet = [255, 255 - color, 255 - color, 255 ] if per > 0 else [255 - color, 255 - color, 255, 255] colorSet = array(colorSet) self.atomStruct.residues[key - 1].ribbon_color = colorSet self.atomStruct.residues[key - 1].atoms.colors = colorSet js = "attributeName = '" + fileName.split( "/")[-1] + "';updateDisplay()" self.html_view.runJavaScript(js) self.attributeFile = True if self.atomStruct.surfaces() != []: run(self.session, "color #" + self.atomStruct.surfaces()[0].id_string + " fromatoms", log=False) # This is to remove model for changing def deleteModel(self): if self.atomStruct == None: return if self.atomStruct.deleted: return if self.atomStruct.surfaces() != []: self.session.models.remove([self.atomStruct.surfaces()[0]]) self.session.models.remove([self.atomStruct]) self.attributeFile = False self.atomStruct = None self.atomLocation = [] self.surfacePositions = [] js = "modelName = null; frameCount = 0; reset();" self.html_view.runJavaScript(js) # This is to move the atoms def changeModel(self, frame, surf): locs = self.atomLocation[frame] atms = self.atomStruct.atoms atms.coords = locs if surf: ### Need to recalculate the surfce here if self.atomStruct.surfaces() == []: surface(self.session, atoms=self.atomStruct.atoms) else: surfPos = self.surfacePositions[frame] if surfPos is None: self.session.models.remove([self.atomStruct.surfaces()[0]]) surface(self.session, atoms=self.atomStruct.atoms) self.surfacePositions[frame] = self.atomStruct.surfaces( )[0] else: self.session.models.remove([self.atomStruct.surfaces()[0]]) self.atomStruct.add([surfPos]) ### run(self.session, "color #" + self.atomStruct.surfaces()[0].id_string + " fromatoms", log=False) elif self.atomStruct.surfaces() != []: self.session.models.remove([self.atomStruct.surfaces()[0]]) # This opens a file def openFile(self): fileName, filt = QFileDialog.getOpenFileName( caption="Open PDB Trajectory File", filter="PDB File (*.pdb)") if fileName == "": return if self.atomStruct != None: self.deleteModel() # This create a simple Oxygen atom # Need to get this to display self.atomStruct = AtomicStructure(self.session) myFile = open(fileName) coords = [] prevRes = None res = None modelNum = -1 for line in myFile: if line.startswith("MODEL"): modelNum = modelNum + 1 coords = [] prevRes = None res = None elif line.startswith("ENDMDL"): self.atomLocation.append(array(coords)) elif line.startswith("ATOM"): x = float(line[30:38].strip()) y = float(line[38:46].strip()) z = float(line[46:54].strip()) name = line[12:16].strip() resName = line[17:20].strip() chain = line[21] resSeq = line[22:26].strip() element = line[76:78].strip() if element == "H": continue coords.append([x, y, z]) if modelNum != 0: continue if resName + resSeq != prevRes: prevRes = resName + resSeq res = self.atomStruct.new_residue(resName, chain, int(resSeq)) atom = self.atomStruct.new_atom(name, element) atom.coord = array([x, y, z], dtype=float64) res.add_atom(atom) myFile.close() self.atomStruct.connect_structure() self.session.models.add([self.atomStruct]) for i in range(len(self.atomLocation)): self.surfacePositions.append(None) self.atomStruct.atoms.coords = self.atomLocation[0] fileName = fileName.split("/")[-1] js = "modelName = '" + fileName + "'; frameCount = " + str( len(self.atomLocation)) + "; reset();" self.html_view.runJavaScript(js) run(self.session, "lighting full", log=False)
def openFile(self): fileName, filt = QFileDialog.getOpenFileName( caption="Open PDB Trajectory File", filter="PDB File (*.pdb)") if fileName == "": return if self.atomStruct != None: self.deleteModel() # This create a simple Oxygen atom # Need to get this to display self.atomStruct = AtomicStructure(self.session) myFile = open(fileName) coords = [] prevRes = None res = None modelNum = -1 for line in myFile: if line.startswith("MODEL"): modelNum = modelNum + 1 coords = [] prevRes = None res = None elif line.startswith("ENDMDL"): self.atomLocation.append(array(coords)) elif line.startswith("ATOM"): x = float(line[30:38].strip()) y = float(line[38:46].strip()) z = float(line[46:54].strip()) name = line[12:16].strip() resName = line[17:20].strip() chain = line[21] resSeq = line[22:26].strip() element = line[76:78].strip() if element == "H": continue coords.append([x, y, z]) if modelNum != 0: continue if resName + resSeq != prevRes: prevRes = resName + resSeq res = self.atomStruct.new_residue(resName, chain, int(resSeq)) atom = self.atomStruct.new_atom(name, element) atom.coord = array([x, y, z], dtype=float64) res.add_atom(atom) myFile.close() self.atomStruct.connect_structure() self.session.models.add([self.atomStruct]) for i in range(len(self.atomLocation)): self.surfacePositions.append(None) self.atomStruct.atoms.coords = self.atomLocation[0] fileName = fileName.split("/")[-1] js = "modelName = '" + fileName + "'; frameCount = " + str( len(self.atomLocation)) + "; reset();" self.html_view.runJavaScript(js) run(self.session, "lighting full", log=False)
def _read_block(session, stream, line_number): # XYZ files are stored in blocks, with each block representing # a set of atoms. This function reads a single block # and builds a ChimeraX AtomStructure instance containing # the atoms listed in the block. # First line should be an integer count of the number of # atoms in the block. count_line = stream.readline() if not count_line: # Reached EOF, normal termination condition return None, line_number line_number += 1 try: count = int(count_line) except ValueError: session.logger.error("line %d: atom count missing" % line_number) return None, line_number # Create the AtomicStructure instance for atoms in this block. # All atoms in the structure are placed in one residue # since XYZ format does not partition atoms into groups. from chimerax.atomic import AtomicStructure from numpy import array, float64 s = AtomicStructure(session) residue = s.new_residue("UNK", 'A', 1) # XYZ format supplies the atom element type only, but # ChimeraX keeps track of both the element type and # a unique name for each atom. To construct the unique # atom name, the # 'element_count' dictionary is used # to track the number of atoms of each element type so far, # and the current count is used to build unique atom names. element_count = {} # Next line is a comment line s.comment = stream.readline().strip() line_number += 1 # There should be "count" lines of atoms. for n in range(count): atom_line = stream.readline() if not atom_line: session.logger.error("line %d: atom data missing" % line_number) return None, line_number line_number += 1 # Extract available data parts = atom_line.split() if len(parts) != 4: session.logger.error("line %d: atom data malformatted" % line_number) return None, line_number # Convert to required parameters for creating atom. # Since XYZ format only required atom element, we # create a unique atom name by putting a number after # the element name. xyz = [float(v) for v in parts[1:]] element = parts[0] n = element_count.get(element, 0) + 1 name = element + str(n) element_count[element] = n # Create atom in AtomicStructure instance 's', # set its coordinates, and add to residue atom = s.new_atom(name, element) atom.coord = array(xyz, dtype=float64) residue.add_atom(atom) # Use AtomicStructure method to add bonds based on interatomic distances s.connect_structure() # Updating state such as atom types while adding atoms iteratively # is unnecessary (and generally incorrect for partial structures). # When all atoms have been added, the instance is notified to # tell it to update internal state. s.new_atoms() # Return AtomicStructure instance and current line number return s, line_number
def process_ok_models(self, ok_models_text, stdout_text, get_pdb_model): ok_models_lines = ok_models_text.rstrip().split('\n') headers = [h.strip() for h in ok_models_lines[0].split('\t')][1:] for i, hdr in enumerate(headers): if hdr.endswith(" score"): headers[i] = hdr[:-6] from chimerax.core.utils import string_to_attr attr_names = [ string_to_attr(hdr, prefix="modeller_") for hdr in headers ] from chimerax.atomic import AtomicStructure for attr_name in attr_names: AtomicStructure.register_attr(self.session, attr_name, "Modeller", attr_type=float) from chimerax import match_maker as mm models = [] match_okay = True for i, line in enumerate(ok_models_lines[1:]): fields = line.strip().split() pdb_name, scores = fields[0], [float(f) for f in fields[1:]] model = get_pdb_model(pdb_name) for attr_name, val in zip(attr_names, scores): setattr(model, attr_name, val) model.name = self.target_seq_name if model.num_chains == len(self.match_chains): pairings = list(zip(self.match_chains, model.chains)) mm.match(self.session, mm.CP_SPECIFIC_SPECIFIC, pairings, mm.defaults['matrix'], mm.defaults['alignment_algorithm'], mm.defaults['gap_open'], mm.defaults['gap_extend'], cutoff_distance=mm.defaults['iter_cutoff']) else: match_okay = False models.append(model) if not match_okay: self.session.logger.warning( "The number of model chains does not match the number used from" " the template structure(s) [which can be okay if you closed or modified template" " structures while the job was running], so no superposition of the models onto the" " templates was performed.") reset_alignments = [] for alignment, target_seq in self.targets: alignment.associate(models, seq=target_seq) if alignment.auto_associate: alignment.auto_associate = False reset_alignments.append(alignment) self.session.models.add_group(models, name=self.target_seq_name + " models") for alignment in reset_alignments: alignment.auto_associate = True if self.show_gui and self.session.ui.is_gui: from .tool import ModellerResultsViewer ModellerResultsViewer(self.session, "Modeller Results", models, attr_names)
def _make_structure(self): """Build ChimeraX structure and reset structure data cache""" try: if self._molecule is None: return if self.atomic: from chimerax.atomic import AtomicStructure as SC else: from chimerax.atomic import Structure as SC # Create structure s = SC(self.session, auto_style=self.auto_style) s.name = self._molecule.mol_name SC.register_attr(self.session, "viewdockx_data", "ViewDockX") s.viewdockx_data = self._data if self._molecule.charge_type: s.charge_model = self._molecule.charge_type if self._molecule.mol_type: s.mol2_type = self._molecule.mol_type if self._molecule.mol_comment: s.mol2_comment = self._molecule.mol_comment # Create residues substid2residue = {} for subst_data in self._substs: # ChimeraX limitation: 4-letter residue type name = subst_data.subst_name[:4] chain = subst_data.chain if chain is None or chain == "****": chain = '' residue = s.new_residue(name, chain, subst_data.subst_id) substid2residue[subst_data.subst_id] = residue # Create atoms atomid2atom = {} from numpy import array, float64 for atom_data in self._atoms: name = atom_data.atom_name element = atom_data.atom_type if '.' in element: element = element.split('.')[0] atom = s.new_atom(name, element) atom.coord = array([atom_data.x, atom_data.y, atom_data.z], dtype=float64) if atom_data.charge is not None: atom.charge = atom_data.charge try: residue = substid2residue[atom_data.subst_id] except KeyError: # Must not have been a substructure section residue = s.new_residue("UNK", '', atom_data.subst_id) substid2residue[atom_data.subst_id] = residue residue.add_atom(atom) atomid2atom[atom_data.atom_id] = atom # Create bonds for bond_data in self._bonds: try: origin = atomid2atom[bond_data.origin_atom_id] target = atomid2atom[bond_data.target_atom_id] except KeyError: self.session.logger.warning("bad atom index in bond") else: s.new_bond(origin, target) self.structures.append(s) finally: self._reset_structure()
def _make_structure(self, block): from numpy import array from .maestro import IndexAttribute if self.atomic: from chimerax.atomic import AtomicStructure as SC else: from chimerax.atomic import Structure as SC from chimerax.atomic import Element atoms = block.get_sub_block("m_atom") if atoms is None: print("No m_atom block found") return None bonds = block.get_sub_block("m_bond") s = SC(self.session, auto_style=self.auto_style) SC.register_attr(self.session, "viewdockx_data", "ViewDockX") residue_map = {} atom_map = {} for row in range(atoms.size): attrs = atoms.get_attribute_map(row) index = attrs[IndexAttribute] # Get residue data and create if necessary res_seq = attrs["i_m_residue_number"] insert_code = attrs.get("s_m_insertion_code", None) if not insert_code: insert_code = ' ' chain_id = attrs.get("s_m_chain_name", ' ') res_key = (chain_id, res_seq, insert_code) try: r = residue_map[res_key] except KeyError: res_name = attrs.get("s_m_pdb_residue_name", "UNK") r = s.new_residue(res_name, chain_id, res_seq, insert_code) residue_map[res_key] = r rgb = attrs.get("s_m_ribbon_color_rgb", None) if rgb: r.ribbon_color = self._get_color(rgb) # Get atom data and create try: name = attrs["s_m_pdb_atom_name"] except KeyError: name = attrs.get("s_m_atom_name", "") name = name.strip() atomic_number = attrs.get("i_m_atomic_number", 6) element = Element.get_element(atomic_number) if not name: name = element.name a = s.new_atom(name, element) a.coord = array([atoms.get_attribute("r_m_x_coord", row), atoms.get_attribute("r_m_y_coord", row), atoms.get_attribute("r_m_z_coord", row)]) try: a.bfactor = attrs["r_m_pdb_tfactor"] except (KeyError, TypeError): a.bfactor = 0.0 try: a.occupancy = attrs["r_m_pdb_occupancy"] except (KeyError, TypeError): a.occupancy = 1.0 rgb = attrs.get("s_m_color_rgb", None) if rgb: a.color = self._get_color(rgb) # Add atom to residue and to atom map for bonding later r.add_atom(a) atom_map[index] = a if bonds is None or bonds.size == 0: s.connect_structure() else: for row in range(bonds.size): attrs = bonds.get_attribute_map(row) fi = attrs["i_m_from"] ti = attrs["i_m_to"] if ti < fi: # Bonds are reported in both directions. We only need one. continue afi = atom_map[fi] ati = atom_map[ti] b = s.new_bond(afi, ati) b.order = attrs["i_m_order"] return s
def finalize_init(self, mgr, res_type, rot_lib, *, table_info=None): self.mgr = mgr self.res_type = res_type self.rot_lib = rot_lib self.subdialogs = {} from collections import OrderedDict self.opt_columns = OrderedDict() self.handlers = [ self.mgr.triggers.add_handler('fewer rotamers', self._fewer_rots_cb), self.mgr.triggers.add_handler('self destroyed', self._mgr_destroyed_cb), ] from chimerax.ui.widgets import ItemTable global _settings if _settings is None: from chimerax.core.settings import Settings class _RotamerSettings(Settings): EXPLICIT_SAVE = {ItemTable.DEFAULT_SETTINGS_ATTR: {}} _settings = _RotamerSettings(self.session, "Rotamers") from chimerax.ui import MainToolWindow self.tool_window = tw = MainToolWindow(self) parent = tw.ui_area from PyQt5.QtWidgets import QVBoxLayout, QLabel, QCheckBox, QGroupBox, QWidget, QHBoxLayout, \ QPushButton, QRadioButton, QButtonGroup, QGridLayout from PyQt5.QtCore import Qt self.layout = layout = QVBoxLayout() parent.setLayout(layout) lib_display_name = self.session.rotamers.library(rot_lib).display_name layout.addWidget( QLabel("%s %s rotamers" % (lib_display_name, res_type))) column_disp_widget = QWidget() class RotamerTable(ItemTable): def sizeHint(self): from PyQt5.QtCore import QSize return QSize(350, 450) self.table = RotamerTable(column_control_info=(column_disp_widget, _settings, {}, True, None, None, False), auto_multiline_headers=False) for i in range(len(self.mgr.rotamers[0].chis)): self.table.add_column("Chi %d" % (i + 1), lambda r, i=i: r.chis[i], format="%6.1f") self.table.add_column("Probability", "rotamer_prob", format="%.6f ") if table_info: table_state, additional_col_info = table_info for col_type, title, data_fetch, display_format in additional_col_info: AtomicStructure.register_attr( self.session, data_fetch, self.registerer, attr_type=(int if data_fetch.startswith("num") else float)) self.opt_columns[col_type] = self.table.add_column( title, data_fetch, format=display_format) else: table_state = None self.table.data = self.mgr.rotamers self.table.launch(session_info=table_state) if not table_info: self.table.sortByColumn(len(self.mgr.rotamers[0].chis), Qt.DescendingOrder) self.table.selection_changed.connect(self._selection_change) layout.addWidget(self.table) if mgr.base_residue.name == res_type: self.retain_side_chain = QCheckBox("Retain original side chain") self.retain_side_chain.setChecked(False) layout.addWidget(self.retain_side_chain) else: self.retain_side_chain = None column_group = QGroupBox("Column display") layout.addWidget(column_group) cg_layout = QVBoxLayout() cg_layout.setContentsMargins(0, 0, 0, 0) cg_layout.setSpacing(0) column_group.setLayout(cg_layout) cg_layout.addWidget(column_disp_widget) add_col_layout = QGridLayout() add_col_layout.setContentsMargins(0, 0, 0, 0) add_col_layout.setSpacing(0) cg_layout.addLayout(add_col_layout) self.add_col_button = QPushButton("Calculate") add_col_layout.addWidget(self.add_col_button, 0, 0, alignment=Qt.AlignRight) radio_layout = QVBoxLayout() radio_layout.setContentsMargins(0, 0, 0, 0) add_col_layout.addLayout(radio_layout, 0, 1, alignment=Qt.AlignLeft) self.button_group = QButtonGroup() self.add_col_button.clicked.connect( lambda checked, *, bg=self.button_group: self._show_subdialog( bg.checkedButton().text())) for add_type in ["H-Bonds", "Clashes", "Density"]: rb = QRadioButton(add_type) rb.clicked.connect(self._update_button_text) radio_layout.addWidget(rb) if not self.button_group.buttons(): rb.setChecked(True) self.button_group.addButton(rb) self.ignore_solvent_button = QCheckBox("Ignore solvent") self.ignore_solvent_button.setChecked(True) add_col_layout.addWidget(self.ignore_solvent_button, 1, 0, 1, 2, alignment=Qt.AlignCenter) from PyQt5.QtWidgets import QDialogButtonBox as qbbox bbox = qbbox(qbbox.Ok | qbbox.Cancel | qbbox.Help) bbox.accepted.connect(self._apply_rotamer) bbox.rejected.connect(self.tool_window.destroy) from chimerax.core.commands import run bbox.helpRequested.connect( lambda run=run, ses=self.session: run(ses, "help " + self.help)) layout.addWidget(bbox) self.tool_window.manage(placement=None)
def get_chimera(self, session, coordsets=False, filereader=None): """returns a chimerax equivalent of self""" struc = AtomicStructure(session, name=self.name) struc.comment = self.comment self.update_chix(struc) if coordsets and filereader is not None and filereader.all_geom is not None: #make a trajectory #each list of atoms in filereader.all_geom is a frame in the trajectory #replace previous coordinates #this matters when a filereader is given because the #geometry created from a filereader (which was probably passed as geom) #is the last geometry in the log or xyz file xyzs = self.all_geom_coordsets(filereader) struc.add_coordsets(xyzs, replace=True) struc.active_coordset_id = len(xyzs) # if it's a frequency file, draw TS bonds for imaginary modes if filereader is not None and "frequency" in filereader.other: for mode in filereader.other["frequency"].data: if mode.frequency < 0: max_disp = max(np.linalg.norm(x) for x in mode.vector) cur_coords = self.coords coord_forward = self.coords + (0.2 / max_disp) * mode.vector coord_reverse = self.coords - (0.2 / max_disp) * mode.vector forward_connectivity = np.zeros( (len(self.atoms), len(self.atoms))) reverse_connectivity = np.zeros( (len(self.atoms), len(self.atoms))) self.update_geometry(coord_forward) self.refresh_connected() for i, atom1 in enumerate(self.atoms): for j, atom2 in enumerate(self.atoms[:i]): if atom1 in atom2.connected: forward_connectivity[i, j] = 1 forward_connectivity[j, i] = 1 self.update_geometry(coord_reverse) self.refresh_connected() for i, atom1 in enumerate(self.atoms): for j, atom2 in enumerate(self.atoms[:i]): if atom1 in atom2.connected: reverse_connectivity[i, j] = 1 reverse_connectivity[j, i] = 1 changes = forward_connectivity - reverse_connectivity for i, atom1 in enumerate(self.atoms): for j, atom1 in enumerate(self.atoms[:i]): if changes[i, j] != 0: sel = _FauxAtomSelection( atoms=(struc.atoms[i], struc.atoms[j])) tsbond(session, sel) self.update_geometry(cur_coords) if filereader is not None and "Mulliken Charges" in filereader.other: for atom, charge in zip(struc.atoms, filereader.other["Mulliken Charges"]): atom.mullikenCharge = charge atom.charge = charge if not any(attr[0] == "mullikenCharge" for attr in atom.custom_attrs): atom.register_attr(session, "mullikenCharge", "seqcrow ResidueCollection.get_chimera", attr_type=float) if not any(attr[0] == "charge" for attr in atom.custom_attrs): atom.register_attr(session, "charge", "seqcrow ResidueCollection.get_chimera", attr_type=float) if filereader is not None and "NPA Charges" in filereader.other: for atom, charge in zip(struc.atoms, filereader.other["NPA Charges"]): atom.npaCharge = charge atom.charge = charge if not any(attr[0] == "npaCharge" for attr in atom.custom_attrs): atom.register_attr(session, "npaCharge", "seqcrow ResidueCollection.get_chimera", attr_type=float) if not any(attr[0] == "charge" for attr in atom.custom_attrs): atom.register_attr(session, "charge", "seqcrow ResidueCollection.get_chimera", attr_type=float) if filereader is not None and "Nuclear ZEff" in filereader.other: for atom, zeff in zip(struc.atoms, filereader.other["Nuclear ZEff"]): atom.Zeff = zeff if not any(attr[0] == "Zeff" for attr in atom.custom_attrs): atom.register_attr(session, "Zeff", "seqcrow ResidueCollection.get_chimera", attr_type=float) if filereader is not None and "Nuclear spins" in filereader.other: for atom, spin in zip(struc.atoms, filereader.other["Nuclear spins"]): atom.nuclearSpin = spin if not any(attr[0] == "nuclearSpin" for attr in atom.custom_attrs): atom.register_attr(session, "nuclearSpin", "seqcrow ResidueCollection.get_chimera", attr_type=int) if filereader is not None: if any(attr[0] == "aarontools_filereader" for attr in struc.custom_attrs): struc.register_attr( session, "filereader", "SEQCROW", attr_type=FileReader, ) struc.filereader = filereader return struc
class MolecularDynamicsTool(HtmlToolInstance): ''' # Properties for the tool ''' SESSION_ENDURING = False SESSION_SAVE = False CUSTOM_SCHEME = "kmd" def __init__(self, session, tool_name): super().__init__(session, tool_name, size_hint=(500, 500), show_http_in_help=False) self.display_name = DISPLAY_NAME self._build_ui() self.atomStruct = None self.atomLocation = [] self.surfacePositions = [] self.attributeFile = False # Loads the html view to be shown def _build_ui(self): html_file = os.path.join(os.path.dirname(__file__), "dynamics.html") self.html_view.setUrl(pathlib.Path(html_file).as_uri()) # This is to delete the model from the view def delete(self): super(HtmlToolInstance, self).delete() self.deleteModel() ''' # This handles when a window.location becomes kmd:<command> # # @param url - The command ''' def handle_scheme(self, url): command = url.path() if command.startswith("log"): self.session.logger.info(command[len("log_"):]) elif command == "Open": self.openFile() elif command.startswith("FrameChange"): frame = int(command[len("FrameChange_T_"):]) self.changeModel(frame, command[12] == 'T') elif command == "MovieDone": movie_encode(self.session, output=[self.movieName], format="h264") elif command == "MovieStart": fileName, filt = QFileDialog.getSaveFileName( caption="Save Movie", filter="MP4 File (*.mp4)") if fileName == "": return if fileName[-4:] != ".mp4": fileName = fileName + ".mp4" self.movieName = fileName movie_record(self.session) self.html_view.runJavaScript("startMovie()") elif command == "Attribute": self.loadAttributeFile() else: self.session.logger.info("Unknown Command: " + str(url)) def loadAttributeFile(self): fileName, filt = QFileDialog.getOpenFileName( caption="Open Attribute File", filter="Attribute File (*.dat)") if fileName == "": return fileObj = open(fileName) lines = fileObj.readlines()[3:] fileObj.close() mapVals = {} for line in lines: parts = line.split('\t')[1:] mapVals[int(parts[0][1:])] = float(parts[1]) maxValue = max(mapVals.values()) minValue = min(mapVals.values()) midValue = (maxValue + minValue) / 2 # Color from min (blue) to mid (white) to max (red) for key in mapVals.keys(): value = mapVals[key] per = (value - minValue) / (midValue - minValue) - 1 color = abs(per) * 255 colorSet = [255, 255 - color, 255 - color, 255 ] if per > 0 else [255 - color, 255 - color, 255, 255] colorSet = array(colorSet) self.atomStruct.residues[key - 1].ribbon_color = colorSet self.atomStruct.residues[key - 1].atoms.colors = colorSet js = "attributeName = '" + fileName.split( "/")[-1] + "';updateDisplay()" self.html_view.runJavaScript(js) self.attributeFile = True if self.atomStruct.surfaces() != []: run(self.session, "color #" + self.atomStruct.surfaces()[0].id_string + " fromatoms", log=False) # This is to remove model for changing def deleteModel(self): if self.atomStruct == None: return if self.atomStruct.deleted: return if self.atomStruct.surfaces() != []: self.session.models.remove([self.atomStruct.surfaces()[0]]) self.session.models.remove([self.atomStruct]) self.attributeFile = False self.atomStruct = None self.atomLocation = [] self.surfacePositions = [] js = "modelName = null; frameCount = 0; reset();" self.html_view.runJavaScript(js) # This is to move the atoms def changeModel(self, frame, surf): locs = self.atomLocation[frame] atms = self.atomStruct.atoms atms.coords = locs if surf: ### Need to recalculate the surfce here if self.atomStruct.surfaces() == []: surface(self.session, atoms=self.atomStruct.atoms) else: surfPos = self.surfacePositions[frame] if surfPos is None: self.session.models.remove([self.atomStruct.surfaces()[0]]) surface(self.session, atoms=self.atomStruct.atoms) self.surfacePositions[frame] = self.atomStruct.surfaces( )[0] else: self.session.models.remove([self.atomStruct.surfaces()[0]]) self.atomStruct.add([surfPos]) ### run(self.session, "color #" + self.atomStruct.surfaces()[0].id_string + " fromatoms", log=False) elif self.atomStruct.surfaces() != []: self.session.models.remove([self.atomStruct.surfaces()[0]]) # This opens a file def openFile(self): fileName, filt = QFileDialog.getOpenFileName(caption="Open Trajectory File", filter="PDB File (*.pdb);;" \ "H5 Trajectory File (*.h5);;" \ "HDF5 Trajectory File (*.hdf5)" \ "GRO Topology File (*.gro)" ) print(fileName) print(filt) if fileName == "": return if filt == "(*.gro)": gro_fileName = fileName fileName, filt = QFileDialog.getOpenFileName(caption="Open Trajectory File", filter="XTC Trajectory File (*.xtc);;" \ "TRR Trajectory File (*.trr);;" \ ) print(fileName) print(filt) else: gro_fileName = None if self.atomStruct != None: self.deleteModel() # This create a simple Oxygen atom # Need to get this to display self.atomStruct = AtomicStructure(self.session) if filt in ["(*.xtc)", "(*.trr)"]: trj = mdtraj.load(fileName, topology=gro_fileName) else: trj = mdtraj.load(fileName) trj.atom_slice(trj.topology.select("not element H")) table, bond_table = trj.topology.to_dataframe() table.rename(columns={ "name": "atomname", "resName": "residue_name", "chainID": "chain_id", "resSeq": "pos" }, inplace=True) table["pos"] = table["pos"].astype(int) xyz = trj.xyz[0] table["loc"] = [array(x, dtype=float64) for x in xyz] res_infos = table[["residue_name", "chain_id", "pos" ]].drop_duplicates(keep="first").to_dict("records") for res in res_infos: residue = self.atomStruct.new_residue(**res) qry = ' and '.join( ['{} == {}'.format(k, v) for k, v in res.items()]) res_atoms = table.query(qry)[["name", "element", "loc"]].to_dict("records") for a in res_atoms: atom = self.atomStruct.new_atom({ "name": a["name"], "element": a["element"] }) atom.coord = a["loc"] residue.add_atom(atom) for frame in range(1, trj.n_frames): xyz = trj.xyz[frame] self.atomLocation.append(array(xyz, dtype=float64)) self.atomStruct.connect_structure() self.session.models.add([self.atomStruct]) for i in range(len(self.atomLocation)): self.surfacePositions.append(None) self.atomStruct.atoms.coords = self.atomLocation[0] fileName = fileName.split("/")[-1] js = "modelName = '" + fileName + "'; frameCount = " + str( len(self.atomLocation)) + "; reset();" self.html_view.runJavaScript(js) run(self.session, "lighting full", log=False)
def register_model_isolde_init_attr(session): from chimerax.atomic import AtomicStructure AtomicStructure.register_attr(session, 'isolde_initialized', 'isolde', attr_type=bool)
def set_state_from_snapshot(self, session, data): AtomicStructure.set_state_from_snapshot(self, session, data['atomic structure state'])