Skip to content

Commit

Permalink
Finalize bcif support.
Browse files Browse the repository at this point in the history
BCIF is the default format for fetching from the pdb now.  The secondary
structure annotations have changed, which necessitated regenerating all
reference images.

Also, added bfactor support to regular CIFs to address Issue #792
  • Loading branch information
dkoes committed Jul 6, 2024
1 parent 5edfb1a commit 38198cc
Show file tree
Hide file tree
Showing 280 changed files with 7,909 additions and 352 deletions.
2,973 changes: 2,691 additions & 282 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
"dependencies": {
"iobuffer": "^5.3.1",
"netcdfjs": "^3.0.0",
"npm": "^10.8.1",
"nvm": "^0.0.4",
"pako": "^2.1.0",
"upng-js": "^2.1.0"
},
Expand Down
2 changes: 1 addition & 1 deletion src/GLModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2836,7 +2836,7 @@ export class GLModel {
console.log("Unknown format: " + format);
// try to guess correct format from data contents
if (data instanceof Uint8Array) {
format = "mmtf"; //currently only supported binary format?
format = "bcif"; //mmtf deprecated so go with bcif
} else if ((data as string).match(/^@<TRIPOS>MOLECULE/gm)) {
format = "mol2";
} else if ((data as string).match(/^data_/gm) && (data as string).match(/^loop_/gm)) {
Expand Down
196 changes: 177 additions & 19 deletions src/parsers/BCIF.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,145 @@ import { isEmpty } from "./utils/isEmpty";
declare var MMTF: MMTFobj;


class Connectivity {
C: Record<string, Record<string, Record<string, number>>> = {};

constructor(comp_bond) {
if (comp_bond) {
let ids = comp_bond.getField('comp_id');
let a1s = comp_bond.getField('atom_id_1');
let a2s = comp_bond.getField('atom_id_2');
let orders = comp_bond.getField('value_order');

for (let i = 0; i < ids.length; i++) {
let resn = ids[i];
let a1 = a1s[i];
let a2 = a2s[i];
let oname = orders[i];
let o = 1;
if (oname == 'doub') o = 2;
else if (oname == 'trip') o = 3;

if (this.C[resn] == undefined) {
this.C[resn] = {};
}
if (this.C[resn][a1] == undefined) {
this.C[resn][a1] = {};
}
if (this.C[resn][a2] == undefined) {
this.C[resn][a2] = {};
}
this.C[resn][a1][a2] = o;
this.C[resn][a2][a1] = o;
}
}
}

//returns bond order, zero if not connected
order(resn: string, atom1: string, atom2: string): number {
if (this.C[resn] !== undefined) {
if (this.C[resn][atom1] !== undefined) {
if (this.C[resn][atom1][atom2] !== undefined) {
return this.C[resn][atom1][atom2];
}
}
}
return 0;
}
}

/* Class for recording inter-component connectivity */
class StructConn {
public C: [[string,number,string,string],[string,number,string,string],number][] = []; //chain,resi,resn,atomn

constructor(struct_conn) {
if(struct_conn) {
//have no idea what the deal with with ptnr3..
let types = struct_conn.getField('conn_type_id');
let chain1 = struct_conn.getField('ptnr1_label_asym_id');
let resi1 = struct_conn.getField('ptnr1_label_seq_id');
let resn1 = struct_conn.getField('ptnr1_label_comp_id');
let a1 = struct_conn.getField('ptnr1_label_atom_id');
let chain2 = struct_conn.getField('ptnr2_label_asym_id');
let resi2 = struct_conn.getField('ptnr2_label_seq_id');
let resn2 = struct_conn.getField('ptnr2_label_comp_id');
let a2 = struct_conn.getField('ptnr2_label_atom_id');
let bo = struct_conn.getField('pdbx_value_order');

for(let i = 0; i < types.length; i++) {
if(types[i] == 'disulf' || types[i] == 'covale') { //metal too?
let o = bo ? (bo[i] == "" ? 1: parseInt(bo[i])) : 1;
this.C.push([[chain1[i],resi1[i],resn1[i],a1[i]],[chain2[i],resi2[i],resn2[i],a2[i]],o]);
}
}

}
}
}

/* group atoms by chain/resid */
class Residues {
R: Record<string, Record<number, Record<string, Array<AtomSpec>>>> = {}; //chain, resid, resn (redundant), atom list
constructor() {

}

add(a: AtomSpec) {
if (this.R[a.lchain] == undefined) this.R[a.lchain] = {};
if (this.R[a.lchain][a.lresi] == undefined) this.R[a.lchain][a.lresi] = {};
if (this.R[a.lchain][a.lresi][a.lresn] == undefined) this.R[a.lchain][a.lresi][a.lresn] = [];
this.R[a.lchain][a.lresi][a.lresn].push(a);
this.R[a.lchain][a.lresi][a.lresn][a.atom] = a; //look up by atom name
}

private geta([ch,resi,resn,aname]: [string,number,string,string]) {
if(this.R[ch] !== undefined &&
this.R[ch][resi] !== undefined &&
this.R[ch][resi][resn] !== undefined) {
return this.R[ch][resi][resn][aname];
}
return undefined;
}

setBonds(C: Connectivity, SC: StructConn) {
for(let ch in this.R) {
for(let resi in this.R[ch]) {
for(let resn in this.R[ch][resi]) {
let atoms = this.R[ch][resi][resn];
for(let i = 0; i < atoms.length; i++) {
for(let j = i+1; j < atoms.length; j++) {
let a1 = atoms[i];
let a2 = atoms[j];
let bo = C.order(resn,a1.atom,a2.atom);
if(bo > 0) {
a1.bonds.push(a2.index);
a2.bonds.push(a1.index);
a1.bondOrder.push(bo);
a2.bondOrder.push(bo);
}
}
}
}
}
}

for(let conn of SC.C) {
let a1 = conn[0];
let a2 = conn[1];
let bo = conn[2];
let atom1 = this.geta(a1);
let atom2 = this.geta(a2);
if(atom1 != undefined && atom2 != undefined) {
atom1.bonds.push(atom2.index);
atom2.bonds.push(atom1.index);
atom1.bondOrder.push(bo);
atom2.bondOrder.push(bo);
}
}

}
}


/**
* @param bindata - binary UInt8Array buffer or a base64 encoded string
Expand All @@ -22,7 +161,7 @@ declare var MMTF: MMTFobj;
export function BCIF(bindata: any, options: ParserOptionsSpec) {

var noH = !options.keepH; // suppress hydrogens by default
//var selAltLoc = options.altLoc ? options.altLoc : 'A'; //default alternate location to select if present
var selAltLoc = options.altLoc ? options.altLoc : 'A'; //default alternate location to select if present
var computeStruct = !options.noComputeSecondaryStructure;
//var assemblyIndex = options.assemblyIndex ? options.assemblyIndex : 0;
const noAssembly = !options.doAssembly; // don't assemble by default
Expand Down Expand Up @@ -59,10 +198,10 @@ export function BCIF(bindata: any, options: ParserOptionsSpec) {



//loop over models,
//loop over models
for (let m = 0; m < numModels; m++) {

const serialToIndex: number[] = []; // map from pdb serial to index in atoms
let startm = atoms.length;
const serialToIndex: [number, number][] = []; // map from pdb serial to index in atoms
modelData.push({ symmetries: [] });
atoms.push([]);

Expand Down Expand Up @@ -158,8 +297,13 @@ export function BCIF(bindata: any, options: ParserOptionsSpec) {
modelData[modelData.length - 1].symmetries.push(matrix);
}
}
//atom info

//extract connectivity information
let connect = new Connectivity(cats.chem_comp_bond);
let residues = new Residues();
let sconnect = new StructConn(cats.struct_conn);

//atom info
let asites = cats.atom_site;
let atomCount = asites.rowCount;
let group_pdb = asites.getField('group_PDB')
Expand Down Expand Up @@ -192,7 +336,10 @@ export function BCIF(bindata: any, options: ParserOptionsSpec) {
if (options.multimodel) {
if (!options.onemol) {
atoms.push([]);
modelData.push(modelData[modelData.length-1]);
modelData.push(modelData[modelData.length - 1]);
curmodel = modelnums[i];
residues.setBonds(connect, sconnect);
residues = new Residues();
}
} else {
break; //first model only
Expand Down Expand Up @@ -225,13 +372,15 @@ export function BCIF(bindata: any, options: ParserOptionsSpec) {
: label_comp_id
? label_comp_id[i].trim()
: undefined;
atom.lresn = label_comp_id ? label_comp_id[i].trim() : undefined;
atom.atom = auth_atom_id
? auth_atom_id[i].replace(/"/gm, "")
: label_atom_id
? label_atom_id[i].replace(/"/gm, "")
: undefined; //"primed" names are in quotes

atom.icode = icodes ? icodes[i] : undefined;
atom.altLoc = atom.icode;
atom.hetflag =
!group_pdb ||
group_pdb[i] === "HETA" ||
Expand All @@ -244,32 +393,41 @@ export function BCIF(bindata: any, options: ParserOptionsSpec) {
atom.elem = elem[0].toUpperCase() + elem.substring(1, 2).toLowerCase();
if (bfactors) atom.b = bfactors[i];

if(noH && atom.elem == 'H') {
if (noH && atom.elem == 'H') {
continue;
}
if (atom.altLoc != '' && atom.altLoc != selAltLoc && selAltLoc != '*') {
continue;
}

atom.bonds = [];
atom.ss = "c";
atom.serial = serials[i];
serialToIndex[atom.serial] = i;
atom.model = curmodel;
atom.bondOrder = [];
atom.properties = {};
atom.index = atoms[atoms.length - 1].length;
serialToIndex[atom.serial] = [atoms.length, atom.index];
atoms[atoms.length - 1].push(atom);
}
residues.add(atom);

}

residues.setBonds(connect, sconnect);
// Assign secondary structures from pdb file
if (!isEmpty(sslookup)) {
let matoms = atoms[atoms.length - 1];
for (let i = 0; i < matoms.length; i++) {
const atom = matoms[i];
if (atom === undefined) continue;
if (atom.lchain in sslookup && atom.lresi in sslookup[atom.lchain]) {
const code = sslookup[atom.lchain][atom.lresi];
atom.ss = code[0];
if (code.length > 1) {
if (code[1] == "1") atom.ssbegin = true;
else if (code[1] == "2") atom.ssend = true;
for (let mi = startm; mi < atoms.length; mi++) {
let matoms = atoms[mi];
for (let i = 0; i < matoms.length; i++) {
const atom = matoms[i];
if (atom === undefined) continue;
if (atom.lchain in sslookup && atom.lresi in sslookup[atom.lchain]) {
const code = sslookup[atom.lchain][atom.lresi];
atom.ss = code[0];
if (code.length > 1) {
if (code[1] == "1") atom.ssbegin = true;
else if (code[1] == "2") atom.ssend = true;
}
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/parsers/CIF.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,9 @@ export function CIF(str: string, options: ParserOptionsSpec = {}) {
!mmCIF._atom_site_group_pdb ||
mmCIF._atom_site_group_pdb[i] === "HETA" ||
mmCIF._atom_site_group_pdb[i] === "HETATM";
if(mmCIF._atom_site_b_iso_or_equiv ) {
atom.b = parseFloat(mmCIF._atom_site_b_iso_or_equiv[i]);
}
let elem = "X";
if (mmCIF._atom_site_type_symbol) {
elem = mmCIF._atom_site_type_symbol[i].replace(/\(?\+?\d+.*/, "");
Expand Down
23 changes: 14 additions & 9 deletions src/parsers/VASP.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

import { Matrix3 } from "../WebGL";

import { assignBonds } from "./utils/assignBonds";
import { ParserOptionsSpec } from "./ParserOptionsSpec";

/**
* @param {string}
Expand All @@ -10,9 +12,11 @@ import { assignBonds } from "./utils/assignBonds";
* @category Parsers
*/

export function VASP(str: string, options?) {
export function VASP(str: string, options: ParserOptionsSpec = {}) {
var atoms: any[][] & Record<string, any> = [[]];
var lattice: Record<string, number | Float32Array> = {};
const assignbonds =
options.assignBonds === undefined ? true : options.assignBonds;

var lines = str.replace(/^\s+/, "").split(/\r?\n/);

Expand Down Expand Up @@ -62,9 +66,9 @@ export function VASP(str: string, options?) {
(lines[6] as any).replace(/^\s+/, "").split(/\s+/)
);
var vaspMode = lines[7].replace(/\s+/, "");

var selective = false
if (vaspMode.match(/S/)){
if (vaspMode.match(/S/)) {
selective = true
vaspMode = lines[8].replace(/\s+/, "");
}
Expand All @@ -87,13 +91,13 @@ export function VASP(str: string, options?) {
return atoms;
}

if (selective){
if (selective) {
lines.splice(0, 9);
}
else{
else {
lines.splice(0, 8);
}

var atomCounter = 0;

for (var i = 0, len = atomSymbols.length; i < len; i++) {
Expand Down Expand Up @@ -135,9 +139,10 @@ export function VASP(str: string, options?) {
atomCounter += atomSpeciesNumber[i];
}

for (let i = 0; i < atoms.length; i++) {
assignBonds(atoms[i], options);
if (assignbonds) {
for (let i = 0; i < atoms.length; i++) {
assignBonds(atoms[i], options);
}
}

return atoms;
}
2 changes: 1 addition & 1 deletion src/parsers/utils/areConnected.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function areConnected(atom1: AtomSpec, atom2: AtomSpec, options: ParserOp
isNaN(distSquared) ||
distSquared < 0.5 ||
distSquared > maxsq ||
(atom1.altLoc !== atom2.altLoc && atom1.altLoc !== " " && atom2.altLoc !== " ")
(atom1.altLoc !== atom2.altLoc && atom1.altLoc.trim() !== "" && atom2.altLoc.trim() !== "")
)
return false;

Expand Down
2 changes: 2 additions & 0 deletions src/specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { ColorSpec } from "./colors";
export interface AtomSpec {
/** Parent residue name */
resn?: string;
/** Residue label name */
lresn?: string;
/** Atom's x coordinate */
x?: number;
/** Atom's y coordinate */
Expand Down
2 changes: 1 addition & 1 deletion src/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ export function download(query, viewer, options, callback?) {
}
else {
if (query.substring(0, 4) === 'pdb:') {
type = 'pdb';
type = 'bcif';
if (options && options.format) {
type = options.format; //can override and require pdb
}
Expand Down
Loading

0 comments on commit 38198cc

Please sign in to comment.