diff --git a/README.md b/README.md index b056d10f9655b711a5d4bf6ba840aa991b2cd85d..a03c9462eb3ba0a884e85a3f5facb8de1830d000 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,12 @@ Run the image export NODE_PATH="build/src"; node build/state-docs +**Convert any CIF to BinaryCIF** + + node build/model-server/preprocess -i file.cif -ob file.bcif + +To see all available commands, use ``node build/model-server/preprocess -h``. + ## Contributing Just open an issue or make a pull request. All contributions are welcome. diff --git a/src/apps/basic-wrapper/coloring.ts b/src/apps/basic-wrapper/coloring.ts new file mode 100644 index 0000000000000000000000000000000000000000..54c89117c7f78c5209364fab76dcd1d35f3f7599 --- /dev/null +++ b/src/apps/basic-wrapper/coloring.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { CustomElementProperty } from 'mol-model-props/common/custom-element-property'; +import { Model, ElementIndex } from 'mol-model/structure'; +import { Color } from 'mol-util/color'; + +export const StripedResidues = CustomElementProperty.create<number>({ + isStatic: true, + name: 'basic-wrapper-residue-striping', + display: 'Residue Stripes', + getData(model: Model) { + const map = new Map<ElementIndex, number>(); + const residueIndex = model.atomicHierarchy.residueAtomSegments.index; + for (let i = 0, _i = model.atomicHierarchy.atoms._rowCount; i < _i; i++) { + map.set(i as ElementIndex, residueIndex[i] % 2); + } + return map; + }, + coloring: { + getColor(e) { return e === 0 ? Color(0xff0000) : Color(0x0000ff) }, + defaultColor: Color(0x777777) + }, + format(e) { + return e === 0 ? 'Odd stripe' : 'Even stripe' + } +}) \ No newline at end of file diff --git a/src/apps/basic-wrapper/index.html b/src/apps/basic-wrapper/index.html index 07592b99d310d9376a10be671fa594b4314e01e0..efa0b8c9f43fe1a4085acd2509b11096e997234c 100644 --- a/src/apps/basic-wrapper/index.html +++ b/src/apps/basic-wrapper/index.html @@ -35,19 +35,39 @@ #controls > hr { margin: 5px 0; } + + #controls > input, #controls > select { + width: 100%; + display: block; + } </style> <link rel="stylesheet" type="text/css" href="app.css" /> <script type="text/javascript" src="./index.js"></script> </head> <body> <div id='controls'> - + <h3>Source</h3> + <input type='text' id='url' placeholder='url' /> + <input type='text' id='assemblyId' placeholder='assembly id' /> + <select id='format'> + <option value='cif' selected>CIF</option> + <option value='pdb'>PDB</option> + </select> </div> <div id="app"></div> - <script> + <script> + function $(id) { return document.getElementById(id); } + var pdbId = '1grm', assemblyId= '1'; var url = 'https://www.ebi.ac.uk/pdbe/static/entry/' + pdbId + '_updated.cif'; var format = 'cif'; + + $('url').value = url; + $('url').onchange = function (e) { url = e.target.value; } + $('assemblyId').value = assemblyId; + $('assemblyId').onchange = function (e) { assemblyId = e.target.value; } + $('format').value = format; + $('format').onchange = function (e) { format = e.target.value; } // var url = 'https://www.ebi.ac.uk/pdbe/entry-files/pdb' + pdbId + '.ent'; // var format = 'pdb'; @@ -58,9 +78,8 @@ BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId }); BasicMolStarWrapper.toggleSpin(); - addHeader('Source'); addControl('Load Asym Unit', () => BasicMolStarWrapper.load({ url: url, format: format })); - addControl('Load Assembly 1', () => BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId })); + addControl('Load Assembly', () => BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId })); addSeparator(); @@ -73,7 +92,7 @@ // adjust this number to make the animation faster or slower // requires to "restart" the animation if changed - BasicMolStarWrapper.animate.modelIndex.maxFPS = 4; + BasicMolStarWrapper.animate.modelIndex.maxFPS = 30; addControl('Play To End', () => BasicMolStarWrapper.animate.modelIndex.onceForward()); addControl('Play To Start', () => BasicMolStarWrapper.animate.modelIndex.onceBackward()); @@ -81,24 +100,28 @@ addControl('Play Loop', () => BasicMolStarWrapper.animate.modelIndex.loop()); addControl('Stop', () => BasicMolStarWrapper.animate.modelIndex.stop()); + addHeader('Misc'); + + addControl('Apply Stripes', () => BasicMolStarWrapper.coloring.applyStripes()); + //////////////////////////////////////////////////////// function addControl(label, action) { var btn = document.createElement('button'); btn.onclick = action; btn.innerText = label; - document.getElementById('controls').appendChild(btn); + $('controls').appendChild(btn); } function addSeparator() { var hr = document.createElement('hr'); - document.getElementById('controls').appendChild(hr); + $('controls').appendChild(hr); } function addHeader(header) { var h = document.createElement('h3'); h.innerText = header; - document.getElementById('controls').appendChild(h); + $('controls').appendChild(h); } </script> </body> diff --git a/src/apps/basic-wrapper/index.ts b/src/apps/basic-wrapper/index.ts index ec4ed52e3ce2bc31bfb56da127e952ca6000f459..f030ff31181b3c406ea3aaf2c77337d0fb23af0c 100644 --- a/src/apps/basic-wrapper/index.ts +++ b/src/apps/basic-wrapper/index.ts @@ -11,9 +11,10 @@ import { PluginCommands } from 'mol-plugin/command'; import { StateTransforms } from 'mol-plugin/state/transforms'; import { StructureRepresentation3DHelpers } from 'mol-plugin/state/transforms/representation'; import { Color } from 'mol-util/color'; -import { PluginStateObject as PSO } from 'mol-plugin/state/objects'; +import { PluginStateObject as PSO, PluginStateObject } from 'mol-plugin/state/objects'; import { AnimateModelIndex } from 'mol-plugin/state/animation/built-in'; import { StateBuilder } from 'mol-state'; +import { StripedResidues } from './coloring'; require('mol-plugin/skin/light.scss') type SupportedFormats = 'cif' | 'pdb' @@ -30,6 +31,10 @@ class BasicWrapper { showControls: false } }); + + this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add(StripedResidues.Descriptor.name, StripedResidues.colorTheme!); + this.plugin.lociLabels.addProvider(StripedResidues.labelProvider); + this.plugin.customModelProperties.register(StripedResidues.propertyProvider); } private download(b: StateBuilder.To<PSO.Root>, url: string) { @@ -43,6 +48,7 @@ class BasicWrapper { return parsed .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }) + .apply(StateTransforms.Model.CustomModelProperties, { properties: [StripedResidues.Descriptor.name] }, { ref: 'props', props: { isGhost: false } }) .apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' }); } @@ -110,6 +116,22 @@ class BasicWrapper { stop: () => this.plugin.state.animation.stop() } } + + coloring = { + applyStripes: async () => { + const state = this.plugin.state.dataState; + + const visuals = state.selectQ(q => q.ofType(PluginStateObject.Molecule.Representation3D).filter(c => c.transform.transformer === StateTransforms.Representation.StructureRepresentation3D)); + const tree = state.build(); + const colorTheme = { name: StripedResidues.Descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(StripedResidues.Descriptor.name).defaultValues }; + + for (const v of visuals) { + tree.to(v.transform.ref).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme })); + } + + await PluginCommands.State.Update.dispatch(this.plugin, { state, tree }); + } + } } (window as any).BasicMolStarWrapper = new BasicWrapper(); \ No newline at end of file diff --git a/src/mol-model-props/common/custom-element-property.ts b/src/mol-model-props/common/custom-element-property.ts new file mode 100644 index 0000000000000000000000000000000000000000..609674ae6f8b420e841e2033f366c1e91fa72969 --- /dev/null +++ b/src/mol-model-props/common/custom-element-property.ts @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { ElementIndex, Model, ModelPropertyDescriptor } from 'mol-model/structure'; +import { StructureElement } from 'mol-model/structure/structure'; +import { Location } from 'mol-model/location'; +import { CustomPropertyRegistry } from './custom-property-registry'; +import { Task } from 'mol-task'; +import { ThemeDataContext, ThemeProvider } from 'mol-theme/theme'; +import { ColorTheme, LocationColor } from 'mol-theme/color'; +import { Color } from 'mol-util/color'; +import { TableLegend } from 'mol-util/color/tables'; +import { Loci } from 'mol-model/loci'; +import { OrderedSet } from 'mol-data/int'; + +export { CustomElementProperty }; + +namespace CustomElementProperty { + export interface CreateParams<T> { + isStatic: boolean, + name: string, + autoAttach?: boolean, + display: string, + attachableTo?: (model: Model) => boolean, + getData(model: Model): Map<ElementIndex, T> | Promise<Map<ElementIndex, T>>, + format?(e: T): string, + coloring?: { + getColor: (e: T) => Color, + defaultColor: Color + } + } + + export function create<T>(params: CreateParams<T>) { + const name = params.name; + + const Descriptor = ModelPropertyDescriptor({ + isStatic: params.isStatic, + name: params.name, + }); + + function attach(model: Model) { + return Task.create(`Attach ${params.display}`, async () => { + const data = await params.getData(model); + + if (params.isStatic) { + model._staticPropertyData[name] = data; + } else { + model._dynamicPropertyData[name] = data; + } + + model.customProperties.add(Descriptor); + + return true; + }) + } + + function getStatic(e: StructureElement) { return e.unit.model._staticPropertyData[name].get(e.element); } + function getDynamic(e: StructureElement) { return e.unit.model._staticPropertyData[name].get(e.element); } + + const propertyProvider: CustomPropertyRegistry.Provider = { + option: [name, params.display], + descriptor: Descriptor, + defaultSelected: !!params.autoAttach, + attachableTo: params.attachableTo || (() => true), + attach + }; + + const get = params.isStatic ? getStatic : getDynamic; + + function has(model: Model) { return model.customProperties.has(Descriptor); } + + function Coloring(ctx: ThemeDataContext, props: {}): ColorTheme<{}> { + let color: LocationColor; + const getColor = params.coloring!.getColor; + const defaultColor = params.coloring!.defaultColor; + + if (ctx.structure && !ctx.structure.isEmpty && has(ctx.structure.models[0])) { + color = (location: Location) => { + if (StructureElement.isLocation(location)) { + const e = get(location); + if (typeof e !== 'undefined') return getColor(e); + } + return defaultColor; + } + } else { + color = () => defaultColor; + } + + return { + factory: Coloring, + granularity: 'group', + color: color, + props: props, + description: 'Assign element colors based on the provided data.', + legend: TableLegend([]) + }; + } + + const colorTheme: ThemeProvider<ColorTheme<{}>, {}> = { + label: params.display, + factory: Coloring, + getParams: () => ({}), + defaultValues: {}, + isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && !ctx.structure.isEmpty && has(ctx.structure.models[0]) + } + + function LabelProvider(loci: Loci): string | undefined { + if (loci.kind === 'element-loci') { + const e = loci.elements[0]; + if (!has(e.unit.model)) return void 0; + return params.format!(get(StructureElement.create(e.unit, e.unit.elements[OrderedSet.getAt(e.indices, 0)]))); + } + return void 0; + } + + return { + Descriptor, + attach, + get, + propertyProvider, + colorTheme: params.coloring ? colorTheme : void 0, + labelProvider: params.format ? LabelProvider : ((loci: Loci) => void 0) + }; + } +} \ No newline at end of file diff --git a/src/mol-plugin/util/custom-prop-registry.ts b/src/mol-model-props/common/custom-property-registry.ts similarity index 100% rename from src/mol-plugin/util/custom-prop-registry.ts rename to src/mol-model-props/common/custom-property-registry.ts diff --git a/src/mol-model/structure/model/properties/utils/secondary-structure.ts b/src/mol-model/structure/model/properties/utils/secondary-structure.ts index 5f977bb97b0ee730906568301c1b1a89c188a8a9..672d113a4bb468bddae55b0d2bf5305f83bbefb1 100644 --- a/src/mol-model/structure/model/properties/utils/secondary-structure.ts +++ b/src/mol-model/structure/model/properties/utils/secondary-structure.ts @@ -48,7 +48,7 @@ export function computeModelDSSP(hierarchy: AtomicHierarchy, conformation: Atomi for (let i = 0, il = proteinResidues.length; i < il; ++i) { type[proteinResidues[i]] = assignment[i] } - + const secondaryStructure: SecondaryStructure = { type, key: [], // TODO @@ -294,7 +294,7 @@ function getDSSPAssignment(flags: Uint32Array, useOriginal = false) { * Q = -332 * 0.42 * 0.20 * * f is the dimensional factor - * + * * q1 and q2 are partial charges which are placed on the C,O * (+q1,-q1) and N,H (-q2,+q2) */ @@ -313,7 +313,7 @@ function calcHbondEnergy(oPos: Vec3, cPos: Vec3, nPos: Vec3, hPos: Vec3) { const distON = Vec3.distance(oPos, nPos) const e1 = Q / distOH - Q / distCH - const e2 = Q / distCN - Q / distON + const e2 = Q / distCN - Q / distON return e1 + e2 } @@ -321,7 +321,7 @@ function calcHbondEnergy(oPos: Vec3, cPos: Vec3, nPos: Vec3, hPos: Vec3) { * The basic turn pattern is a single H bond of type (i, i + n). * We assign an n-turn at residue i if there is an H bond from CO(i) to NH(i + n), * i.e., “n-turn(i)=: Hbond(i, i + n), n = 3, 4, 5.” - * + * * Type: T */ function assignTurns(ctx: DSSPContext) { @@ -329,7 +329,7 @@ function assignTurns(ctx: DSSPContext) { const { chains, residueAtomSegments, chainAtomSegments } = hierarchy const { label_asym_id } = chains - const turnFlag = [ 0, 0, 0, DSSPType.Flag.T3, DSSPType.Flag.T4, DSSPType.Flag.T5 ] + const turnFlag = [0, 0, 0, DSSPType.Flag.T3, DSSPType.Flag.T4, DSSPType.Flag.T5] for (let i = 0, il = proteinResidues.length; i < il; ++i) { const rI = proteinResidues[i] @@ -354,18 +354,18 @@ function assignTurns(ctx: DSSPContext) { /** * Two nonoverlapping stretches of three residues each, i - 1, i, i + 1 and j - 1, j, j + 1, - * form either a parallel or antiparallel bridge, depending on which of + * form either a parallel or antiparallel bridge, depending on which of * two basic patterns is matched. We assign a bridge between residues i and j * if there are two H bonds characteristic of P-structure; in particular, - * + * * Parallel Bridge(i, j) =: * [Hbond(i - 1, j) and Hbond(j, i + 1)] or * [Hbond(j - 1, i) and Hbond(i, j + 1)] - * + * * Antiparallel Bridge(i, j) =: * [Hbond(i, j) and Hbond(j, i)] or * [Hbond(i - 1, j + 1) and Hbond(j - 1, i + l)] - * + * * Type: B */ function assignBridges(ctx: DSSPContext) { @@ -378,7 +378,7 @@ function assignBridges(ctx: DSSPContext) { for (let t = offset[k], _t = offset[k + 1]; t < _t; t++) { const l = b[t] if (k > l) continue - + // Parallel Bridge(i, j) =: [Hbond(i - 1, j) and Hbond(j, i + 1)] i = k + 1 // k is i - 1 j = l @@ -418,18 +418,18 @@ function assignBridges(ctx: DSSPContext) { * A minimal helix is defined by two consecutive n-turns. * For example, a 4-helix, of minimal length 4 from residues i to i + 3, * requires 4-turns at residues i - 1 and i, - * + * * 3-helix(i,i + 2)=: [3-turn(i - 1) and 3-turn(i)] * 4-helix(i,i + 3)=: [4-turn(i - 1) and 4-turn(i)] * 5-helix(i,i + 4)=: [5-turn(i - 1) and 5-turn(i)] - * + * * Type: G (n=3), H (n=4), I (n=5) */ function assignHelices(ctx: DSSPContext) { const { proteinResidues, flags } = ctx - - const turnFlag = [ 0, 0, 0, DSSPType.Flag.T3, DSSPType.Flag.T4, DSSPType.Flag.T5 ] - const helixFlag = [ 0, 0, 0, DSSPType.Flag.G, DSSPType.Flag.H, DSSPType.Flag.I ] + + const turnFlag = [0, 0, 0, DSSPType.Flag.T3, DSSPType.Flag.T4, DSSPType.Flag.T5] + const helixFlag = [0, 0, 0, DSSPType.Flag.G, DSSPType.Flag.H, DSSPType.Flag.I] for (let i = 1, il = proteinResidues.length; i < il; ++i) { const fI = DSSPType.create(flags[i]) @@ -447,7 +447,7 @@ function assignHelices(ctx: DSSPContext) { /** * ladder=: set of one or more consecutive bridges of identical type - * + * * Type: E */ function assignLadders(ctx: DSSPContext) { @@ -456,7 +456,7 @@ function assignLadders(ctx: DSSPContext) { /** * sheet=: set of one or more ladders connected by shared residues - * + * * Type: E */ function assignSheets(ctx: DSSPContext) { @@ -465,7 +465,7 @@ function assignSheets(ctx: DSSPContext) { /** * Bend(i) =: [angle ((CW - Ca(i - 2)),(C"(i + 2) - C"(i))) > 70"] - * + * * Type: S */ function assignBends(ctx: DSSPContext) { diff --git a/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts b/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts index dc882b0efb0991435ff6f0ff618ecdbdf17ca957..110ec57b823b70cada7d330ce14a68677cc18cf3 100644 --- a/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts +++ b/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts @@ -9,10 +9,10 @@ import { StructureQualityReport } from 'mol-model-props/pdbe/structure-quality-r import { StructureQualityReportColorTheme } from 'mol-model-props/pdbe/themes/structure-quality-report'; import { Loci } from 'mol-model/loci'; import { StructureElement } from 'mol-model/structure'; -import { CustomPropertyRegistry } from 'mol-plugin/util/custom-prop-registry'; import { ParamDefinition as PD } from 'mol-util/param-definition'; import { PluginBehavior } from '../../../behavior'; import { ThemeDataContext } from 'mol-theme/theme'; +import { CustomPropertyRegistry } from 'mol-model-props/common/custom-property-registry'; export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: boolean }>({ name: 'pdbe-structure-quality-report-prop', diff --git a/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts b/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts index fc131da26a344fac0b72dc545072c2b8263f50f1..e156dd20d54e7f3b8af6dda004005ba23920e398 100644 --- a/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts +++ b/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts @@ -7,12 +7,12 @@ import { PluginBehavior } from 'mol-plugin/behavior'; import { ParamDefinition as PD } from 'mol-util/param-definition' import { AssemblySymmetry } from 'mol-model-props/rcsb/assembly-symmetry'; -import { CustomPropertyRegistry } from 'mol-plugin/util/custom-prop-registry'; import { AssemblySymmetryClusterColorThemeProvider } from 'mol-model-props/rcsb/themes/assembly-symmetry-cluster'; import { AssemblySymmetryAxesRepresentationProvider } from 'mol-model-props/rcsb/representations/assembly-symmetry-axes'; import { Loci, isDataLoci } from 'mol-model/loci'; import { OrderedSet } from 'mol-data/int'; import { Table } from 'mol-data/db'; +import { CustomPropertyRegistry } from 'mol-model-props/common/custom-property-registry'; export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean }>({ name: 'rcsb-assembly-symmetry-prop', diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 25bd3648cfee27be1f62c3cf5753f41f3c761411..0a3904c8f29753a9e5cd940569fa9ced98a8f7e1 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -24,7 +24,6 @@ import { TaskManager } from './util/task-manager'; import { Color } from 'mol-util/color'; import { LociLabelEntry, LociLabelManager } from './util/loci-label-manager'; import { ajaxGet } from 'mol-util/data-source'; -import { CustomPropertyRegistry } from './util/custom-prop-registry'; import { VolumeRepresentationRegistry } from 'mol-repr/volume/registry'; import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version'; import { PluginLayout } from './layout'; @@ -32,6 +31,7 @@ import { List } from 'immutable'; import { StateTransformParameters } from './ui/state/common'; import { DataFormatRegistry } from './state/actions/volume'; import { PluginBehavior } from './behavior/behavior'; +import { CustomPropertyRegistry } from 'mol-model-props/common/custom-property-registry'; export class PluginContext { private disposed = false; diff --git a/src/mol-state/state/builder.ts b/src/mol-state/state/builder.ts index ec0bab0c3c2e4c245a4c614b39d4fd912adc736a..824cf3561eb8af52533f44bab4c222f581d42669 100644 --- a/src/mol-state/state/builder.ts +++ b/src/mol-state/state/builder.ts @@ -9,6 +9,7 @@ import { TransientTree } from '../tree/transient'; import { StateObject, StateObjectCell } from '../object'; import { StateTransform } from '../transform'; import { StateTransformer } from '../transformer'; +import { State } from 'mol-state/state'; export { StateBuilder } @@ -24,7 +25,7 @@ namespace StateBuilder { lastUpdate?: StateTransform.Ref } - interface State { + interface BuildState { tree: TransientTree, editInfo: EditInfo } @@ -38,7 +39,7 @@ namespace StateBuilder { } export class Root implements StateBuilder { - private state: State; + private state: BuildState; get editInfo() { return this.state.editInfo; } to<A extends StateObject>(ref: StateTransform.Ref) { return new To<A>(this.state, ref, this); } @@ -90,8 +91,30 @@ namespace StateBuilder { return new To(this.state, t.ref, this.root); } + /** + * Updates a transform in an instantiated tree, passing the transform's source into the providers + * + * This only works if the transform source is NOT updated by the builder. Use at own discression. + */ + updateInState<T extends StateTransformer<any, A, any>>(transformer: T, state: State, provider: (old: StateTransformer.Params<T>, a: StateTransformer.From<T>) => StateTransformer.Params<T>): Root { + const old = this.state.tree.transforms.get(this.ref)!; + const cell = state.cells.get(this.ref); + if (!cell || !cell.sourceRef) throw new Error('Source cell is not present in the tree.'); + const parent = state.cells.get(cell.sourceRef); + if (!parent || !parent.obj) throw new Error('Parent cell is not present or computed.'); + + const params = provider(old.params as any, parent.obj as any); + + if (this.state.tree.setParams(this.ref, params)) { + this.editInfo.count++; + this.editInfo.lastUpdate = this.ref; + } + + return this.root; + } + update<T extends StateTransformer<any, A, any>>(transformer: T, params: (old: StateTransformer.Params<T>) => StateTransformer.Params<T>): Root - update(params: any): Root + update<T extends StateTransformer<any, A, any> = StateTransformer<any, A, any>>(params: StateTransformer.Params<T>): Root update<T extends StateTransformer<any, A, any>>(paramsOrTransformer: T, provider?: (old: StateTransformer.Params<T>) => StateTransformer.Params<T>) { let params: any; if (provider) { @@ -115,7 +138,7 @@ namespace StateBuilder { getTree(): StateTree { return this.state.tree.asImmutable(); } - constructor(private state: State, ref: StateTransform.Ref, private root: Root) { + constructor(private state: BuildState, ref: StateTransform.Ref, private root: Root) { this.ref = ref; if (!this.state.tree.transforms.has(ref)) { throw new Error(`Could not find node '${ref}'.`); diff --git a/src/mol-state/state/selection.ts b/src/mol-state/state/selection.ts index b70b4c8d19f817601030faa5d23ec96bd5c0fd10..986dc4467fc670796d1928b4ee4a1731cd0b02e8 100644 --- a/src/mol-state/state/selection.ts +++ b/src/mol-state/state/selection.ts @@ -97,6 +97,14 @@ namespace StateSelection { }); } + export function ofType(type: StateObject.Ctor) { + return build(() => state => { + const ctx = { ret: [] as StateObjectCell[], cells: state.cells, type: type.type }; + StateTree.doPreOrder(state.tree, state.tree.root, ctx, _findOfType); + return ctx.ret; + }); + } + function _findRootsOfType(n: StateTransform, _: any, s: { type: StateObject.Type, roots: StateObjectCell[], cells: State.Cells }) { const cell = s.cells.get(n.ref); if (cell && cell.obj && cell.obj.type === s.type) { @@ -105,6 +113,14 @@ namespace StateSelection { } return true; } + + function _findOfType(n: StateTransform, _: any, s: { type: StateObject.Type, ret: StateObjectCell[], cells: State.Cells }) { + const cell = s.cells.get(n.ref); + if (cell && cell.obj && cell.obj.type === s.type) { + s.ret.push(cell); + } + return true; + } } registerModifier('flatMap', flatMap); diff --git a/src/perf-tests/cif-encoder.ts b/src/perf-tests/cif-encoder.ts index 59f0eb3f62ebb8bd08bd23b26d9c09d7fae96f57..5749ba5a4f1cdf503dfe40e66baeea35a428bbef 100644 --- a/src/perf-tests/cif-encoder.ts +++ b/src/perf-tests/cif-encoder.ts @@ -60,7 +60,7 @@ function testBinary() { enc.writeCategory(getCat('cat2'), [{ rowCount: 1, fields: category2fields }]); enc.encode(); const data = enc.getData() as Uint8Array; - fs.writeFileSync('e:/test/mol-star/test.bcif', new Buffer(data)); + fs.writeFileSync('e:/test/mol-star/test.bcif', Buffer.from(data)); console.log('written binary'); } diff --git a/src/servers/model/preprocess/master.ts b/src/servers/model/preprocess/master.ts index 7dbb2ba39f368911653815b50ec82adbbdf8bedf..365d6f3fa496a60dc1f6c88e9cf3d1221917151e 100644 --- a/src/servers/model/preprocess/master.ts +++ b/src/servers/model/preprocess/master.ts @@ -26,6 +26,7 @@ cmdParser.addArgument(['--folderNumProcesses', '-fp'], { help: 'Convert folder n interface CmdArgs { // bulk?: string, + help?: any, cfg?: string, input?: string, outCIF?: string, @@ -36,6 +37,7 @@ interface CmdArgs { folderNumProcesses?: string } + export interface PreprocessConfig { numProcesses?: number, customProperties?: ModelPropertyProviderConfig | string @@ -43,6 +45,11 @@ export interface PreprocessConfig { const cmdArgs = cmdParser.parseArgs() as CmdArgs; +if (Object.keys(cmdArgs).filter(k => (cmdArgs as any)[k] !== null).length === 0 || typeof cmdArgs.help !== 'undefined') { + cmdParser.printHelp(); + process.exit(0); +} + let entries: PreprocessEntry[] = [] let config: PreprocessConfig = { numProcesses: 1, customProperties: void 0 } diff --git a/src/servers/model/preprocess/preprocess.ts b/src/servers/model/preprocess/preprocess.ts index 187a017b762e0c4bab1993e07ef2d54dc3404604..f36ee59ee64122cdf2f856a7c58960dbec67e06d 100644 --- a/src/servers/model/preprocess/preprocess.ts +++ b/src/servers/model/preprocess/preprocess.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { readStructureWrapper, resolveStructures } from '../server/structure-wrapper'; +import { readStructureWrapper, resolveStructures, readDataAndFrame } from '../server/structure-wrapper'; import { classifyCif } from './converter'; import { Structure } from 'mol-model/structure'; import { CifWriter } from 'mol-io/writer/cif'; @@ -15,7 +15,14 @@ import { ModelPropertiesProvider } from '../property-provider'; // TODO: error handling -export async function preprocessFile(filename: string, propertyProvider?: ModelPropertiesProvider, outputCif?: string, outputBcif?: string) { +export function preprocessFile(filename: string, propertyProvider?: ModelPropertiesProvider, outputCif?: string, outputBcif?: string) { + return propertyProvider + ? preprocess(filename, propertyProvider, outputCif, outputBcif) + : convert(filename, outputCif, outputBcif); +} + + +async function preprocess(filename: string, propertyProvider?: ModelPropertiesProvider, outputCif?: string, outputBcif?: string) { const input = await readStructureWrapper('entry', '_local_', filename, propertyProvider); const categories = await classifyCif(input.cifFrame); const inputStructures = (await resolveStructures(input))!; @@ -36,6 +43,34 @@ export async function preprocessFile(filename: string, propertyProvider?: ModelP } } +async function convert(filename: string, outputCif?: string, outputBcif?: string) { + const { frame } = await readDataAndFrame(filename); + const categories = await classifyCif(frame); + + if (outputCif) { + const writer = wrapFileToWriter(outputCif); + const encoder = CifWriter.createEncoder({ binary: false }); + encodeConvert(frame.header, categories, encoder, writer); + writer.end(); + } + + if (outputBcif) { + const writer = wrapFileToWriter(outputBcif); + const encoder = CifWriter.createEncoder({ binary: true, binaryAutoClassifyEncoding: true }); + encodeConvert(frame.header, categories, encoder, writer); + writer.end(); + } +} + +function encodeConvert(header: string, categories: CifWriter.Category[], encoder: CifWriter.Encoder, writer: Writer) { + encoder.startDataBlock(header); + for (const cat of categories) { + encoder.writeCategory(cat); + } + encoder.encode(); + encoder.writeTo(writer); +} + function encode(structure: Structure, header: string, categories: CifWriter.Category[], encoder: CifWriter.Encoder, exportCtx: CifExportContext, writer: Writer) { const skipCategoryNames = new Set<string>(categories.map(c => c.name)); encoder.startDataBlock(header); diff --git a/src/servers/model/property-provider.ts b/src/servers/model/property-provider.ts index d64e5b4cfbdfd3fd20b10d6d16f844f88715cdc3..77ab34c50f691af4eec5b94f8aef27d5adce1f13 100644 --- a/src/servers/model/property-provider.ts +++ b/src/servers/model/property-provider.ts @@ -18,11 +18,11 @@ export type AttachModelProperty = (args: { model: Model, params: any, cache: any export type AttachModelProperties = (args: { model: Model, params: any, cache: any }) => Promise<any>[] export type ModelPropertiesProvider = (model: Model, cache: any) => Promise<any>[] -export function createModelPropertiesProviderFromConfig(): ModelPropertiesProvider { +export function createModelPropertiesProviderFromConfig() { return createModelPropertiesProvider(Config.customProperties); } -export function createModelPropertiesProvider(configOrPath: ModelPropertyProviderConfig | string | undefined): ModelPropertiesProvider { +export function createModelPropertiesProvider(configOrPath: ModelPropertyProviderConfig | string | undefined): ModelPropertiesProvider | undefined { let config: ModelPropertyProviderConfig; if (typeof configOrPath === 'string') { try { @@ -35,7 +35,7 @@ export function createModelPropertiesProvider(configOrPath: ModelPropertyProvide config = configOrPath!; } - if (!config || !config.sources || config.sources.length === 0) return () => []; + if (!config || !config.sources || config.sources.length === 0) return void 0; const ps: AttachModelProperties[] = []; for (const p of config.sources) { diff --git a/src/servers/model/server/api-local.ts b/src/servers/model/server/api-local.ts index 83fe022eb141e6fbf4ddeeaa30d15781978b19a4..74178f2c8204b35b48591d995b43eb9fd87e9c05 100644 --- a/src/servers/model/server/api-local.ts +++ b/src/servers/model/server/api-local.ts @@ -84,7 +84,7 @@ export function wrapFileToWriter(fn: string) { }, writeBinary(this: any, data: Uint8Array) { this.open(); - fs.writeSync(this.file, new Buffer(data.buffer)); + fs.writeSync(this.file, Buffer.from(data.buffer)); return true; }, writeString(this: any, data: string) { diff --git a/src/servers/model/server/api-web.ts b/src/servers/model/server/api-web.ts index a141dbfef311fc663fe1c8c6fde3cdbb9007aa20..a472a9f3da3d1f54e2c0658808d16dadc4bd71cc 100644 --- a/src/servers/model/server/api-web.ts +++ b/src/servers/model/server/api-web.ts @@ -39,7 +39,7 @@ function wrapResponse(fn: string, res: express.Response) { }, writeBinary(this: any, data: Uint8Array) { if (!this.headerWritten) this.writeHeader(true); - return res.write(new Buffer(data.buffer)); + return res.write(Buffer.from(data.buffer)); }, writeString(this: any, data: string) { if (!this.headerWritten) this.writeHeader(false); diff --git a/src/servers/model/server/query.ts b/src/servers/model/server/query.ts index f1079596487273caec14432f1fbbc0afc7274063..a9cd4c754a90e9d56ab0c23554feee44b589673e 100644 --- a/src/servers/model/server/query.ts +++ b/src/servers/model/server/query.ts @@ -30,7 +30,7 @@ const perf = new PerformanceMonitor(); let _propertyProvider: ModelPropertiesProvider; function propertyProvider() { if (_propertyProvider) return _propertyProvider; - _propertyProvider = createModelPropertiesProviderFromConfig(); + _propertyProvider = createModelPropertiesProviderFromConfig() || (() => []); return _propertyProvider; } @@ -156,7 +156,7 @@ const _model_server_stats_fields: CifField<number, Stats>[] = [ const _model_server_result: CifWriter.Category<Job> = { name: 'model_server_result', - instance: (job) => CifWriter.categoryInstance(_model_server_result_fields,{ data: job, rowCount: 1 }) + instance: (job) => CifWriter.categoryInstance(_model_server_result_fields, { data: job, rowCount: 1 }) }; const _model_server_error: CifWriter.Category<string> = { diff --git a/src/servers/model/server/structure-wrapper.ts b/src/servers/model/server/structure-wrapper.ts index 615e57e758653a9aa27fd43ef896be644b3ed91b..6eef79c02a7d393f4a4a2f1dc3fde726e3e799d0 100644 --- a/src/servers/model/server/structure-wrapper.ts +++ b/src/servers/model/server/structure-wrapper.ts @@ -90,24 +90,30 @@ async function parseCif(data: string|Uint8Array) { return parsed.result; } -export async function readStructureWrapper(key: string, sourceId: string | '_local_', entryId: string, propertyProvider: ModelPropertiesProvider | undefined) { - const filename = sourceId === '_local_' ? entryId : Config.mapFile(sourceId, entryId); - if (!filename) throw new Error(`Cound not map '${key}' to a valid filename.`); - if (!fs.existsSync(filename)) throw new Error(`Could not find source file for '${key}'.`); - +export async function readDataAndFrame(filename: string, key?: string) { perf.start('read'); let data; try { data = await readFile(filename); } catch (e) { - ConsoleLogger.error(key, '' + e); - throw new Error(`Could not read the file for '${key}' from disk.`); + ConsoleLogger.error(key || filename, '' + e); + throw new Error(`Could not read the file for '${key || filename}' from disk.`); } perf.end('read'); perf.start('parse'); const frame = (await parseCif(data)).blocks[0]; perf.end('parse'); + + return { data, frame }; +} + +export async function readStructureWrapper(key: string, sourceId: string | '_local_', entryId: string, propertyProvider: ModelPropertiesProvider | undefined) { + const filename = sourceId === '_local_' ? entryId : Config.mapFile(sourceId, entryId); + if (!filename) throw new Error(`Cound not map '${key}' to a valid filename.`); + if (!fs.existsSync(filename)) throw new Error(`Could not find source file for '${key}'.`); + + const { data, frame } = await readDataAndFrame(filename, key); perf.start('createModel'); const models = await trajectoryFromMmCIF(frame).run(); perf.end('createModel'); diff --git a/src/servers/model/test.ts b/src/servers/model/test.ts index 8d7f85b1e4dcbd354389f57bb6227c346e728608..b250de2ef48f836723eeb758a0c62c1bc08ac847 100644 --- a/src/servers/model/test.ts +++ b/src/servers/model/test.ts @@ -13,7 +13,7 @@ function wrapFile(fn: string) { }, writeBinary(this: any, data: Uint8Array) { this.open(); - fs.writeSync(this.file, new Buffer(data)); + fs.writeSync(this.file, Buffer.from(data)); return true; }, writeString(this: any, data: string) { diff --git a/src/servers/volume/common/binary-schema.ts b/src/servers/volume/common/binary-schema.ts index 3bd0b10cc276d19f6fee996b42e398d0bb473913..cdc72fb51d33c5190dfb511225c11c63538be095 100644 --- a/src/servers/volume/common/binary-schema.ts +++ b/src/servers/volume/common/binary-schema.ts @@ -97,7 +97,7 @@ function writeElement(e: Element, buffer: Buffer, src: any, offset: number) { function write(element: Element, src: any) { const size = byteCount(element, src); - const buffer = new Buffer(size); + const buffer = Buffer.alloc(size); writeElement(element, buffer, src, 0); return buffer; } diff --git a/src/servers/volume/common/file.ts b/src/servers/volume/common/file.ts index c01670aa19473a7ff5f4ffe5981ad2e61e006907..f641faf7e1c47d393f80f1c12389c652ec3f1d35 100644 --- a/src/servers/volume/common/file.ts +++ b/src/servers/volume/common/file.ts @@ -57,7 +57,7 @@ export function createFile(filename: string) { }); } -const smallBuffer = SimpleBuffer.fromBuffer(new Buffer(8)); +const smallBuffer = SimpleBuffer.fromBuffer(Buffer.alloc(8)); export async function writeInt(file: FileHandle, value: number, position: number) { smallBuffer.writeInt32LE(value, 0); await file.writeBuffer(position, smallBuffer, 4); diff --git a/src/servers/volume/pack/main.ts b/src/servers/volume/pack/main.ts index a43bddae2d571cfb11f13b0953e01175d681591f..270682fbeb6f061f1752420102d9f1faa579238a 100644 --- a/src/servers/volume/pack/main.ts +++ b/src/servers/volume/pack/main.ts @@ -40,7 +40,7 @@ function updateAllocationProgress(progress: Data.Progress, progressDone: number) */ async function allocateFile(ctx: Data.Context) { const { totalByteSize, file } = ctx; - const buffer = new Buffer(Math.min(totalByteSize, 8 * 1024 * 1024)); + const buffer = Buffer.alloc(Math.min(totalByteSize, 8 * 1024 * 1024)); const progress: Data.Progress = { current: 0, max: Math.ceil(totalByteSize / buffer.byteLength) }; let written = 0; while (written < totalByteSize) { diff --git a/src/servers/volume/pack/sampling.ts b/src/servers/volume/pack/sampling.ts index 97d77961d06c77960d7b0450831e762e6e374284..299c1fb8cb802b6aeb76faf85bfffe861763802b 100644 --- a/src/servers/volume/pack/sampling.ts +++ b/src/servers/volume/pack/sampling.ts @@ -20,11 +20,11 @@ export async function createContext(filename: string, channels: Format.Context[] const { extent, valueType, grid, origin } = channels[0].data.header; const samplingCounts = getSamplingCounts(extent, blockSize); - const cubeBuffer = new Buffer(new ArrayBuffer(channels.length * blockSize * blockSize * blockSize * getElementByteSize(valueType))); + const cubeBuffer = Buffer.from(new ArrayBuffer(channels.length * blockSize * blockSize * blockSize * getElementByteSize(valueType))); const litteEndianCubeBuffer = SimpleBuffer.IsNativeEndianLittle ? cubeBuffer - : new Buffer(new ArrayBuffer(channels.length * blockSize * blockSize * blockSize * getElementByteSize(valueType))); + : Buffer.from(new ArrayBuffer(channels.length * blockSize * blockSize * blockSize * getElementByteSize(valueType))); // The data can be periodic iff the extent is the same as the grid and origin is 0. if (grid.some((v, i) => v !== extent[i]) || origin.some(v => v !== 0)) { @@ -100,7 +100,7 @@ function createBlockBuffer(sampleCount: number[], blockSize: number, valueType: for (let i = 0; i < numChannels; i++) values[i] = createTypedArray(valueType, sampleCount[0] * sampleCount[1] * blockSize); return { values, - buffers: values.map(xs => new Buffer(xs.buffer)), + buffers: values.map(xs => Buffer.from(xs.buffer)), slicesWritten: 0 }; } diff --git a/src/servers/volume/server/local-api.ts b/src/servers/volume/server/local-api.ts index 8a4a514db1cb3cb4234c4b03e3766e5096d6779b..de87df318ec3e7cf457a92aa932598e2bd7b666b 100644 --- a/src/servers/volume/server/local-api.ts +++ b/src/servers/volume/server/local-api.ts @@ -28,7 +28,7 @@ export interface JobEntry { params: { /** Determines the detail level as specified in server-config */ detail?: number, - /** + /** * Determines the sampling level: * 1: Original data * 2: Downsampled by factor 1/2 @@ -121,7 +121,7 @@ function wrapFile(fn: string) { }, writeBinary(this: any, data: Uint8Array) { this.open(); - fs.writeSync(this.file, new Buffer(data)); + fs.writeSync(this.file, Buffer.from(data)); return true; }, writeString(this: any, data: string) { diff --git a/src/servers/volume/server/web-api.ts b/src/servers/volume/server/web-api.ts index 5040ba702e85fd1c6396f7c3a016fc8295d555eb..28a0b33845f65f6d833217651d0498d8f0624da7 100644 --- a/src/servers/volume/server/web-api.ts +++ b/src/servers/volume/server/web-api.ts @@ -60,7 +60,7 @@ function wrapResponse(fn: string, res: express.Response) { }, writeBinary(this: any, data: Uint8Array) { if (!this.headerWritten) this.writeHeader(true); - return res.write(new Buffer(data.buffer)); + return res.write(Buffer.from(data.buffer)); }, writeString(this: any, data: string) { if (!this.headerWritten) this.writeHeader(false); diff --git a/webpack.config.js b/webpack.config.js index 8271634c5e16fae7baeffaece602852313099714..e3d711da35516237d7e9a559daa50ef8fa032e1f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -88,6 +88,7 @@ module.exports = [ createApp('viewer'), createApp('basic-wrapper'), createNodeApp('state-docs'), + createNodeEntryPoint('preprocess', 'servers/model', 'model-server'), createApp('model-server-query'), createBrowserTest('font-atlas'),