molstar
Version:
A comprehensive macromolecular library.
419 lines (418 loc) • 25.6 kB
JavaScript
"use strict";
/**
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.LoadTrajectory = exports.AddTrajectory = exports.EnableStructureCustomProps = exports.EnableModelCustomProps = exports.UpdateTrajectory = exports.DownloadStructure = exports.PdbDownloadProvider = void 0;
const mol_state_1 = require("../../mol-state");
const mol_task_1 = require("../../mol-task");
const param_definition_1 = require("../../mol-util/param-definition");
const representation_preset_1 = require("../builder/structure/representation-preset");
const trajectory_1 = require("../formats/trajectory");
const root_structure_1 = require("../helpers/root-structure");
const objects_1 = require("../objects");
const transforms_1 = require("../transforms");
const model_1 = require("../transforms/model");
const assets_1 = require("../../mol-util/assets");
const config_1 = require("../../mol-plugin/config");
const file_info_1 = require("../../mol-util/file-info");
const type_helpers_1 = require("../../mol-util/type-helpers");
const topology_1 = require("../formats/topology");
const coordinates_1 = require("../formats/coordinates");
const DownloadModelRepresentationOptions = (plugin) => {
const representationDefault = plugin.config.get(config_1.PluginConfig.Structure.DefaultRepresentationPreset) || representation_preset_1.PresetStructureRepresentations.auto.id;
return param_definition_1.ParamDefinition.Group({
type: root_structure_1.RootStructureDefinition.getParams(void 0, 'auto').type,
representation: param_definition_1.ParamDefinition.Select(representationDefault, plugin.builders.structure.representation.getPresets().map(p => [p.id, p.display.name, p.display.group]), { description: 'Which representation preset to use.' }),
representationParams: param_definition_1.ParamDefinition.Group(representation_preset_1.StructureRepresentationPresetProvider.CommonParams, { isHidden: true }),
asTrajectory: param_definition_1.ParamDefinition.Optional(param_definition_1.ParamDefinition.Boolean(false, { description: 'Load all entries into a single trajectory.' }))
}, { isExpanded: false });
};
exports.PdbDownloadProvider = {
'rcsb': param_definition_1.ParamDefinition.Group({
encoding: param_definition_1.ParamDefinition.Select('bcif', param_definition_1.ParamDefinition.arrayToOptions(['cif', 'bcif'])),
}, { label: 'RCSB PDB', isFlat: true }),
'pdbe': param_definition_1.ParamDefinition.Group({
variant: param_definition_1.ParamDefinition.Select('updated-bcif', [['updated-bcif', 'Updated (bcif)'], ['updated', 'Updated'], ['archival', 'Archival']]),
}, { label: 'PDBe', isFlat: true }),
'pdbj': param_definition_1.ParamDefinition.EmptyGroup({ label: 'PDBj' }),
};
const DownloadStructure = mol_state_1.StateAction.build({
from: objects_1.PluginStateObject.Root,
display: { name: 'Download Structure', description: 'Load a structure from the provided source and create its representation.' },
params: (_, plugin) => {
const options = DownloadModelRepresentationOptions(plugin);
const defaultPdbProvider = plugin.config.get(config_1.PluginConfig.Download.DefaultPdbProvider) || 'pdbe';
return {
source: param_definition_1.ParamDefinition.MappedStatic('pdb', {
'pdb': param_definition_1.ParamDefinition.Group({
provider: param_definition_1.ParamDefinition.Group({
id: param_definition_1.ParamDefinition.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma/space separated PDB ids.' }),
server: param_definition_1.ParamDefinition.MappedStatic(defaultPdbProvider, exports.PdbDownloadProvider),
}, { pivot: 'id' }),
options
}, { isFlat: true, label: 'PDB' }),
'pdb-ihm': param_definition_1.ParamDefinition.Group({
provider: param_definition_1.ParamDefinition.Group({
id: param_definition_1.ParamDefinition.Text('8zzc', { label: 'PDB-IHM Id(s)', description: 'One or more comma/space separated ids.' }),
encoding: param_definition_1.ParamDefinition.Select('bcif', param_definition_1.ParamDefinition.arrayToOptions(['cif', 'bcif'])),
}, { pivot: 'id' }),
options
}, { isFlat: true, label: 'PDB-IHM' }),
'swissmodel': param_definition_1.ParamDefinition.Group({
id: param_definition_1.ParamDefinition.Text('Q9Y2I8', { label: 'UniProtKB AC(s)', description: 'One or more comma/space separated ACs.' }),
options
}, { isFlat: true, label: 'SWISS-MODEL', description: 'Loads the best homology model or experimental structure' }),
'alphafolddb': param_definition_1.ParamDefinition.Group({
provider: param_definition_1.ParamDefinition.Group({
id: param_definition_1.ParamDefinition.Text('Q8W3K0', { label: 'UniProtKB AC(s)', description: 'One or more comma/space separated ACs.' }),
encoding: param_definition_1.ParamDefinition.Select('bcif', param_definition_1.ParamDefinition.arrayToOptions(['cif', 'bcif'])),
}, { pivot: 'id' }),
options
}, { isFlat: true, label: 'AlphaFold DB', description: 'Loads the predicted model if available' }),
'modelarchive': param_definition_1.ParamDefinition.Group({
id: param_definition_1.ParamDefinition.Text('ma-bak-cepc-0003', { label: 'Accession Code(s)', description: 'One or more comma/space separated ACs.' }),
options
}, { isFlat: true, label: 'Model Archive' }),
'pubchem': param_definition_1.ParamDefinition.Group({
id: param_definition_1.ParamDefinition.Text('2244,2245', { label: 'PubChem ID', description: 'One or more comma/space separated IDs.' }),
options
}, { isFlat: true, label: 'PubChem', description: 'Loads 3D conformer from PubChem.' }),
'url': param_definition_1.ParamDefinition.Group({
url: param_definition_1.ParamDefinition.Url(''),
format: param_definition_1.ParamDefinition.Select('mmcif', param_definition_1.ParamDefinition.arrayToOptions(trajectory_1.BuiltInTrajectoryFormats.map(f => f[0]), f => f)),
isBinary: param_definition_1.ParamDefinition.Boolean(false),
label: param_definition_1.ParamDefinition.Optional(param_definition_1.ParamDefinition.Text('')),
options
}, { isFlat: true, label: 'URL' })
})
};
}
})(({ params, state }, plugin) => mol_task_1.Task.create('Download Structure', async (ctx) => {
plugin.behaviors.layout.leftPanelTabName.next('data');
const src = params.source;
let downloadParams;
let asTrajectory = false;
let format = 'mmcif';
switch (src.name) {
case 'url':
downloadParams = [{ url: src.params.url, isBinary: src.params.isBinary, label: src.params.label || undefined }];
format = src.params.format;
break;
case 'pdb':
downloadParams = await (src.params.provider.server.name === 'pdbe'
? getPdbeDownloadParams(src)
: src.params.provider.server.name === 'pdbj'
? getPdbjDownloadParams(src)
: src.params.provider.server.name === 'rcsb'
? getRcsbDownloadParams(src)
: (0, type_helpers_1.assertUnreachable)(src));
asTrajectory = !!src.params.options.asTrajectory;
break;
case 'pdb-ihm':
const map = (id) => id.startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`;
downloadParams = await getDownloadParams(src.params.provider.id, id => {
// 4 character PDB id, TODO: support extended PDB ID
if (id.match(/^[1-9][A-Z0-9]{3}$/i) !== null) {
return src.params.provider.encoding === 'bcif'
? `https://pdb-ihm.org/bcif/${id.toLowerCase()}.bcif`
: `https://pdb-ihm.org/cif/${id.toLowerCase()}.cif`;
}
const nId = map(id.toUpperCase());
return src.params.provider.encoding === 'bcif'
? `https://pdb-ihm.org/bcif/${nId}.bcif`
: `https://pdb-ihm.org/cif/${nId}.cif`;
}, id => { const nId = id.toUpperCase(); return nId.match(/^[1-9][A-Z0-9]{3}$/) ? `PDB-IHM: ${nId}` : map(nId); }, src.params.provider.encoding === 'bcif');
asTrajectory = !!src.params.options.asTrajectory;
break;
case 'swissmodel':
downloadParams = await getDownloadParams(src.params.id, id => `https://swissmodel.expasy.org/repository/uniprot/${id.toUpperCase()}.pdb`, id => `SWISS-MODEL: ${id}`, false);
asTrajectory = !!src.params.options.asTrajectory;
format = 'pdb';
break;
case 'alphafolddb':
downloadParams = await getDownloadParams(src.params.provider.id, async (id) => {
const url = `https://www.alphafold.ebi.ac.uk/api/prediction/${id.toUpperCase()}`;
const info = await plugin.runTask(plugin.fetch({ url, type: 'json' }));
if (Array.isArray(info) && info.length > 0) {
const prop = src.params.provider.encoding === 'bcif' ? 'bcifUrl' : 'cifUrl';
return info[0][prop];
}
throw new Error(`No AlphaFold DB entry for '${id}'`);
}, id => `AlphaFold DB: ${id}`, src.params.provider.encoding === 'bcif');
asTrajectory = !!src.params.options.asTrajectory;
format = 'mmcif';
break;
case 'modelarchive':
downloadParams = await getDownloadParams(src.params.id, id => `https://www.modelarchive.org/doi/10.5452/${id.toLowerCase()}.cif`, id => `Model Archive: ${id}`, false);
asTrajectory = !!src.params.options.asTrajectory;
format = 'mmcif';
break;
case 'pubchem':
downloadParams = await getDownloadParams(src.params.id, id => `https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/CID/${id.trim()}/record/SDF/?record_type=3d`, id => `PubChem: ${id}`, false);
asTrajectory = !!src.params.options.asTrajectory;
format = 'mol';
break;
default: (0, type_helpers_1.assertUnreachable)(src);
}
const representationPreset = params.source.params.options.representation || plugin.config.get(config_1.PluginConfig.Structure.DefaultRepresentationPreset) || representation_preset_1.PresetStructureRepresentations.auto.id;
const showUnitcell = representationPreset !== representation_preset_1.PresetStructureRepresentations.empty.id;
const structure = src.params.options.type.name === 'auto' ? void 0 : src.params.options.type;
await state.transaction(async () => {
if (downloadParams.length > 0 && asTrajectory) {
const blob = await plugin.builders.data.downloadBlob({
sources: downloadParams.map((src, i) => ({ id: '' + i, url: src.url, isBinary: src.isBinary })),
maxConcurrency: 6
}, { state: { isGhost: true } });
const trajectory = await plugin.builders.structure.parseTrajectory(blob, { formats: downloadParams.map((_, i) => ({ id: '' + i, format: 'cif' })) });
await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default', {
structure,
showUnitcell,
representationPreset,
representationPresetParams: params.source.params.options.representationParams
});
}
else {
for (const download of downloadParams) {
const data = await plugin.builders.data.download(download, { state: { isGhost: true } });
const provider = plugin.dataFormats.get(format);
if (!provider)
throw new Error('unknown file format');
const trajectory = await plugin.builders.structure.parseTrajectory(data, provider);
await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default', {
structure,
showUnitcell,
representationPreset,
representationPresetParams: params.source.params.options.representationParams
});
}
}
}).runInContext(ctx);
}));
exports.DownloadStructure = DownloadStructure;
async function getDownloadParams(src, url, label, isBinary) {
const ids = src.split(/[,\s]/).map(id => id.trim()).filter(id => !!id && (id.length >= 4 || /^[1-9][0-9]*$/.test(id)));
const ret = [];
for (const id of ids) {
ret.push({ url: assets_1.Asset.Url(await url(id)), isBinary, label: label(id) });
}
return ret;
}
async function getPdbeDownloadParams(src) {
if (src.name !== 'pdb' || src.params.provider.server.name !== 'pdbe')
throw new Error('expected pdbe');
return src.params.provider.server.params.variant === 'updated'
? getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}_updated.cif`, id => `PDBe: ${id} (updated cif)`, false)
: src.params.provider.server.params.variant === 'updated-bcif'
? getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/entry-files/download/${id.toLowerCase()}.bcif`, id => `PDBe: ${id} (updated cif)`, true)
: getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}.cif`, id => `PDBe: ${id} (cif)`, false);
}
async function getPdbjDownloadParams(src) {
if (src.name !== 'pdb' || src.params.provider.server.name !== 'pdbj')
throw new Error('expected pdbj');
return getDownloadParams(src.params.provider.id, id => `https://data.pdbjlc1.pdbj.org/pub/pdb/data/structures/divided/mmCIF/${id.toLowerCase().substring(1, 3)}/${id.toLowerCase()}.cif`, id => `PDBj: ${id} (cif)`, false);
}
async function getRcsbDownloadParams(src) {
if (src.name !== 'pdb' || src.params.provider.server.name !== 'rcsb')
throw new Error('expected rcsb');
return src.params.provider.server.params.encoding === 'cif'
? getDownloadParams(src.params.provider.id, id => `https://files.rcsb.org/download/${id.toUpperCase()}.cif`, id => `RCSB PDB: ${id} (cif)`, false)
: getDownloadParams(src.params.provider.id, id => `https://models.rcsb.org/${id.toUpperCase()}.bcif`, id => `RCSB PDB: ${id} (bcif)`, true);
}
exports.UpdateTrajectory = mol_state_1.StateAction.build({
display: { name: 'Update Trajectory' },
params: {
action: param_definition_1.ParamDefinition.Select('advance', param_definition_1.ParamDefinition.arrayToOptions(['advance', 'reset'])),
by: param_definition_1.ParamDefinition.Optional(param_definition_1.ParamDefinition.Numeric(1, { min: -1, max: 1, step: 1 }))
}
})(({ params, state }) => {
const models = state.selectQ(q => q.ofTransformer(transforms_1.StateTransforms.Model.ModelFromTrajectory));
const update = state.build();
if (params.action === 'reset') {
for (const m of models) {
update.to(m).update({ modelIndex: 0 });
}
}
else {
for (const m of models) {
const parent = mol_state_1.StateSelection.findAncestorOfType(state.tree, state.cells, m.transform.ref, objects_1.PluginStateObject.Molecule.Trajectory);
if (!parent || !parent.obj)
continue;
const traj = parent.obj;
update.to(m).update(old => {
let modelIndex = (old.modelIndex + params.by) % traj.data.frameCount;
if (modelIndex < 0)
modelIndex += traj.data.frameCount;
return { modelIndex };
});
}
}
return state.updateTree(update);
});
exports.EnableModelCustomProps = mol_state_1.StateAction.build({
display: { name: 'Custom Model Properties', description: 'Enable parameters for custom properties of the model.' },
from: objects_1.PluginStateObject.Molecule.Model,
params(a, ctx) {
return ctx.customModelProperties.getParams(a === null || a === void 0 ? void 0 : a.data);
},
isApplicable(a, t, ctx) {
return t.transformer !== model_1.CustomModelProperties;
}
})(({ ref, params }, ctx) => ctx.builders.structure.insertModelProperties(ref, params));
exports.EnableStructureCustomProps = mol_state_1.StateAction.build({
display: { name: 'Custom Structure Properties', description: 'Enable parameters for custom properties of the structure.' },
from: objects_1.PluginStateObject.Molecule.Structure,
params(a, ctx) {
return ctx.customStructureProperties.getParams(a === null || a === void 0 ? void 0 : a.data);
},
isApplicable(a, t, ctx) {
return t.transformer !== model_1.CustomStructureProperties;
}
})(({ ref, params }, ctx) => ctx.builders.structure.insertStructureProperties(ref, params));
exports.AddTrajectory = mol_state_1.StateAction.build({
display: { name: 'Add Trajectory', description: 'Add trajectory from existing model/topology and coordinates.' },
from: objects_1.PluginStateObject.Root,
params(a, ctx) {
const state = ctx.state.data;
const models = [
...state.selectQ(q => q.rootsOfType(objects_1.PluginStateObject.Molecule.Model)),
...state.selectQ(q => q.rootsOfType(objects_1.PluginStateObject.Molecule.Topology)),
];
const modelOptions = models.map(t => [t.transform.ref, t.obj.label]);
const coords = state.selectQ(q => q.rootsOfType(objects_1.PluginStateObject.Molecule.Coordinates));
const coordOptions = coords.map(c => [c.transform.ref, c.obj.label]);
return {
model: param_definition_1.ParamDefinition.Select(modelOptions.length ? modelOptions[0][0] : '', modelOptions),
coordinates: param_definition_1.ParamDefinition.Select(coordOptions.length ? coordOptions[0][0] : '', coordOptions)
};
}
})(({ params, state }, ctx) => mol_task_1.Task.create('Add Trajectory', taskCtx => {
return state.transaction(async () => {
const dependsOn = [params.model, params.coordinates];
const model = state.build().toRoot()
.apply(model_1.TrajectoryFromModelAndCoordinates, {
modelRef: params.model,
coordinatesRef: params.coordinates
}, { dependsOn })
.apply(transforms_1.StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 });
await state.updateTree(model).runInContext(taskCtx);
const structure = await ctx.builders.structure.createStructure(model.selector);
await ctx.builders.structure.representation.applyPreset(structure, 'auto');
}).runInContext(taskCtx);
}));
exports.LoadTrajectory = mol_state_1.StateAction.build({
display: { name: 'Load Trajectory', description: 'Load trajectory of model/topology and coordinates from URL or file.' },
from: objects_1.PluginStateObject.Root,
params(a, ctx) {
const { options } = ctx.dataFormats;
const modelOptions = options.filter(o => o[2] === trajectory_1.TrajectoryFormatCategory || o[2] === topology_1.TopologyFormatCategory);
const coordinatesOptions = options.filter(o => o[2] === coordinates_1.CoordinatesFormatCategory);
const modelExts = [];
const coordinatesExts = [];
for (const { provider } of ctx.dataFormats.list) {
if (provider.category === trajectory_1.TrajectoryFormatCategory || provider.category === topology_1.TopologyFormatCategory) {
if (provider.binaryExtensions)
modelExts.push(...provider.binaryExtensions);
if (provider.stringExtensions)
modelExts.push(...provider.stringExtensions);
}
else if (provider.category === coordinates_1.CoordinatesFormatCategory) {
if (provider.binaryExtensions)
coordinatesExts.push(...provider.binaryExtensions);
if (provider.stringExtensions)
coordinatesExts.push(...provider.stringExtensions);
}
}
return {
source: param_definition_1.ParamDefinition.MappedStatic('file', {
url: param_definition_1.ParamDefinition.Group({
model: param_definition_1.ParamDefinition.Group({
url: param_definition_1.ParamDefinition.Url(''),
format: param_definition_1.ParamDefinition.Select(modelOptions[0][0], modelOptions),
isBinary: param_definition_1.ParamDefinition.Boolean(false),
}, { isExpanded: true }),
coordinates: param_definition_1.ParamDefinition.Group({
url: param_definition_1.ParamDefinition.Url(''),
format: param_definition_1.ParamDefinition.Select(coordinatesOptions[0][0], coordinatesOptions),
}, { isExpanded: true })
}, { isFlat: true }),
file: param_definition_1.ParamDefinition.Group({
model: param_definition_1.ParamDefinition.File({ accept: modelExts.map(e => `.${e}`).join(','), label: 'Model' }),
coordinates: param_definition_1.ParamDefinition.File({ accept: coordinatesExts.map(e => `.${e}`).join(','), label: 'Coordinates' }),
}, { isFlat: true }),
}, { options: [['url', 'URL'], ['file', 'File']] })
};
}
})(({ params, state }, ctx) => mol_task_1.Task.create('Load Trajectory', taskCtx => {
return state.transaction(async () => {
const s = params.source;
if (s.name === 'file' && (s.params.model === null || s.params.coordinates === null)) {
ctx.log.error('No file(s) selected');
return;
}
if (s.name === 'url' && (!s.params.model || !s.params.coordinates)) {
ctx.log.error('No URL(s) given');
return;
}
const processUrl = async (url, format, isBinary) => {
const data = await ctx.builders.data.download({ url, isBinary });
const provider = ctx.dataFormats.get(format);
if (!provider) {
ctx.log.warn(`LoadTrajectory: could not find data provider for '${format}'`);
return;
}
return provider.parse(ctx, data);
};
const processFile = async (file) => {
var _a, _b, _c;
if (!file)
throw new Error('No file selected');
const info = (0, file_info_1.getFileNameInfo)((_b = (_a = file.file) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : '');
const isBinary = ctx.dataFormats.binaryExtensions.has(info.ext);
const { data } = await ctx.builders.data.readFile({ file, isBinary });
const provider = ctx.dataFormats.auto(info, (_c = data.cell) === null || _c === void 0 ? void 0 : _c.obj);
if (!provider) {
ctx.log.warn(`LoadTrajectory: could not find data provider for '${info.ext}'`);
await ctx.state.data.build().delete(data).commit();
return;
}
return provider.parse(ctx, data);
};
try {
const modelParsed = s.name === 'url'
? await processUrl(s.params.model.url, s.params.model.format, s.params.model.isBinary)
: await processFile(s.params.model);
let model;
if ('trajectory' in modelParsed) {
model = await state.build().to(modelParsed.trajectory)
.apply(model_1.ModelFromTrajectory, { modelIndex: 0 })
.commit();
}
else {
model = modelParsed.topology;
}
//
const coordinates = s.name === 'url'
? await processUrl(s.params.coordinates.url, s.params.coordinates.format, true)
: await processFile(s.params.coordinates);
//
const dependsOn = [model.ref, coordinates.ref];
const traj = state.build().toRoot()
.apply(model_1.TrajectoryFromModelAndCoordinates, {
modelRef: model.ref,
coordinatesRef: coordinates.ref
}, { dependsOn })
.apply(transforms_1.StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 });
await state.updateTree(traj).runInContext(taskCtx);
const structure = await ctx.builders.structure.createStructure(traj.selector);
await ctx.builders.structure.representation.applyPreset(structure, 'auto');
}
catch (e) {
console.error(e);
ctx.log.error(`Error loading trajectory`);
}
}).runInContext(taskCtx);
}));