Skip to content
Snippets Groups Projects
Commit ab03b84d authored by Alexander Rose's avatar Alexander Rose
Browse files

removed ply-wrapper wip

parent 845a2be7
No related branches found
No related tags found
No related merge requests found
/**
* 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, ResidueIndex } from 'mol-model/structure';
import { Color } from 'mol-util/color';
const EvolutionaryConservationPalette: Color[] = [
[255, 255, 129], // insufficient
[160, 37, 96], // 9
[240, 125, 171],
[250, 201, 222],
[252, 237, 244],
[255, 255, 255],
[234, 255, 255],
[215, 255, 255],
[140, 255, 255],
[16, 200, 209] // 1
].reverse().map(([r, g, b]) => Color.fromRgb(r, g, b));
const EvolutionaryConservationDefaultColor = Color(0x999999);
export const EvolutionaryConservation = CustomElementProperty.create<number>({
isStatic: true,
name: 'proteopedia-wrapper-evolutionary-conservation',
display: 'Evolutionary Conservation',
async getData(model: Model) {
const id = model.label.toLowerCase();
const req = await fetch(`https://proteopedia.org/cgi-bin/cnsrf?${id}`);
const json = await req.json();
const annotations = (json && json.residueAnnotations) || [];
const conservationMap = new Map<string, number>();
for (const e of annotations) {
for (const r of e.ids) {
conservationMap.set(r, e.annotation);
}
}
const map = new Map<ElementIndex, number>();
const { _rowCount: residueCount } = model.atomicHierarchy.residues;
const { offsets: residueOffsets } = model.atomicHierarchy.residueAtomSegments;
const chainIndex = model.atomicHierarchy.chainAtomSegments.index;
for (let rI = 0 as ResidueIndex; rI < residueCount; rI++) {
const cI = chainIndex[residueOffsets[rI]];
const key = `${model.atomicHierarchy.chains.auth_asym_id.value(cI)} ${model.atomicHierarchy.residues.auth_seq_id.value(rI)}`;
if (!conservationMap.has(key)) continue;
const ann = conservationMap.get(key)!;
for (let aI = residueOffsets[rI]; aI < residueOffsets[rI + 1]; aI++) {
map.set(aI, ann);
}
}
return map;
},
coloring: {
getColor(e: number) {
if (e < 1 || e > 10) return EvolutionaryConservationDefaultColor;
return EvolutionaryConservationPalette[e - 1];
},
defaultColor: EvolutionaryConservationDefaultColor
},
format(e) {
if (e === 10) return `Evolutionary Conservation: InsufficientData`;
return e ? `Evolutionary Conservation: ${e}` : void 0;
}
});
\ No newline at end of file
== v2.0 ==
* Changed how state saving works.
== v1.0 ==
* Initial version.
\ No newline at end of file
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { ResidueIndex, Model } from 'mol-model/structure';
import { BuiltInStructureRepresentationsName } from 'mol-repr/structure/registry';
import { BuiltInColorThemeName } from 'mol-theme/color';
import { AminoAcidNames } from 'mol-model/structure/model/types';
import { PluginContext } from 'mol-plugin/context';
export interface ModelInfo {
hetResidues: { name: string, indices: ResidueIndex[] }[],
assemblies: { id: string, details: string, isPreferred: boolean }[],
preferredAssemblyId: string | undefined
}
export namespace ModelInfo {
async function getPreferredAssembly(ctx: PluginContext, model: Model) {
if (model.label.length <= 3) return void 0;
try {
const id = model.label.toLowerCase();
const src = await ctx.runTask(ctx.fetch({ url: `https://www.ebi.ac.uk/pdbe/api/pdb/entry/summary/${id}` })) as string;
const json = JSON.parse(src);
const data = json && json[id];
const assemblies = data[0] && data[0].assemblies;
if (!assemblies || !assemblies.length) return void 0;
for (const asm of assemblies) {
if (asm.preferred) {
return asm.assembly_id;
}
}
return void 0;
} catch (e) {
console.warn('getPreferredAssembly', e);
}
}
export async function get(ctx: PluginContext, model: Model, checkPreferred: boolean): Promise<ModelInfo> {
const { _rowCount: residueCount } = model.atomicHierarchy.residues;
const { offsets: residueOffsets } = model.atomicHierarchy.residueAtomSegments;
const chainIndex = model.atomicHierarchy.chainAtomSegments.index;
// const resn = SP.residue.label_comp_id, entType = SP.entity.type;
const pref = checkPreferred
? getPreferredAssembly(ctx, model)
: void 0;
const hetResidues: ModelInfo['hetResidues'] = [];
const hetMap = new Map<string, ModelInfo['hetResidues'][0]>();
for (let rI = 0 as ResidueIndex; rI < residueCount; rI++) {
const comp_id = model.atomicHierarchy.residues.label_comp_id.value(rI);
if (AminoAcidNames.has(comp_id)) continue;
const mod_parent = model.properties.modifiedResidues.parentId.get(comp_id);
if (mod_parent && AminoAcidNames.has(mod_parent)) continue;
const cI = chainIndex[residueOffsets[rI]];
const eI = model.atomicHierarchy.index.getEntityFromChain(cI);
if (model.entities.data.type.value(eI) === 'water') continue;
let lig = hetMap.get(comp_id);
if (!lig) {
lig = { name: comp_id, indices: [] };
hetResidues.push(lig);
hetMap.set(comp_id, lig);
}
lig.indices.push(rI);
}
const preferredAssemblyId = await pref;
return {
hetResidues: hetResidues,
assemblies: model.symmetry.assemblies.map(a => ({ id: a.id, details: a.details, isPreferred: a.id === preferredAssemblyId })),
preferredAssemblyId
};
}
}
export type SupportedFormats = 'cif' | 'pdb'
export interface LoadParams {
plyurl: string,
url: string,
format?: SupportedFormats,
assemblyId?: string,
representationStyle?: RepresentationStyle
}
export interface RepresentationStyle {
sequence?: RepresentationStyle.Entry,
hetGroups?: RepresentationStyle.Entry,
water?: RepresentationStyle.Entry
}
export namespace RepresentationStyle {
export type Entry = { kind?: BuiltInStructureRepresentationsName, coloring?: BuiltInColorThemeName }
}
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Mol* PLY Wrapper</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#app {
position: absolute;
left: 160px;
top: 100px;
width: 600px;
height: 400px;
border: 1px solid #ccc;
}
#select {
position: absolute;
left: 10px;
top: 480px;
}
#diagram {
position: absolute;
left: 10px;
top: 520px;
width: 1210px;
height: 510px;
border: 1px solid #ccc;
}
#controls {
position: absolute;
width: 130px;
top: 10px;
left: 10px;
}
#controls > button {
display: block;
width: 100%;
text-align: left;
}
#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>
<link rel="stylesheet" type="text/css" href="/FProject5.3/style.css">
<!--<link rel="stylesheet" type="text/css" href="/FProject5.3/dist/slimselect.css" />-->
</head>
<body>
<script>var aminoAcid = 68</script>
<div id='controls'>
<h3>Source</h3>
<input type='text' id='plyurl' placeholder='plyurl' style='width: 400px' />
<input type='text' id='url' placeholder='url' style='width: 400px' />
<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>
// create an instance of the plugin
var PluginWrapper = new MolStarPLYWrapper();
console.log('Wrapper version', MolStarPLYWrapper.VERSION_MAJOR);
function $(id) { return document.getElementById(id); }
var pdbId = '1tca', assemblyId= 'preferred';
var url = '/test-data/' + pdbId + '_updated.cif';
var format = 'cif';
var plyName = 'run_0_mesh';
var plyurl = '/test-data/' + plyName + '.ply';
$('plyurl').value = plyurl;
$('plyurl').onchange = function (e) { url = e.target.value; }
$('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';
// var assemblyId = 'deposited';
PluginWrapper.init('app' /** or document.getElementById('app') */);
PluginWrapper.setBackground(0xffffff);
PluginWrapper.load({ plyurl: plyurl, url: url, format: format, assemblyId: assemblyId });
PluginWrapper.toggleSpin();
PluginWrapper.events.modelInfo.subscribe(function (info) {
console.log('Model Info', info);
});
addControl('Load Asym Unit', () => PluginWrapper.load({ plyurl: plyurl, url: url, format: format }));
addControl('Load Assembly', () => PluginWrapper.load({ plyurl: plyurl, url: url, format: format, assemblyId: assemblyId }));
addSeparator();
addHeader('Camera');
addControl('Toggle Spin', () => PluginWrapper.toggleSpin());
addSeparator();
addHeader('Animation');
// adjust this number to make the animation faster or slower
// requires to "restart" the animation if changed
PluginWrapper.animate.modelIndex.maxFPS = 30;
addControl('Play To End', () => PluginWrapper.animate.modelIndex.onceForward());
addControl('Play To Start', () => PluginWrapper.animate.modelIndex.onceBackward());
addControl('Play Palindrome', () => PluginWrapper.animate.modelIndex.palindrome());
addControl('Play Loop', () => PluginWrapper.animate.modelIndex.loop());
addControl('Stop', () => PluginWrapper.animate.modelIndex.stop());
addSeparator();
addHeader('Misc');
addControl('Apply Evo Cons', () => PluginWrapper.coloring.evolutionaryConservation());
addControl('Default Visuals', () => PluginWrapper.updateStyle());
addSeparator();
addHeader('State');
var snapshot;
addControl('Create Snapshot', () => {
snapshot = PluginWrapper.snapshot.get();
// could use JSON.stringify(snapshot) and upload the data
});
addControl('Apply Snapshot', () => {
if (!snapshot) return;
PluginWrapper.snapshot.set(snapshot);
// or download snapshot using fetch or ajax or whatever
// or PluginWrapper.snapshot.download(url);
});
////////////////////////////////////////////////////////
function addControl(label, action) {
var btn = document.createElement('button');
btn.onclick = action;
btn.innerText = label;
$('controls').appendChild(btn);
}
function addSeparator() {
var hr = document.createElement('hr');
$('controls').appendChild(hr);
}
function addHeader(header) {
var h = document.createElement('h3');
h.innerText = header;
$('controls').appendChild(h);
}
PluginWrapper.klick;
</script>
<!-- --------- FProject start --------- -->
<select id="select" onchange="iniciar()">
<option value="/FProject5.3/Contact_density/run_0.json">Run 0</option>
<option value="/FProject5.3/Contact_density/run_1.json">Run 1</option>
<option value="/FProject5.3/Contact_density/run_2.json">Run 2</option>
<option value="/FProject5.3/Contact_density/run_3.json">Run 3</option>
<option value="/FProject5.3/Contact_density/run_4.json">Run 4</option>
<option value="/FProject5.3/Contact_density/run_5.json">Run 5</option>
<option value="/FProject5.3/Contact_density/run_6.json">Run 6</option>
<option value="/FProject5.3/Contact_density/run_7.json">Run 7</option>
<option value="/FProject5.3/Contact_density/run_8.json">Run 8</option>
<option value="/FProject5.3/Contact_density/run_9.json">Run 9</option>
<option value="/FProject5.3/Contact_density/run_total.json">Run total</option>
</select>
<div id="diagram">
</div>
<!--
<script>
setTimeout(function() {
new SlimSelect({
select: '#select'
})
}, 300)
</script>
<script src="/FProject5.3/dist/slimselect.min.js"></script>
-->
<!-- load the d3.js library -->
<script src="/FProject5.3/d3.v4.min.js"></script>
<script src="/FProject5.3/jquery-3.3.1.min.js"></script>
<script src="https://d3js.org/d3-path.v1.min.js"></script>
<script src="https://d3js.org/d3-shape.v1.min.js"></script>
<script src="/FProject5.3/scriptv2.js">
</script>
<!-- --------- FProject end --------- -->
</body>
</html>
\ No newline at end of file
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { createPlugin, DefaultPluginSpec } from 'mol-plugin';
import './index.html'
import { PluginContext } from 'mol-plugin/context';
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, PluginStateObject } from 'mol-plugin/state/objects';
import { AnimateModelIndex } from 'mol-plugin/state/animation/built-in';
import {StateBuilder, StateObject} from 'mol-state';
import { EvolutionaryConservation } from './annotation';
import { LoadParams, SupportedFormats, RepresentationStyle, ModelInfo } from './helpers';
import { RxEventHelper } from 'mol-util/rx-event-helper';
import { ControlsWrapper } from './ui/controls';
import { PluginState } from 'mol-plugin/state';
import { Canvas3D } from 'mol-canvas3d/canvas3d';
require('mol-plugin/skin/light.scss')
class MolStarPLYWrapper {
static VERSION_MAJOR = 2;
static VERSION_MINOR = 0;
private _ev = RxEventHelper.create();
readonly events = {
modelInfo: this._ev<ModelInfo>()
};
plugin: PluginContext;
init(target: string | HTMLElement) {
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginSpec,
layout: {
initial: {
isExpanded: false,
showControls: false
},
controls: {
right: ControlsWrapper
}
}
});
this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add(EvolutionaryConservation.Descriptor.name, EvolutionaryConservation.colorTheme!);
this.plugin.lociLabels.addProvider(EvolutionaryConservation.labelProvider);
this.plugin.customModelProperties.register(EvolutionaryConservation.propertyProvider);
}
get state() {
return this.plugin.state.dataState;
}
get klick(){
this.plugin.canvas3d.interaction.click.subscribe(e =>{
console.log('atomID', e)
aminoAcid = 169;
})
return 0
}
private download(b: StateBuilder.To<PSO.Root>, url: string) {
return b.apply(StateTransforms.Data.Download, { url, isBinary: false })
}
private model(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, assemblyId: string) {
const parsed = format === 'cif'
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
return parsed
.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }, { ref: 'model' });
}
private plyData(b: StateBuilder.To<PSO.Data.String>) {
return b.apply(StateTransforms.Data.ParsePly)
.apply(StateTransforms.Model.ShapeFromPly)
.apply(StateTransforms.Representation.ShapeRepresentation3D);
}
private structure(assemblyId: string) {
const model = this.state.build().to('model');
return model
.apply(StateTransforms.Model.CustomModelProperties, { properties: [EvolutionaryConservation.Descriptor.name] }, { ref: 'props', props: { isGhost: false } })
.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' });
}
private visual(ref: string, style?: RepresentationStyle) {
const structure = this.getObj<PluginStateObject.Molecule.Structure>(ref);
if (!structure) return;
const root = this.state.build().to(ref);
root.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: 'sequence' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin,
(style && style.sequence && style.sequence.kind) || 'cartoon',
(style && style.sequence && style.sequence.coloring) || 'unit-index', structure),
{ ref: 'sequence-visual' });
root.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' }, { ref: 'het' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin,
(style && style.hetGroups && style.hetGroups.kind) || 'ball-and-stick',
(style && style.hetGroups && style.hetGroups.coloring), structure),
{ ref: 'het-visual' });
root.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' }, { ref: 'water' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin,
(style && style.water && style.water.kind) || 'ball-and-stick',
(style && style.water && style.water.coloring), structure, { alpha: 0.51 }),
{ ref: 'water-visual' });
return root;
}
private getObj<T extends StateObject>(ref: string): T['data'] {
const state = this.state;
const cell = state.select(ref)[0];
if (!cell || !cell.obj) return void 0;
return (cell.obj as T).data;
}
private async doInfo(checkPreferredAssembly: boolean) {
const model = this.getObj<PluginStateObject.Molecule.Model>('model');
if (!model) return;
const info = await ModelInfo.get(this.plugin, model, checkPreferredAssembly)
this.events.modelInfo.next(info);
return info;
}
private applyState(tree: StateBuilder) {
return PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
}
private loadedParams: LoadParams = { plyurl: '', url: '', format: 'cif', assemblyId: '' };
async load({ plyurl, url, format = 'cif', assemblyId = '', representationStyle }: LoadParams) {
let loadType: 'full' | 'update' = 'full';
const state = this.plugin.state.dataState;
if (this.loadedParams.plyurl !== plyurl || this.loadedParams.url !== url || this.loadedParams.format !== format) {
loadType = 'full';
} else if (this.loadedParams.url === url) {
if (state.select('asm').length > 0) loadType = 'update';
}
if (loadType === 'full') {
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref });
// pdb/cif loading
const modelTree = this.model(this.download(state.build().toRoot(), url), format, assemblyId);
await this.applyState(modelTree);
const info = await this.doInfo(true);
const structureTree = this.structure((assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId);
await this.applyState(structureTree);
// ply loading
const modelTreePly = this.plyData(this.download(state.build().toRoot(), plyurl));
await this.applyState(modelTreePly);
} else {
const tree = state.build();
tree.to('asm').update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' }));
await this.applyState(tree);
}
await this.updateStyle(representationStyle);
this.loadedParams = { plyurl, url, format, assemblyId };
PluginCommands.Camera.Reset.dispatch(this.plugin, { });
}
async updateStyle(style?: RepresentationStyle) {
const tree = this.visual('asm', style);
if (!tree) return;
await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
}
setBackground(color: number) {
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { backgroundColor: Color(color) } });
}
toggleSpin() {
const trackball = this.plugin.canvas3d.props.trackball;
const spinning = trackball.spin;
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
if (!spinning) PluginCommands.Camera.Reset.dispatch(this.plugin, { });
}
animate = {
modelIndex: {
maxFPS: 8,
onceForward: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }) },
onceBackward: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }) },
palindrome: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }) },
loop: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }) },
stop: () => this.plugin.state.animation.stop()
}
}
coloring = {
evolutionaryConservation: async () => {
await this.updateStyle({ sequence: { kind: 'spacefill' } });
const state = this.state;
// const visuals = state.selectQ(q => q.ofType(PluginStateObject.Molecule.Structure.Representation3D).filter(c => c.transform.transformer === StateTransforms.Representation.StructureRepresentation3D));
const tree = state.build();
const colorTheme = { name: EvolutionaryConservation.Descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(EvolutionaryConservation.Descriptor.name).defaultValues };
tree.to('sequence-visual').update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
// for (const v of visuals) {
// }
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
}
}
snapshot = {
get: () => {
return this.plugin.state.getSnapshot();
},
set: (snapshot: PluginState.Snapshot) => {
return this.plugin.state.setSnapshot(snapshot);
},
download: async (url: string) => {
try {
const data = await this.plugin.runTask(this.plugin.fetch({ url }));
const snapshot = JSON.parse(data);
await this.plugin.state.setSnapshot(snapshot);
} catch (e) {
console.log(e);
}
}
}
}
(window as any).MolStarPLYWrapper = MolStarPLYWrapper;
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as React from 'react';
import { PluginUIComponent } from 'mol-plugin/ui/base';
import { CurrentObject } from 'mol-plugin/ui/plugin';
import { AnimationControls } from 'mol-plugin/ui/state/animation';
import { CameraSnapshots } from 'mol-plugin/ui/camera';
export class ControlsWrapper extends PluginUIComponent {
render() {
return <div className='msp-scrollable-container msp-right-controls'>
<CurrentObject />
<AnimationControls />
<CameraSnapshots />
</div>;
}
}
\ No newline at end of file
...@@ -97,7 +97,6 @@ module.exports = [ ...@@ -97,7 +97,6 @@ module.exports = [
createApp('viewer'), createApp('viewer'),
createApp('basic-wrapper'), createApp('basic-wrapper'),
createEntry('examples/proteopedia-wrapper/index', 'examples/proteopedia-wrapper', 'index'), createEntry('examples/proteopedia-wrapper/index', 'examples/proteopedia-wrapper', 'index'),
createEntry('examples/ply-wrapper/index', 'examples/ply-wrapper', 'index'),
createNodeApp('state-docs'), createNodeApp('state-docs'),
createNodeEntryPoint('preprocess', 'servers/model', 'model-server'), createNodeEntryPoint('preprocess', 'servers/model', 'model-server'),
createApp('model-server-query'), createApp('model-server-query'),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment