UNPKG

molstar

Version:

A comprehensive macromolecular library.

176 lines (175 loc) 8.14 kB
/** * Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Adam Midlik <midlik@gmail.com> * @author David Sehnal <david.sehnal@gmail.com> */ import { omitObjectKeys, pickObjectKeys } from '../../../../mol-util/object.js'; import { addDefaults, condenseTree, convertTree, dfs, resolveUris } from '../generic/tree-utils.js'; import { MVSTreeSchema } from '../mvs/mvs-tree.js'; /** Convert `format` parameter of `parse` node in `MolstarTree` * into `format` and `is_binary` parameters in `MolstarTree` */ export const ParseFormatMvsToMolstar = { // trajectory mmcif: { format: 'cif', is_binary: false }, bcif: { format: 'cif', is_binary: true }, pdb: { format: 'pdb', is_binary: false }, pdbqt: { format: 'pdbqt', is_binary: false }, gro: { format: 'gro', is_binary: false }, xyz: { format: 'xyz', is_binary: false }, mol: { format: 'mol', is_binary: false }, sdf: { format: 'sdf', is_binary: false }, mol2: { format: 'mol2', is_binary: false }, lammpstrj: { format: 'lammpstrj', is_binary: false }, // coordinates xtc: { format: 'xtc', is_binary: true }, nctraj: { format: 'nctraj', is_binary: true }, dcd: { format: 'dcd', is_binary: true }, trr: { format: 'trr', is_binary: true }, // topology psf: { format: 'psf', is_binary: false }, prmtop: { format: 'prmtop', is_binary: false }, top: { format: 'top', is_binary: false }, // maps map: { format: 'map', is_binary: true }, dx: { format: 'dx', is_binary: false }, dxbin: { format: 'dxbin', is_binary: true }, }; const TopologyFormats = new Set(['psf', 'prmtop', 'top']); /** Conversion rules for conversion from `MVSTree` (with all parameter values) to `MolstarTree` */ const mvsToMolstarConversionRules = { 'download': node => ({ subtree: [] }), 'parse': (node, parent) => { const { format, is_binary } = ParseFormatMvsToMolstar[node.params.format]; if ((parent === null || parent === void 0 ? void 0 : parent.kind) === 'download') { return { subtree: [ { kind: 'download', params: { ...parent.params, is_binary }, custom: parent.custom, ref: parent.ref }, { kind: 'parse', params: { ...node.params, format }, custom: node.custom, ref: node.ref } ] }; } else { console.warn('"parse" node is not being converted, this is suspicious'); return { subtree: [ { kind: 'parse', params: { ...node.params, format }, custom: node.custom, ref: node.ref } ] }; } }, 'coordinates': (node, parent) => { if ((parent === null || parent === void 0 ? void 0 : parent.kind) !== 'parse') throw new Error(`Parent of "coordinates" must be "parse", not "${parent === null || parent === void 0 ? void 0 : parent.kind}".`); const { format } = ParseFormatMvsToMolstar[parent.params.format]; return { subtree: [ { kind: 'coordinates', params: { format }, custom: node.custom, ref: node.ref } ] }; }, 'structure': (node, parent) => { if ((parent === null || parent === void 0 ? void 0 : parent.kind) !== 'parse') throw new Error(`Parent of "structure" must be "parse", not "${parent === null || parent === void 0 ? void 0 : parent.kind}".`); const { format } = ParseFormatMvsToMolstar[parent.params.format]; if (TopologyFormats.has(parent.params.format)) { if (!node.params.coordinates_ref) { throw new Error(`"structure" node with topology format "${parent.params.format}" must have "coordinates_ref" parameter.`); } return { subtree: [ { kind: 'topology_with_coordinates', params: { format, coordinates_ref: node.params.coordinates_ref } }, { kind: 'model', params: pickObjectKeys(node.params, ['model_index']) }, { kind: 'structure', params: omitObjectKeys(node.params, ['block_header', 'block_index', 'model_index', 'coordinates_ref']), custom: node.custom, ref: node.ref }, ] }; } else if (node.params.coordinates_ref) { return { subtree: [ { kind: 'trajectory', params: { format, ...pickObjectKeys(node.params, ['block_header', 'block_index']) } }, { kind: 'model', params: { model_index: 0 } }, { kind: 'trajectory_with_coordinates', params: { coordinates_ref: node.params.coordinates_ref } }, { kind: 'model', params: pickObjectKeys(node.params, ['model_index']) }, { kind: 'structure', params: omitObjectKeys(node.params, ['block_header', 'block_index', 'model_index', 'coordinates_ref']), custom: node.custom, ref: node.ref }, ] }; } else { return { subtree: [ { kind: 'trajectory', params: { format, ...pickObjectKeys(node.params, ['block_header', 'block_index']) } }, { kind: 'model', params: pickObjectKeys(node.params, ['model_index']) }, { kind: 'structure', params: omitObjectKeys(node.params, ['block_header', 'block_index', 'model_index', 'coordinates_ref']), custom: node.custom, ref: node.ref }, ] }; } }, }; /** Node kinds in `MolstarTree` that it makes sense to condense */ const molstarNodesToCondense = new Set(['download', 'parse', 'trajectory', 'model']); /** Convert MolViewSpec tree into MolStar tree */ export function convertMvsToMolstar(mvsTree, sourceUrl) { const full = addDefaults(mvsTree, MVSTreeSchema); if (sourceUrl) resolveUris(full, sourceUrl, ['uri', 'url']); const converted = convertTree(full, mvsToMolstarConversionRules); if (converted.kind !== 'root') throw new Error("Root's type is not 'root' after conversion from MVS tree to Molstar tree."); const condensed = condenseTree(converted, molstarNodesToCondense); return condensed; } function fileExtensionMatches(filename, extensions) { filename = filename.toLowerCase(); return extensions.some(ext => ext === '*' || filename.endsWith(ext)); } const StructureFormatExtensions = { // trajectory mmcif: ['.cif', '.mmif'], bcif: ['.bcif'], pdb: ['.pdb', '.ent'], pdbqt: ['.pdbqt'], gro: ['.gro'], xyz: ['.xyz'], mol: ['.mol'], sdf: ['.sdf'], mol2: ['.mol2'], lammpstrj: ['.lammpstrj'], // coordinates xtc: ['.xtc'], nctraj: ['.nc', '.nctraj'], dcd: ['.dcd'], trr: ['.trr'], // topology psf: ['.psf'], prmtop: ['.prmtop', '.parm7'], top: ['.top'], // volumes map: ['.map', '.ccp4', '.mrc', '.mrcs'], dx: ['.dx'], dxbin: ['.dxbin'], }; /** Run some sanity check on a MVSTree. Return a list of potential problems (`undefined` if there are none) */ export function mvsSanityCheckIssues(tree) { const result = []; dfs(tree, (node, parent) => { if (node.kind === 'parse' && (parent === null || parent === void 0 ? void 0 : parent.kind) === 'download') { const source = parent.params.url; const extensions = StructureFormatExtensions[node.params.format]; if (!fileExtensionMatches(source, extensions)) { result.push(`Parsing data from ${source} as ${node.params.format} format might be a mistake. The file extension doesn't match recommended file extensions (${extensions.join(', ')})`); } } }); return result.length > 0 ? result : undefined; } /** Run some sanity check on a MVSTree and print potential issues to the console. */ export function mvsSanityCheck(tree) { const issues = mvsSanityCheckIssues(tree); if (issues) { console.warn('There are potential issues in the MVS tree:'); for (const issue of issues) { console.warn(' ', issue); } } }