diff --git a/CHANGELOG.md b/CHANGELOG.md index e2cf63915e19266bc929dede3585e37368c4af4e..ea2857d409c5e19c8592a994363b1c33db0e0cf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ Note that since we don't clearly distinguish between a public and private interf - Improve aromatic bond visuals (add ``aromaticScale``, ``aromaticSpacing``, ``aromaticDashCount`` params) - [Breaking] Change ``adjustCylinderLength`` default to ``false`` (set to true for focus representation) - Fix marker highlight color overriding select color +- CellPack extension update + - add binary model support + - add compartment (including membrane) geometry support + - add latest mycoplasma model example ## [v2.3.5] - 2021-10-19 diff --git a/README.md b/README.md index c9f448128806e06830dee74590d6f360cb1729d0..0ce2d30e7e4e6666c706618f3091ffc146adcea6 100644 --- a/README.md +++ b/README.md @@ -122,9 +122,9 @@ and navigate to `build/viewer` **Convert any CIF to BinaryCIF** - node lib/servers/model/preprocess -i file.cif -ob file.bcif + node lib/commonjs/servers/model/preprocess -i file.cif -ob file.bcif -To see all available commands, use ``node lib/servers/model/preprocess -h``. +To see all available commands, use ``node lib/commonjs/servers/model/preprocess -h``. Or diff --git a/src/extensions/cellpack/color/generate.ts b/src/extensions/cellpack/color/generate.ts index 247324b76fc4d35d411cdf1730cfc3f5e0e12f57..1d115228d9773eefb6f13cbcb24bb4b597e3a5f6 100644 --- a/src/extensions/cellpack/color/generate.ts +++ b/src/extensions/cellpack/color/generate.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ diff --git a/src/extensions/cellpack/color/provided.ts b/src/extensions/cellpack/color/provided.ts index a24ecabedb9f9c105dbd8e1b6a295a0fbb96fd63..6e035e830787e8bd11c4b8a959c58a5a4ac62073 100644 --- a/src/extensions/cellpack/color/provided.ts +++ b/src/extensions/cellpack/color/provided.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ diff --git a/src/extensions/cellpack/curve.ts b/src/extensions/cellpack/curve.ts index 83358584a5ce5c2c098d928a398dc328fbbcc409..5229ef4320d1afc8d09ad18448d8fdf61722b7cf 100644 --- a/src/extensions/cellpack/curve.ts +++ b/src/extensions/cellpack/curve.ts @@ -1,7 +1,7 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * - * @author Ludovic Autin <autin@scripps.edu> + * @author Ludovic Autin <ludovic.autin@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> */ diff --git a/src/extensions/cellpack/data.ts b/src/extensions/cellpack/data.ts index f7d8e469c9ed2c76d9003774aba7ee57addaff9b..6764ddc7faaebcfeda0f9dc9c128fae3c8be629b 100644 --- a/src/extensions/cellpack/data.ts +++ b/src/extensions/cellpack/data.ts @@ -13,16 +13,27 @@ export interface CellPack { export interface CellPacking { name: string, - location: 'surface' | 'interior' | 'cytoplasme', + location: 'surface' | 'interior' | 'cytoplasme' ingredients: Packing['ingredients'] + compartment?: CellCompartment } -// +export interface CellCompartment { + filename?: string + geom_type?: 'raw' | 'file' | 'sphere' | 'mb' | 'None' + compartment_primitives?: CompartmentPrimitives +} export interface Cell { recipe: Recipe + options?: RecipeOptions cytoplasme?: Packing compartments?: { [key: string]: Compartment } + mapping_ids?: { [key: number]: [number, string] } +} + +export interface RecipeOptions { + resultfile?: string } export interface Recipe { @@ -35,8 +46,29 @@ export interface Recipe { export interface Compartment { surface?: Packing interior?: Packing + geom?: unknown + geom_type?: 'raw' | 'file' | 'sphere' | 'mb' | 'None' + mb?: CompartmentPrimitives +} + +// Primitives discribing a compartment +export const enum CompartmentPrimitiveType { + MetaBall = 0, + Sphere = 1, + Cube = 2, + Cylinder = 3, + Cone = 4, + Plane = 5, + None = 6 } +export interface CompartmentPrimitives{ + positions?: number[]; + radii?: number[]; + types?: CompartmentPrimitiveType[]; +} + + export interface Packing { ingredients: { [key: string]: Ingredient } } @@ -64,11 +96,13 @@ export interface Ingredient { [curveX: string]: unknown; /** the orientation in the membrane */ principalAxis?: Vec3; + principalVector?: Vec3; /** offset along membrane */ offset?: Vec3; ingtype?: string; color?: Vec3; confidence?: number; + Type?: string; } export interface IngredientSource { diff --git a/src/extensions/cellpack/index.ts b/src/extensions/cellpack/index.ts index e3810eff8a041766af3cd6f0cca2a4c763c07699..962d85c69b668602e1755041c345eb1799a1751e 100644 --- a/src/extensions/cellpack/index.ts +++ b/src/extensions/cellpack/index.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ diff --git a/src/extensions/cellpack/model.ts b/src/extensions/cellpack/model.ts index f52b431332b18dbde1c30a53d6ce6bf7e728995c..5715ce080984519a44ea5cf26662fca30c4b4da4 100644 --- a/src/extensions/cellpack/model.ts +++ b/src/extensions/cellpack/model.ts @@ -2,13 +2,14 @@ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Ludovic Autin <ludovic.autin@gmail.com> */ import { StateAction, StateBuilder, StateTransformer, State } from '../../mol-state'; import { PluginContext } from '../../mol-plugin/context'; import { PluginStateObject as PSO } from '../../mol-plugin-state/objects'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; -import { Ingredient, IngredientSource, CellPacking } from './data'; +import { Ingredient, CellPacking, CompartmentPrimitives } from './data'; import { getFromPdb, getFromCellPackDB, IngredientFiles, parseCif, parsePDBfile, getStructureMean, getFromOPM } from './util'; import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit, Trajectory } from '../../mol-model/structure'; import { trajectoryFromMmCIF, MmcifFormat } from '../../mol-model-formats/structure/mmcif'; @@ -17,7 +18,7 @@ import { Mat4, Vec3, Quat } from '../../mol-math/linear-algebra'; import { SymmetryOperator } from '../../mol-math/geometry'; import { Task, RuntimeContext } from '../../mol-task'; import { StateTransforms } from '../../mol-plugin-state/transforms'; -import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl, StructureFromAssemblies } from './state'; +import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl, StructureFromAssemblies, CreateCompartmentSphere } from './state'; import { MolScriptBuilder as MS } from '../../mol-script/language/builder'; import { getMatFromResamplePoints } from './curve'; import { compile } from '../../mol-script/runtime/query/compiler'; @@ -28,8 +29,9 @@ import { createModels } from '../../mol-model-formats/structure/basic/parser'; import { CellpackPackingPreset, CellpackMembranePreset } from './preset'; import { Asset } from '../../mol-util/assets'; import { Color } from '../../mol-util/color'; -import { readFromFile } from '../../mol-util/data-source'; import { objectForEach } from '../../mol-util/object'; +import { readFromFile } from '../../mol-util/data-source'; +import { ColorNames } from '../../mol-util/color/names'; function getCellPackModelUrl(fileName: string, baseUrl: string) { return `${baseUrl}/results/${fileName}`; @@ -41,10 +43,14 @@ class TrajectoryCache { get(id: string) { return this.map.get(id); } } -async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient, baseUrl: string, trajCache: TrajectoryCache, file?: Asset.File) { +async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient, + baseUrl: string, trajCache: TrajectoryCache, location: string, + file?: Asset.File +) { const assetManager = plugin.managers.asset; const modelIndex = (ingredient.source.model) ? parseInt(ingredient.source.model) : 0; - const surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false; + let surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false; + if (location === 'surface') surface = true; let trajectory = trajCache.get(id); const assets: Asset.Wrapper[] = []; if (!trajectory) { @@ -72,6 +78,7 @@ async function getModel(plugin: PluginContext, id: string, ingredient: Ingredien try { const data = await getFromOPM(plugin, id, assetManager); assets.push(data.asset); + data.pdb.id! = id.toUpperCase(); trajectory = await plugin.runTask(trajectoryFromPDB(data.pdb)); } catch (e) { // fallback to getFromPdb @@ -100,7 +107,7 @@ async function getModel(plugin: PluginContext, id: string, ingredient: Ingredien return { model, assets }; } -async function getStructure(plugin: PluginContext, model: Model, source: IngredientSource, props: { assembly?: string } = {}) { +async function getStructure(plugin: PluginContext, model: Model, source: Ingredient, props: { assembly?: string } = {}) { let structure = Structure.ofModel(model); const { assembly } = props; @@ -108,11 +115,12 @@ async function getStructure(plugin: PluginContext, model: Model, source: Ingredi structure = await plugin.runTask(StructureSymmetry.buildAssembly(structure, assembly)); } let query; - if (source.selection) { - const asymIds: string[] = source.selection.replace(' ', '').replace(':', '').split('or'); + if (source.source.selection) { + const sel = source.source.selection; + // selection can have the model ID as well. remove it + const asymIds: string[] = sel.replace(/ /g, '').replace(/:/g, '').split('or').slice(1); query = MS.struct.modifier.union([ MS.struct.generator.atomGroups({ - 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']), 'chain-test': MS.core.set.has([MS.set(...asymIds), MS.ammp('auth_asym_id')]) }) ]); @@ -123,11 +131,11 @@ async function getStructure(plugin: PluginContext, model: Model, source: Ingredi }) ]); } - const compiled = compile<StructureSelection>(query); const result = compiled(new QueryContext(structure)); structure = StructureSelection.unionStructure(result); - + // change here if possible the label ? + // structure.label = source.name; return structure; } @@ -141,9 +149,9 @@ function getTransformLegacy(trans: Vec3, rot: Quat) { } function getTransform(trans: Vec3, rot: Quat) { - const q: Quat = Quat.create(rot[0], rot[1], rot[2], rot[3]); + const q: Quat = Quat.create(-rot[0], rot[1], rot[2], -rot[3]); const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q); - const p: Vec3 = Vec3.create(trans[0], trans[1], trans[2]); + const p: Vec3 = Vec3.create(-trans[0], trans[1], trans[2]); Mat4.setTranslation(m, p); return m; } @@ -168,7 +176,7 @@ function getCurveTransforms(ingredient: Ingredient) { for (let i = 0; i < n; ++i) { const cname = `curve${i}`; if (!(cname in ingredient)) { - // console.warn(`Expected '${cname}' in ingredient`) + console.warn(`Expected '${cname}' in ingredient`); continue; } const _points = ingredient[cname] as Vec3[]; @@ -179,7 +187,7 @@ function getCurveTransforms(ingredient: Ingredient) { // test for resampling const distance: number = Vec3.distance(_points[0], _points[1]); if (distance >= segmentLength + 2.0) { - console.info(distance); + // console.info(distance); resampling = true; } const points = new Float32Array(_points.length * 3); @@ -190,8 +198,8 @@ function getCurveTransforms(ingredient: Ingredient) { return instances; } -function getAssembly(transforms: Mat4[], structure: Structure) { - const builder = Structure.Builder(); +function getAssembly(name: string, transforms: Mat4[], structure: Structure) { + const builder = Structure.Builder({ label: name }); const { units } = structure; for (let i = 0, il = transforms.length; i < il; ++i) { @@ -307,13 +315,13 @@ async function getCurve(plugin: PluginContext, name: string, ingredient: Ingredi }); const curveModel = await plugin.runTask(curveModelTask); - return getStructure(plugin, curveModel, ingredient.source); + // ingredient.source.selection = undefined; + return getStructure(plugin, curveModel, ingredient); } -async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache) { +async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache, location: 'surface' | 'interior' | 'cytoplasme') { const { name, source, results, nbCurve } = ingredient; if (source.pdb === 'None') return; - const file = ingredientFiles[source.pdb]; if (!file) { // TODO can these be added to the library? @@ -325,13 +333,13 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi } // model id in case structure is NMR - const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, trajCache, file); + const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, trajCache, location, file); if (!model) return; - let structure: Structure; if (nbCurve) { structure = await getCurve(plugin, name, ingredient, getCurveTransforms(ingredient), model); } else { + if ((!results || results.length === 0)) return; let bu: string|undefined = source.bu ? source.bu : undefined; if (bu) { if (bu === 'AU') { @@ -340,10 +348,11 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi bu = bu.slice(2); } } - structure = await getStructure(plugin, model, source, { assembly: bu }); + structure = await getStructure(plugin, model, ingredient, { assembly: bu }); // transform with offset and pcp let legacy: boolean = true; - if (ingredient.offset || ingredient.principalAxis) { + const pcp = ingredient.principalVector ? ingredient.principalVector : ingredient.principalAxis; + if (pcp) { legacy = false; const structureMean = getStructureMean(structure); Vec3.negate(structureMean, structureMean); @@ -351,38 +360,44 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi Mat4.setTranslation(m1, structureMean); structure = Structure.transform(structure, m1); if (ingredient.offset) { - if (!Vec3.exactEquals(ingredient.offset, Vec3.zero())) { + const o: Vec3 = Vec3.create(ingredient.offset[0], ingredient.offset[1], ingredient.offset[2]); + if (!Vec3.exactEquals(o, Vec3.zero())) { // -1, 1, 4e-16 ?? + if (location !== 'surface') { + Vec3.negate(o, o); + } const m: Mat4 = Mat4.identity(); - Mat4.setTranslation(m, ingredient.offset); + Mat4.setTranslation(m, o); structure = Structure.transform(structure, m); } } - if (ingredient.principalAxis) { - if (!Vec3.exactEquals(ingredient.principalAxis, Vec3.unitZ)) { + if (pcp) { + const p: Vec3 = Vec3.create(pcp[0], pcp[1], pcp[2]); + if (!Vec3.exactEquals(p, Vec3.unitZ)) { const q: Quat = Quat.identity(); - Quat.rotationTo(q, ingredient.principalAxis, Vec3.unitZ); + Quat.rotationTo(q, p, Vec3.unitZ); const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q); structure = Structure.transform(structure, m); } } } - structure = getAssembly(getResultTransforms(results, legacy), structure); + + structure = getAssembly(name, getResultTransforms(results, legacy), structure); } return { structure, assets }; } + export function createStructureFromCellPack(plugin: PluginContext, packing: CellPacking, baseUrl: string, ingredientFiles: IngredientFiles) { return Task.create('Create Packing Structure', async ctx => { - const { ingredients, name } = packing; + const { ingredients, location, name } = packing; const assets: Asset.Wrapper[] = []; const trajCache = new TrajectoryCache(); const structures: Structure[] = []; const colors: Color[] = []; - let skipColors: boolean = false; for (const iName in ingredients) { if (ctx.shouldUpdate) await ctx.update(iName); - const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache); + const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache, location); if (ingredientStructure) { structures.push(ingredientStructure.structure); assets.push(...ingredientStructure.assets); @@ -390,7 +405,7 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell if (c) { colors.push(Color.fromNormalizedRgb(c[0], c[1], c[2])); } else { - skipColors = true; + colors.push(Color.fromNormalizedRgb(1, 0, 0)); } } } @@ -414,21 +429,20 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell } if (ctx.shouldUpdate) await ctx.update(`${name} - structure`); - const structure = Structure.create(units); + const structure = Structure.create(units, { label: name + '.' + location }); for (let i = 0, il = structure.models.length; i < il; ++i) { Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i }); } - return { structure, assets, colors: skipColors ? undefined : colors }; + return { structure, assets, colors: colors }; }); } async function handleHivRna(plugin: PluginContext, packings: CellPacking[], baseUrl: string) { for (let i = 0, il = packings.length; i < il; ++i) { - if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') { + if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0' || packings[i].name === 'HIV_capsid') { const url = Asset.getUrlAsset(plugin.managers.asset, `${baseUrl}/extras/rna_allpoints.json`); const json = await plugin.runTask(plugin.managers.asset.resolve(url, 'json', false)); const points = json.data.points as number[]; - const curve0: Vec3[] = []; for (let j = 0, jl = points.length; j < jl; j += 3) { curve0.push(Vec3.fromArray(Vec3(), points, j)); @@ -465,7 +479,8 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p } } } - + let legacy_membrane: boolean = false; // temporary variable until all membrane are converted to the new correct cif format + let geometry_membrane: boolean = false; // membrane can be a mesh geometry let b = state.build().toRoot(); if (file) { if (file.name.endsWith('.cif')) { @@ -474,27 +489,82 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p b = b.apply(StateTransforms.Data.ReadFile, { file, isBinary: true, label: file.name }, { state: { isGhost: true } }); } } else { - const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}.bcif`); - b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } }); + if (name.toLowerCase().endsWith('.bcif')) { + const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}`); + b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } }); + } else if (name.toLowerCase().endsWith('.cif')) { + const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}`); + b = b.apply(StateTransforms.Data.Download, { url, isBinary: false, label: name }, { state: { isGhost: true } }); + } else if (name.toLowerCase().endsWith('.ply')) { + const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/geometries/${name}`); + b = b.apply(StateTransforms.Data.Download, { url, isBinary: false, label: name }, { state: { isGhost: true } }); + geometry_membrane = true; + } else { + const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}.bcif`); + b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } }); + legacy_membrane = true; + } } - - const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } }) - .apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } }) - .apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } }) - .apply(StructureFromAssemblies, undefined, { state: { isGhost: true } }) - .commit({ revertOnError: true }); - - const membraneParams = { - representation: params.preset.representation, + const props = { + type: { + name: 'assembly' as const, + params: { id: '1' } + } }; + if (legacy_membrane) { + // old membrane + const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } }) + .apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } }) + .apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } }) + .apply(StructureFromAssemblies, undefined, { state: { isGhost: true } }) + .commit({ revertOnError: true }); + const membraneParams = { + representation: params.preset.representation, + }; + await CellpackMembranePreset.apply(membrane, membraneParams, plugin); + } else if (geometry_membrane) { + await b.apply(StateTransforms.Data.ParsePly, undefined, { state: { isGhost: true } }) + .apply(StateTransforms.Model.ShapeFromPly) + .apply(StateTransforms.Representation.ShapeRepresentation3D, { xrayShaded: true, + doubleSided: true, coloring: { name: 'uniform', params: { color: ColorNames.orange } } }) + .commit({ revertOnError: true }); + } else { + const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } }) + .apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } }) + .apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } }) + .apply(StateTransforms.Model.StructureFromModel, props, { state: { isGhost: true } }) + .commit({ revertOnError: true }); + const membraneParams = { + representation: params.preset.representation, + }; + await CellpackMembranePreset.apply(membrane, membraneParams, plugin); + } +} - await CellpackMembranePreset.apply(membrane, membraneParams, plugin); +async function handleMembraneSpheres(state: State, primitives: CompartmentPrimitives) { + const nSpheres = primitives.positions!.length / 3; + // console.log('ok mb ', nSpheres); + // TODO : take in account the type of the primitives. + for (let j = 0; j < nSpheres; j++) { + await state.build() + .toRoot() + .apply(CreateCompartmentSphere, { + center: Vec3.create( + primitives.positions![j * 3 + 0], + primitives.positions![j * 3 + 1], + primitives.positions![j * 3 + 2] + ), + radius: primitives!.radii![j] + }) + .commit(); + } } async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) { const ingredientFiles = params.ingredients || []; let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>; + let resultsFile: Asset.File | null = params.results; if (params.source.name === 'id') { const url = Asset.getUrlAsset(plugin.managers.asset, getCellPackModelUrl(params.source.params, params.baseUrl)); cellPackJson = state.build().toRoot() @@ -506,29 +576,36 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat return; } - let jsonFile: Asset.File; + let modelFile: Asset.File; if (file.name.toLowerCase().endsWith('.zip')) { const data = await readFromFile(file.file, 'zip').runInContext(runtime); - jsonFile = Asset.File(new File([data['model.json']], 'model.json')); + if (data['model.json']) { + modelFile = Asset.File(new File([data['model.json']], 'model.json')); + } else { + throw new Error('model.json missing from zip file'); + } + if (data['results.bin']) { + resultsFile = Asset.File(new File([data['results.bin']], 'results.bin')); + } objectForEach(data, (v, k) => { if (k === 'model.json') return; + if (k === 'results.bin') return; ingredientFiles.push(Asset.File(new File([v], k))); }); } else { - jsonFile = file; + modelFile = file; } - cellPackJson = state.build().toRoot() - .apply(StateTransforms.Data.ReadFile, { file: jsonFile, isBinary: false, label: jsonFile.name }, { state: { isGhost: true } }); + .apply(StateTransforms.Data.ReadFile, { file: modelFile, isBinary: false, label: modelFile.name }, { state: { isGhost: true } }); } const cellPackBuilder = cellPackJson .apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } }) - .apply(ParseCellPack); + .apply(ParseCellPack, { resultsFile, baseUrl: params.baseUrl }); const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(runtime); - const { packings } = cellPackObject.obj!.data; + const { packings } = cellPackObject.obj!.data; await handleHivRna(plugin, packings, params.baseUrl); for (let i = 0, il = packings.length; i < il; ++i) { @@ -544,8 +621,30 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat representation: params.preset.representation, }; await CellpackPackingPreset.apply(packing, packingParams, plugin); - if (packings[i].location === 'surface' && params.membrane) { - await loadMembrane(plugin, packings[i].name, state, params); + if (packings[i].compartment) { + if (params.membrane === 'lipids') { + if (packings[i].compartment!.geom_type) { + if (packings[i].compartment!.geom_type === 'file') { + // TODO: load mesh files or vertex,faces data + await loadMembrane(plugin, packings[i].compartment!.filename!, state, params); + } else if (packings[i].compartment!.compartment_primitives) { + await handleMembraneSpheres(state, packings[i].compartment!.compartment_primitives!); + } + } else { + // try loading membrane from repo as a bcif file or from the given list of files. + if (params.membrane === 'lipids') { + await loadMembrane(plugin, packings[i].name, state, params); + } + } + } else if (params.membrane === 'geometry') { + if (packings[i].compartment!.compartment_primitives) { + await handleMembraneSpheres(state, packings[i].compartment!.compartment_primitives!); + } else if (packings[i].compartment!.geom_type === 'file') { + if (packings[i].compartment!.filename!.toLowerCase().endsWith('.ply')) { + await loadMembrane(plugin, packings[i].compartment!.filename!, state, params); + } + } + } } } } @@ -555,19 +654,19 @@ const LoadCellPackModelParams = { 'id': PD.Select('InfluenzaModel2.json', [ ['blood_hiv_immature_inside.json', 'Blood HIV immature'], ['HIV_immature_model.json', 'HIV immature'], - ['BloodHIV1.0_mixed_fixed_nc1.cpr', 'Blood HIV'], - ['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV'], + ['Blood_HIV.json', 'Blood HIV'], + ['HIV-1_0.1.6-8_mixed_radii_pdb.json', 'HIV'], ['influenza_model1.json', 'Influenza envelope'], - ['InfluenzaModel2.json', 'Influenza Complete'], + ['InfluenzaModel2.json', 'Influenza complete'], ['ExosomeModel.json', 'Exosome Model'], - ['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma simple'], - ['MycoplasmaModel.json', 'Mycoplasma WholeCell model'], + ['MycoplasmaGenitalium.json', 'Mycoplasma Genitalium curated model'], ] as const, { description: 'Download the model definition with `id` from the server at `baseUrl.`' }), - 'file': PD.File({ accept: '.json,.cpr,.zip', description: 'Open model definition from .json/.cpr file or open .zip file containing model definition plus ingredients.' }), + 'file': PD.File({ accept: '.json,.cpr,.zip', description: 'Open model definition from .json/.cpr file or open .zip file containing model definition plus ingredients.', label: 'Recipe file' }), }, { options: [['id', 'Id'], ['file', 'File']] }), baseUrl: PD.Text(DefaultCellPackBaseUrl), - membrane: PD.Boolean(true), - ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredients' }), + results: PD.File({ accept: '.bin', description: 'open results file in binary format from cellpackgpu for the specified recipe', label: 'Results file' }), + membrane: PD.Select('lipids', PD.arrayToOptions(['lipids', 'geometry', 'none'])), + ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredient files' }), preset: PD.Group({ traceOnly: PD.Boolean(false), representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'orientation'])) @@ -581,4 +680,4 @@ export const LoadCellPackModel = StateAction.build({ from: PSO.Root })(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => { await loadPackings(ctx, taskCtx, state, params); -})); \ No newline at end of file +})); diff --git a/src/extensions/cellpack/preset.ts b/src/extensions/cellpack/preset.ts index 0cd3ae55bcf8daf75fdf58bfe2f6854fe5bbd402..895f2fb1ab7d085a297bcdfe5cc350dcd06e1e43 100644 --- a/src/extensions/cellpack/preset.ts +++ b/src/extensions/cellpack/preset.ts @@ -1,7 +1,8 @@ /** - * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Ludovic Autin <ludovic.autin@gmail.com> */ import { StateObjectRef } from '../../mol-state'; @@ -9,8 +10,6 @@ import { StructureRepresentationPresetProvider, presetStaticComponent } from '.. import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { ColorNames } from '../../mol-util/color/names'; import { CellPackGenerateColorThemeProvider } from './color/generate'; -import { CellPackInfoProvider } from './property'; -import { CellPackProvidedColorThemeProvider } from './color/provided'; export const CellpackPackingPresetParams = { traceOnly: PD.Boolean(true), @@ -42,8 +41,8 @@ export const CellpackPackingPreset = StructureRepresentationPresetProvider({ Object.assign(reprProps, { sizeFactor: 2 }); } - const info = structureCell.obj?.data && CellPackInfoProvider.get(structureCell.obj?.data).value; - const color = info?.colors ? CellPackProvidedColorThemeProvider.name : CellPackGenerateColorThemeProvider.name; + // default is generated + const color = CellPackGenerateColorThemeProvider.name; const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {}); const representations = { @@ -92,4 +91,4 @@ export const CellpackMembranePreset = StructureRepresentationPresetProvider({ return { components, representations }; } -}); \ No newline at end of file +}); diff --git a/src/extensions/cellpack/property.ts b/src/extensions/cellpack/property.ts index c2b8550e588c715fba551d604afcd0c164ac212d..9350e6501e54182138b5b7c3f45bc7488fbe6cc3 100644 --- a/src/extensions/cellpack/property.ts +++ b/src/extensions/cellpack/property.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -34,4 +34,4 @@ export const CellPackInfoProvider: CustomStructureProperty.Provider<typeof CellP value: { ...CellPackInfoParams.info.defaultValue, ...props.info } }; } -}); \ No newline at end of file +}); diff --git a/src/extensions/cellpack/representation.ts b/src/extensions/cellpack/representation.ts new file mode 100644 index 0000000000000000000000000000000000000000..261e3a3c4f50d52d81fe0cfb8fdc6b318ed832f2 --- /dev/null +++ b/src/extensions/cellpack/representation.ts @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Ludovic Autin <ludovic.autin@gmail.com> + */ + +import { ShapeRepresentation } from '../../mol-repr/shape/representation'; +import { Shape } from '../../mol-model/shape'; +import { ColorNames } from '../../mol-util/color/names'; +import { RuntimeContext } from '../../mol-task'; +import { ParamDefinition as PD } from '../../mol-util/param-definition'; +import { Mesh } from '../../mol-geo/geometry/mesh/mesh'; +import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder'; +// import { Polyhedron, DefaultPolyhedronProps } from '../../mol-geo/primitive/polyhedron'; +// import { Icosahedron } from '../../mol-geo/primitive/icosahedron'; +import { Sphere } from '../../mol-geo/primitive/sphere'; +import { Mat4, Vec3 } from '../../mol-math/linear-algebra'; +import { RepresentationParamsGetter, Representation, RepresentationContext } from '../../mol-repr/representation'; + + +interface MembraneSphereData { + radius: number + center: Vec3 +} + + +const MembraneSphereParams = { + ...Mesh.Params, + cellColor: PD.Color(ColorNames.orange), + cellScale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }), + radius: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }), + center: PD.Vec3(Vec3.create(0, 0, 0)), + quality: { ...Mesh.Params.quality, isEssential: false }, +}; + +type MeshParams = typeof MembraneSphereParams + +const MembraneSphereVisuals = { + 'mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneSphereData, MeshParams>) => ShapeRepresentation(getMBShape, Mesh.Utils), +}; + +export const MBParams = { + ...MembraneSphereParams +}; +export type MBParams = typeof MBParams +export type UnitcellProps = PD.Values<MBParams> + +function getMBMesh(data: MembraneSphereData, props: UnitcellProps, mesh?: Mesh) { + const state = MeshBuilder.createState(256, 128, mesh); + const radius = props.radius; + const asphere = Sphere(3); + const trans: Mat4 = Mat4.identity(); + Mat4.fromScaling(trans, Vec3.create(radius, radius, radius)); + state.currentGroup = 1; + MeshBuilder.addPrimitive(state, trans, asphere); + const m = MeshBuilder.getMesh(state); + return m; +} + +function getMBShape(ctx: RuntimeContext, data: MembraneSphereData, props: UnitcellProps, shape?: Shape<Mesh>) { + const geo = getMBMesh(data, props, shape && shape.geometry); + const label = 'mb'; + return Shape.create(label, data, geo, () => props.cellColor, () => 1, () => label); +} + +export type MBRepresentation = Representation<MembraneSphereData, MBParams> +export function MBRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneSphereData, MBParams>): MBRepresentation { + return Representation.createMulti('MB', ctx, getParams, Representation.StateBuilder, MembraneSphereVisuals as unknown as Representation.Def<MembraneSphereData, MBParams>); +} \ No newline at end of file diff --git a/src/extensions/cellpack/state.ts b/src/extensions/cellpack/state.ts index 9be5b9ede505b01e6e22f4961493b226a52d20e0..c06979faddf27689c46ed661006e7da078b3a500 100644 --- a/src/extensions/cellpack/state.ts +++ b/src/extensions/cellpack/state.ts @@ -1,7 +1,8 @@ /** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Ludovic Autin <ludovic.autin@gmail.com> */ import { PluginStateObject as PSO, PluginStateTransform } from '../../mol-plugin-state/objects'; @@ -15,9 +16,13 @@ import { PluginContext } from '../../mol-plugin/context'; import { CellPackInfoProvider } from './property'; import { Structure, StructureSymmetry, Unit, Model } from '../../mol-model/structure'; import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry'; +import { Vec3, Quat } from '../../mol-math/linear-algebra'; +import { StateTransformer } from '../../mol-state'; +import { MBRepresentation, MBParams } from './representation'; +import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';; +import { getFloatValue } from './util'; -export const DefaultCellPackBaseUrl = 'https://mesoscope.scripps.edu/data/cellPACK_data/cellPACK_database_1.1.0/'; - +export const DefaultCellPackBaseUrl = 'https://raw.githubusercontent.com/mesoscope/cellPACK_data/master/cellPACK_database_1.1.0'; export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { } export { ParseCellPack }; @@ -26,26 +31,173 @@ const ParseCellPack = PluginStateTransform.BuiltIn({ name: 'parse-cellpack', display: { name: 'Parse CellPack', description: 'Parse CellPack from JSON data' }, from: PSO.Format.Json, - to: CellPack + to: CellPack, + params: a => { + return { + resultsFile: PD.File({ accept: '.bin' }), + baseUrl: PD.Text(DefaultCellPackBaseUrl) + }; + } })({ - apply({ a }) { + apply({ a, params, cache }, plugin: PluginContext) { return Task.create('Parse CellPack', async ctx => { const cell = a.data as Cell; - + let counter_id = 0; + let fiber_counter_id = 0; + let comp_counter = 0; const packings: CellPacking[] = []; const { compartments, cytoplasme } = cell; + if (!cell.mapping_ids) cell.mapping_ids = {}; + if (cytoplasme) { + packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients }); + for (const iName in cytoplasme.ingredients) { + if (cytoplasme.ingredients[iName].ingtype === 'fiber') { + cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName]; + if (!cytoplasme.ingredients[iName].nbCurve) cytoplasme.ingredients[iName].nbCurve = 0; + fiber_counter_id++; + } else { + cell.mapping_ids[counter_id] = [comp_counter, iName]; + if (!cytoplasme.ingredients[iName].results) { cytoplasme.ingredients[iName].results = []; } + counter_id++; + } + } + comp_counter++; + } if (compartments) { for (const name in compartments) { const { surface, interior } = compartments[name]; - if (surface) packings.push({ name, location: 'surface', ingredients: surface.ingredients }); - if (interior) packings.push({ name, location: 'interior', ingredients: interior.ingredients }); + let filename = ''; + if (compartments[name].geom_type === 'file') { + filename = (compartments[name].geom) ? compartments[name].geom as string : ''; + } + const compartment = { filename: filename, geom_type: compartments[name].geom_type, compartment_primitives: compartments[name].mb }; + if (surface) { + packings.push({ name, location: 'surface', ingredients: surface.ingredients, compartment: compartment }); + for (const iName in surface.ingredients) { + if (surface.ingredients[iName].ingtype === 'fiber') { + cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName]; + if (!surface.ingredients[iName].nbCurve) surface.ingredients[iName].nbCurve = 0; + fiber_counter_id++; + } else { + cell.mapping_ids[counter_id] = [comp_counter, iName]; + if (!surface.ingredients[iName].results) { surface.ingredients[iName].results = []; } + counter_id++; + } + } + comp_counter++; + } + if (interior) { + if (!surface) packings.push({ name, location: 'interior', ingredients: interior.ingredients, compartment: compartment }); + else packings.push({ name, location: 'interior', ingredients: interior.ingredients }); + for (const iName in interior.ingredients) { + if (interior.ingredients[iName].ingtype === 'fiber') { + cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName]; + if (!interior.ingredients[iName].nbCurve) interior.ingredients[iName].nbCurve = 0; + fiber_counter_id++; + } else { + cell.mapping_ids[counter_id] = [comp_counter, iName]; + if (!interior.ingredients[iName].results) { interior.ingredients[iName].results = []; } + counter_id++; + } + } + comp_counter++; + } } } - if (cytoplasme) packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients }); + const { options } = cell; + let resultsAsset: Asset.Wrapper<'binary'> | undefined; + if (params.resultsFile) { + resultsAsset = await plugin.runTask(plugin.managers.asset.resolve(params.resultsFile, 'binary', true)); + } else if (options?.resultfile) { + const url = `${params.baseUrl}/results/${options.resultfile}`; + resultsAsset = await plugin.runTask(plugin.managers.asset.resolve(Asset.getUrlAsset(plugin.managers.asset, url), 'binary', true)); + } + if (resultsAsset) { + (cache as any).asset = resultsAsset; + const results = resultsAsset.data; + // flip the byte order if needed + const buffer = IsNativeEndianLittle ? results.buffer : flipByteOrder(results, 4); + const numbers = new DataView(buffer); + const ninst = getFloatValue(numbers, 0); + const npoints = getFloatValue(numbers, 4); + const ncurve = getFloatValue(numbers, 8); + + let offset = 12; + + if (ninst !== 0) { + const pos = new Float32Array(buffer, offset, ninst * 4); + offset += ninst * 4 * 4; + const quat = new Float32Array(buffer, offset, ninst * 4); + offset += ninst * 4 * 4; + + for (let i = 0; i < ninst; i++) { + const x: number = pos[i * 4 + 0]; + const y: number = pos[i * 4 + 1]; + const z: number = pos[i * 4 + 2]; + const ingr_id = pos[i * 4 + 3] as number; + const pid = cell.mapping_ids![ingr_id]; + if (!packings[pid[0]].ingredients[pid[1]].results) { + packings[pid[0]].ingredients[pid[1]].results = []; + } + packings[pid[0]].ingredients[pid[1]].results.push([Vec3.create(x, y, z), + Quat.create(quat[i * 4 + 0], quat[i * 4 + 1], quat[i * 4 + 2], quat[i * 4 + 3])]); + } + } + if (npoints !== 0) { + const ctr_pos = new Float32Array(buffer, offset, npoints * 4); + offset += npoints * 4 * 4; + offset += npoints * 4 * 4; + const ctr_info = new Float32Array(buffer, offset, npoints * 4); + offset += npoints * 4 * 4; + const curve_ids = new Float32Array(buffer, offset, ncurve * 4); + offset += ncurve * 4 * 4; + + let counter = 0; + let ctr_points: Vec3[] = []; + let prev_ctype = 0; + let prev_cid = 0; + + for (let i = 0; i < npoints; i++) { + const x: number = -ctr_pos[i * 4 + 0]; + const y: number = ctr_pos[i * 4 + 1]; + const z: number = ctr_pos[i * 4 + 2]; + const cid: number = ctr_info[i * 4 + 0]; // curve id + const ctype: number = curve_ids[cid * 4 + 0]; // curve type + // cid 148 165 -1 0 + // console.log("cid ",cid,ctype,prev_cid,prev_ctype);//165,148 + if (prev_ctype !== ctype) { + const pid = cell.mapping_ids![-prev_ctype - 1]; + const cname = `curve${counter}`; + packings[pid[0]].ingredients[pid[1]].nbCurve = counter + 1; + packings[pid[0]].ingredients[pid[1]][cname] = ctr_points; + ctr_points = []; + counter = 0; + } else if (prev_cid !== cid) { + ctr_points = []; + const pid = cell.mapping_ids![-prev_ctype - 1]; + const cname = `curve${counter}`; + packings[pid[0]].ingredients[pid[1]][cname] = ctr_points; + counter += 1; + } + ctr_points.push(Vec3.create(x, y, z)); + prev_ctype = ctype; + prev_cid = cid; + } + + // do the last one + const pid = cell.mapping_ids![-prev_ctype - 1]; + const cname = `curve${counter}`; + packings[pid[0]].ingredients[pid[1]].nbCurve = counter + 1; + packings[pid[0]].ingredients[pid[1]][cname] = ctr_points; + } + } return new CellPack({ cell, packings }); }); - } + }, + dispose({ cache }) { + ((cache as any)?.asset as Asset.Wrapper | undefined)?.dispose(); + }, }); export { StructureFromCellpack }; @@ -77,9 +229,8 @@ const StructureFromCellpack = PluginStateTransform.BuiltIn({ await CellPackInfoProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, structure, { info: { packingsCount: a.data.packings.length, packingIndex: params.packing, colors } }); - (cache as any).assets = assets; - return new PSO.Molecule.Structure(structure, { label: packing.name }); + return new PSO.Molecule.Structure(structure, { label: packing.name + '.' + packing.location }); }); }, dispose({ b, cache }) { @@ -125,7 +276,7 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({ const s = await StructureSymmetry.buildAssembly(initial_structure, a.id).runInContext(ctx); structures.push(s); } - const builder = Structure.Builder(); + const builder = Structure.Builder({ label: 'Membrane' }); let offsetInvariantId = 0; for (const s of structures) { let maxInvariantId = 0; @@ -148,3 +299,28 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({ b?.data.customPropertyDescriptors.dispose(); } }); + +const CreateTransformer = StateTransformer.builderFactory('cellPACK'); +export const CreateCompartmentSphere = CreateTransformer({ + name: 'create-compartment-sphere', + display: 'CompartmentSphere', + from: PSO.Root, // or whatever data source + to: PSO.Shape.Representation3D, + params: { + center: PD.Vec3(Vec3()), + radius: PD.Numeric(1), + label: PD.Text(`Compartment Sphere`) + } +})({ + canAutoUpdate({ oldParams, newParams }) { + return true; + }, + apply({ a, params }, plugin: PluginContext) { + return Task.create('Compartment Sphere', async ctx => { + const data = params; + const repr = MBRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => (MBParams)); + await repr.createOrUpdate({ ...params, quality: 'custom', xrayShaded: true, doubleSided: true }, data).runInContext(ctx); + return new PSO.Shape.Representation3D({ repr, sourceData: a }, { label: data.label }); + }); + } +}); \ No newline at end of file diff --git a/src/extensions/cellpack/util.ts b/src/extensions/cellpack/util.ts index 2d25a12cdf6d987033e68fa53e6d5c43318d7707..b7c8aea3053bdb970a4b68db0b8b6035fd58909f 100644 --- a/src/extensions/cellpack/util.ts +++ b/src/extensions/cellpack/util.ts @@ -1,7 +1,8 @@ /** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Ludovic Autin <ludovic.autin@gmail.com> */ import { CIF } from '../../mol-io/reader/cif'; @@ -37,7 +38,7 @@ async function downloadPDB(plugin: PluginContext, url: string, id: string, asset } export async function getFromPdb(plugin: PluginContext, pdbId: string, assetManager: AssetManager) { - const { cif, asset } = await downloadCif(plugin, `https://models.rcsb.org/${pdbId.toUpperCase()}.bcif`, true, assetManager); + const { cif, asset } = await downloadCif(plugin, `https://models.rcsb.org/${pdbId}.bcif`, true, assetManager); return { mmcif: cif.blocks[0], asset }; } @@ -74,4 +75,35 @@ export function getStructureMean(structure: Structure) { } const { elementCount } = structure; return Vec3.create(xSum / elementCount, ySum / elementCount, zSum / elementCount); +} + +export function getFloatValue(value: DataView, offset: number) { + // if the last byte is a negative value (MSB is 1), the final + // float should be too + const negative = value.getInt8(offset + 2) >>> 31; + + // this is how the bytes are arranged in the byte array/DataView + // buffer + const [b0, b1, b2, exponent] = [ + // get first three bytes as unsigned since we only care + // about the last 8 bits of 32-bit js number returned by + // getUint8(). + // Should be the same as: getInt8(offset) & -1 >>> 24 + value.getUint8(offset), + value.getUint8(offset + 1), + value.getUint8(offset + 2), + + // get the last byte, which is the exponent, as a signed int + // since it's already correct + value.getInt8(offset + 3) + ]; + + let mantissa = b0 | (b1 << 8) | (b2 << 16); + if (negative) { + // need to set the most significant 8 bits to 1's since a js + // number is 32 bits but our mantissa is only 24. + mantissa |= 255 << 24; + } + + return mantissa * Math.pow(10, exponent); } \ No newline at end of file diff --git a/src/mol-plugin-ui/sequence.tsx b/src/mol-plugin-ui/sequence.tsx index 068b4e8ab61783638b19f87f66d61a1701292b9a..44316caf7717d7c5cba6e9942b97cde8a4d2e8f5 100644 --- a/src/mol-plugin-ui/sequence.tsx +++ b/src/mol-plugin-ui/sequence.tsx @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author David Sehnal <david.sehnal@gmail.com> @@ -25,6 +25,9 @@ import { StructureSelectionManager } from '../mol-plugin-state/manager/structure import { arrayEqual } from '../mol-util/array'; const MaxDisplaySequenceLength = 5000; +// TODO: add virtualized Select controls (at best with a search box)? +const MaxSelectOptionsCount = 1000; +const MaxSequenceWrappersCount = 30; function opKey(l: StructureElement.Location) { const ids = SP.unit.pdbx_struct_oper_list_ids(l); @@ -94,7 +97,7 @@ function getSequenceWrapper(state: { structure: Structure, modelEntityId: string } } -function getModelEntityOptions(structure: Structure, polymersOnly = false) { +function getModelEntityOptions(structure: Structure, polymersOnly = false): [string, string][] { const options: [string, string][] = []; const l = StructureElement.Location.create(structure); const seen = new Set<string>(); @@ -118,13 +121,17 @@ function getModelEntityOptions(structure: Structure, polymersOnly = false) { const label = `${id}: ${description}`; options.push([key, label]); seen.add(key); + + if (options.length > MaxSelectOptionsCount) { + return [['', 'Too many entities']]; + } } if (options.length === 0) options.push(['', 'No entities']); return options; } -function getChainOptions(structure: Structure, modelEntityId: string) { +function getChainOptions(structure: Structure, modelEntityId: string): [number, string][] { const options: [number, string][] = []; const l = StructureElement.Location.create(structure); const seen = new Set<number>(); @@ -144,13 +151,17 @@ function getChainOptions(structure: Structure, modelEntityId: string) { options.push([id, label]); seen.add(id); + + if (options.length > MaxSelectOptionsCount) { + return [[-1, 'Too many chains']]; + } } - if (options.length === 0) options.push([-1, 'No units']); + if (options.length === 0) options.push([-1, 'No chains']); return options; } -function getOperatorOptions(structure: Structure, modelEntityId: string, chainGroupId: number) { +function getOperatorOptions(structure: Structure, modelEntityId: string, chainGroupId: number): [string, string][] { const options: [string, string][] = []; const l = StructureElement.Location.create(structure); const seen = new Set<string>(); @@ -168,6 +179,10 @@ function getOperatorOptions(structure: Structure, modelEntityId: string, chainGr const label = unit.conformation.operator.name; options.push([id, label]); seen.add(id); + + if (options.length > MaxSelectOptionsCount) { + return [['', 'Too many operators']]; + } } if (options.length === 0) options.push(['', 'No operators']); @@ -266,6 +281,7 @@ export class SequenceView extends PluginUIComponent<{ defaultMode?: SequenceView }, this.plugin.managers.structure.selection), label: `${cLabel} | ${eLabel}` }); + if (wrappers.length > MaxSequenceWrappersCount) return []; } } }