From ca825d720e2c8b1d056a60b189b4182868236f4a Mon Sep 17 00:00:00 2001 From: ludovic autin <autin@scripps.edu> Date: Wed, 11 Aug 2021 10:44:44 -0700 Subject: [PATCH] binary model loading support, latest mycoplasma model. --- README.md | 4 +- src/extensions/cellpack/data.ts | 10 ++ src/extensions/cellpack/model.ts | 198 ++++++++++++++------ src/extensions/cellpack/preset.ts | 11 +- src/extensions/cellpack/property.ts | 2 +- src/extensions/cellpack/representation.ts | 84 +++++++++ src/extensions/cellpack/state.ts | 209 ++++++++++++++++++++-- src/extensions/cellpack/util.ts | 36 +++- 8 files changed, 475 insertions(+), 79 deletions(-) create mode 100644 src/extensions/cellpack/representation.ts diff --git a/README.md b/README.md index c9f448128..0ce2d30e7 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/data.ts b/src/extensions/cellpack/data.ts index 98fd34756..13be96b84 100644 --- a/src/extensions/cellpack/data.ts +++ b/src/extensions/cellpack/data.ts @@ -15,6 +15,7 @@ export interface CellPacking { name: string, location: 'surface' | 'interior' | 'cytoplasme', ingredients: Packing['ingredients'] + mb?: Membrane } // @@ -23,6 +24,7 @@ export interface Cell { recipe: Recipe cytoplasme?: Packing compartments?: { [key: string]: Compartment } + mapping_ids?: { [key: number]: [number,string] } } export interface Recipe { @@ -35,6 +37,12 @@ export interface Recipe { export interface Compartment { surface?: Packing interior?: Packing + mb?: Membrane +} + +export interface Membrane{ + positions: number[]; + radii: number[]; } export interface Packing { @@ -64,11 +72,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/model.ts b/src/extensions/cellpack/model.ts index 8213f1adc..d92f93a51 100644 --- a/src/extensions/cellpack/model.ts +++ b/src/extensions/cellpack/model.ts @@ -8,7 +8,7 @@ import { StateAction, StateBuilder, StateTransformer, State } from '../../mol-st 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 } 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 +17,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, CreateSphere } from './state'; import { MolScriptBuilder as MS } from '../../mol-script/language/builder'; import { getMatFromResamplePoints } from './curve'; import { compile } from '../../mol-script/runtime/query/compiler'; @@ -30,6 +30,7 @@ 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 fetch from 'node-fetch'; function getCellPackModelUrl(fileName: string, baseUrl: string) { return `${baseUrl}/results/${fileName}`; @@ -41,10 +42,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); let assets: Asset.Wrapper[] = []; if (!trajectory) { @@ -72,6 +77,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,34 +106,44 @@ 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 label = { label: 'Model', description: Structure.elementDescription(base) }; + //let structure = new PSO.Molecule.Structure(base, label); const { assembly } = props; if (assembly) { 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){ + var sel: any = source.source.selection; + //selection can have the model ID as well. remove it + const asymIds: string[] = sel.replaceAll(' ', '').replaceAll(':', '').split('or').slice(1); + //console.log("selection is ", source.selection, asymIds); + //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')]) + // }) + //]); 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')]) }) - ]); + ]); } else { query = MS.struct.modifier.union([ MS.struct.generator.atomGroups({ 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']) }) - ]); + ]); } - const compiled = compile<StructureSelection>(query); const result = compiled(new QueryContext(structure)); structure = StructureSelection.unionStructure(result); - + //change here if possible the label or the? + //structure.label = source.name; return structure; } @@ -141,9 +157,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 +184,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[]; @@ -190,8 +206,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 +323,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 +341,15 @@ 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) { + //console.log("await getCurve", name, nbCurve, model); structure = await getCurve(plugin, name, ingredient, getCurveTransforms(ingredient), model); + //console.log("getCurve", structure); } else { + if ( (!results || results.length===0)) return; let bu: string|undefined = source.bu ? source.bu : undefined; if (bu){ if (bu === 'AU') { @@ -340,10 +358,14 @@ 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){ + //if (name === 'MG_213_214_298_6MER_ADP') { + // console.log("getStructure ", ingredient.offset,ingredient.principalVector,ingredient); + //} + var pcp = ingredient.principalVector?ingredient.principalVector:ingredient.principalAxis; + if (pcp){ legacy = false; const structureMean = getStructureMean(structure); Vec3.negate(structureMean, structureMean); @@ -351,22 +373,38 @@ 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())){ + let 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')//(name === 'MG_213_214_298_6MER_ADP') + { + Vec3.negate(o, o); + //console.log("after negate offset ",name, 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){ + let p: Vec3 = Vec3.create(pcp[0], pcp[1], pcp[2]); + if (!Vec3.exactEquals(p, Vec3.unitZ)){ + //if (location !== 'surface')//(name === 'MG_213_214_298_6MER_ADP') + //{ + //Vec3.negate(p, p); + //console.log("after negate ", p); + // } 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); + //if (location !== 'surface') Mat4.invert(m, m); structure = Structure.transform(structure, m); + //if (location === 'surface') console.log('surface',name,ingredient.principalVector, q); } } } - structure = getAssembly(getResultTransforms(results, legacy), structure); + + structure = getAssembly(name, getResultTransforms(results, legacy), structure); + //console.log("getStructure ", name, structure.label, structure); } return { structure, assets }; @@ -374,15 +412,15 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi 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; + //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 +428,8 @@ 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)); + //skipColors = true; } } } @@ -414,21 +453,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)); @@ -477,30 +515,58 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p 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 } }); } - - const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } }) + const props = { + type: { + name: 'assembly' as const, + params: { id: '1' } + } + }; + if (params.source.name === 'id' && params.source.params !== "MycoplasmaGenitalium.json") + //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); + const membraneParams = { + representation: params.preset.representation, + }; + await CellpackMembranePreset.apply(membrane, membraneParams, plugin); + } 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); + } } 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 modelFile: Asset.File|null= params.results; if (params.source.name === 'id') { const url = Asset.getUrlAsset(plugin.managers.asset, getCellPackModelUrl(params.source.params, params.baseUrl)); + //console.log("getting "+params.source.params+" "+url.url); cellPackJson = state.build().toRoot() .apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.source.params }, { state: { isGhost: true } }); + + if (params.source.params === "MycoplasmaGenitalium.json"){ + const m_url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/results/results_149_curated_serialized.bin`); + //console.log("getting results "+m_url.url); + const model_data = await fetch(m_url.url); + modelFile = Asset.File(new File([await model_data.arrayBuffer()], 'model.bin')); + //console.log("MycoplasmaGenitalium.json loading setup ?",modelFile); + } } else { const file = params.source.params; + const rfile = params.results; if (!file?.file) { plugin.log.error('No file selected'); return; @@ -510,25 +576,29 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat 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')); + modelFile = Asset.File(new File([data['model.bin']], 'model.bin')); objectForEach(data, (v, k) => { if (k === 'model.json') return; + else if (k === 'model.bin') return; ingredientFiles.push(Asset.File(new File([v], k))); }); } else { jsonFile = file; + modelFile = rfile; } - cellPackJson = state.build().toRoot() .apply(StateTransforms.Data.ReadFile, { file: jsonFile, isBinary: false, label: jsonFile.name }, { state: { isGhost: true } }); + } const cellPackBuilder = cellPackJson .apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } }) - .apply(ParseCellPack); - + .apply(ParseCellPack,{modeFile:modelFile}); + const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(runtime); + const { packings } = cellPackObject.obj!.data; - + await handleHivRna(plugin, packings, params.baseUrl); for (let i = 0, il = packings.length; i < il; ++i) { @@ -544,8 +614,22 @@ 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].location === 'surface') { + if (params.membrane){ + await loadMembrane(plugin, packings[i].name, state, params); + } + if (typeof(packings[i].mb) !== 'undefined'){ + var nSpheres = packings[i].mb!.positions.length/3; + for (var j=0;j<nSpheres;j++) { + await state.build() + .toRoot() + .apply(CreateSphere, {center:Vec3.create(packings[i].mb!.positions[j*3+0], + packings[i].mb!.positions[j*3+1], + packings[i].mb!.positions[j*3+2]), + radius:packings[i].mb!.radii[j] }) + .commit() + } + } } } } @@ -556,16 +640,18 @@ const LoadCellPackModelParams = { ['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'], + ['HIV-1_0.1.6-8_mixed_radii_pdb.json', 'HIV'], ['influenza_model1.json', 'Influenza envelope'], ['InfluenzaModel2.json', 'Influenza Complete'], ['ExosomeModel.json', 'Exosome Model'], - ['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma simple'], - ['MycoplasmaModel.json', 'Mycoplasma WholeCell 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.' }), }, { options: [['id', 'Id'], ['file', 'File']] }), baseUrl: PD.Text(DefaultCellPackBaseUrl), + results : PD.File({ accept: '.bin,.json' }), membrane: PD.Boolean(true), ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredients' }), preset: PD.Group({ @@ -581,4 +667,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 0cd3ae55b..10c1b7004 100644 --- a/src/extensions/cellpack/preset.ts +++ b/src/extensions/cellpack/preset.ts @@ -9,8 +9,8 @@ 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'; +//import { CellPackInfoProvider } from './property'; +//import { CellPackProvidedColorThemeProvider } from './color/provided'; export const CellpackPackingPresetParams = { traceOnly: PD.Boolean(true), @@ -42,8 +42,9 @@ 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; + //const info = structureCell.obj?.data && CellPackInfoProvider.get(structureCell.obj?.data).value; + //default is generated + const color = CellPackGenerateColorThemeProvider.name;//info?.colors ? CellPackProvidedColorThemeProvider.name : CellPackGenerateColorThemeProvider.name; const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {}); const representations = { @@ -92,4 +93,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 c2b8550e5..608b01805 100644 --- a/src/extensions/cellpack/property.ts +++ b/src/extensions/cellpack/property.ts @@ -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 000000000..8ffa32f90 --- /dev/null +++ b/src/extensions/cellpack/representation.ts @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +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 { 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; + var p = DefaultPolyhedronProps; + p.detail = 3; + p.radius = radius; + const { vertices, indices } = Icosahedron(); + const asphere = Polyhedron(vertices, indices, p); + //const asphere = Sphere(3); + var 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 function getMBData(model: Model, symmetry: Symmetry, props: UnitcellProps) { + const ref = Vec3(); + if (props.ref === 'model') { + Vec3.transformMat4(ref, Model.getCenter(model), symmetry.spacegroup.cell.toFractional); + } + return { symmetry, ref }; +} +*/ + +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 b5fabdf4f..d7dd385d1 100644 --- a/src/extensions/cellpack/state.ts +++ b/src/extensions/cellpack/state.ts @@ -9,15 +9,19 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { Task } from '../../mol-task'; import { CellPack as _CellPack, Cell, CellPacking } from './data'; import { createStructureFromCellPack } from './model'; -import { IngredientFiles } from './util'; +import { IngredientFiles, getFloatValue } from './util'; import { Asset } from '../../mol-util/assets'; 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 { readFromFile } from '../../mol-util/data-source'; +import { StateTransformer } from '../../mol-state'; +import { MBRepresentation, MBParams } from './representation'; -export const DefaultCellPackBaseUrl = 'https://mesoscope.scripps.edu/data/cellPACK_data/cellPACK_database_1.1.0/'; - +//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,23 +30,178 @@ 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 { + modeFile: PD.File({ accept: '.bin' }) + }; + } })({ - apply({ a }) { + apply({a, params}) { 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; + let iName = ""; + if(!cell.mapping_ids)cell.mapping_ids={}; + if (cytoplasme) { + packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients }); + for (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 }); + if (surface) { + packings.push({ name, location: 'surface', ingredients: surface.ingredients, mb: compartments[name].mb }); + for (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) { + packings.push({ name, location: 'interior', ingredients: interior.ingredients }); + for (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 }); - + if (params.modeFile && params.modeFile.file){ + const model_data = await readFromFile(params.modeFile.file, 'binary').runInContext(ctx);//async ? + var numbers = new DataView(model_data.buffer); + var ninst = getFloatValue(numbers,0); + var npoints = getFloatValue(numbers,4); + var ncurve = getFloatValue(numbers,8); + /* + console.log("Parse CellPack"); + console.log("ninst ",ninst); + console.log("npoints ",npoints); + console.log("ncurve ",ncurve); + */ + let pos = new Float32Array(); + let quat = new Float32Array(); + let ctr_pos = new Float32Array(); + //let ctr_norm = new Float32Array(); + let ctr_info = new Float32Array(); + let curve_ids = new Float32Array(); + //let ctrl_pts= new Float32Array(); + //let ctrl_normal= new Float32Array(); + //let ctrl_info= new Float32Array();//#curve_id, curve_type, angle, uLength + let offset = 12; + if (ninst !== 0){ + pos = new Float32Array(model_data.buffer,offset,ninst*4);offset+=ninst*4*4; + quat = new Float32Array(model_data.buffer,offset,ninst*4);offset+=ninst*4*4; + } + if ( npoints != 0 ) + { + ctr_pos = new Float32Array(model_data.buffer,offset,npoints*4);offset+=npoints*4*4; + //ctr_norm = new Float32Array(model_data,offset,ncurve*4); + offset+=npoints*4*4; + ctr_info = new Float32Array(model_data.buffer,offset,npoints*4);offset+=npoints*4*4; + curve_ids = new Float32Array(model_data.buffer,offset,ncurve*4);offset+=ncurve*4*4; + } + //data_from_buffer = {"pos":pos,"quat":quat,"ctrl_pts":ctrl_pts,"ctrl_normal":ctrl_normal,"ctrl_info":ctrl_info}; + //create all the bodys then the instance ? + for (var i=0;i<ninst;i++) + { + let x:number = pos[i*4+0]; + let y:number = pos[i*4+1]; + let z:number = pos[i*4+2]; + //q = new THREE.Quaternion(quats[i*4+0],quats[i*4+1],quats[i*4+2],quats[i*4+3]); + let ingr_id = pos[i*4+3] as number; + let 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])]); + } + let counter = 0; + let ctr_points:Vec3[] = []; + let prev_ctype = 0; + let prev_cid = 0; + /* + console.log("ctr_info",ctr_info); + console.log("curve_ids",curve_ids); + */ + for (var i=0;i<npoints;i++) + { + let x:number = -ctr_pos[i*4+0]; + let y:number = ctr_pos[i*4+1]; + let z:number = ctr_pos[i*4+2]; + let cid:number = ctr_info[i*4+0];//curve id + let 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){ + let pid = cell.mapping_ids[-prev_ctype-1]; + const cname = `curve${counter}`; + //console.log("ctype ",pid,cname,prev_ctype,(-prev_ctype-1)); + 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 = []; + let pid = cell.mapping_ids[-prev_ctype-1]; + const cname = `curve${counter}`; + //console.log("cid ",pid,cname,prev_ctype,(-prev_ctype-1),prev_cid,cid); + 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 + if ( npoints != 0 ) + { + let pid = cell.mapping_ids[-prev_ctype-1]; + const cname = `curve${counter}`; + //console.log("ctype ",pid,cname,prev_ctype,(-prev_ctype-1)); + packings[pid[0]].ingredients[pid[1]].nbCurve = counter+1; + packings[pid[0]].ingredients[pid[1]][cname] = ctr_points; + ctr_points = []; + } + counter = 0; + //console.log("done"); + //console.log(packings); + } return new CellPack({ cell, packings }); }); } @@ -77,9 +236,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 +283,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 +306,28 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({ b?.data.customPropertyDescriptors.dispose(); } }); + + +const CreateTransformer = StateTransformer.builderFactory('cellPACK'); +export const CreateSphere = CreateTransformer({ + name: 'create-sphere', + display: 'Sphere', + from: PSO.Root, // or whatever data source + to: PSO.Shape.Representation3D, + params: { + center: PD.Vec3(Vec3()), + radius: PD.Numeric(1) + } +})({ + canAutoUpdate({ oldParams, newParams }) { + return true; + }, + apply({ a, params }, plugin: PluginContext) { + return Task.create('Custom Sphere', async ctx => { + const data = params; + const repr = MBRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => (MBParams)); + await repr.createOrUpdate({ ...params, quality: 'custom',doubleSided:true }, data).runInContext(ctx); + return new PSO.Shape.Representation3D({ repr, sourceData: a }, { label: `My Sphere` }); + }); + } +}); \ No newline at end of file diff --git a/src/extensions/cellpack/util.ts b/src/extensions/cellpack/util.ts index 0d790ad1b..ec14f1797 100644 --- a/src/extensions/cellpack/util.ts +++ b/src/extensions/cellpack/util.ts @@ -37,7 +37,8 @@ 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); + //${pdbId.toUpperCase()} + 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); -} \ No newline at end of file +} + +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); +} -- GitLab