diff --git a/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts b/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts
index 11282871cf9674cafdbd026e3553e3f972fd3f7c..59385d880d2d13189889c9f268c02cb0bb079b8b 100644
--- a/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts
+++ b/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts
@@ -55,13 +55,27 @@ export namespace VolumeStreaming {
}
export function createParams(data?: VolumeServerInfo.Data, defaultView?: ViewTypes, binding?: typeof DefaultBindings) {
+ const map = new Map<string, VolumeServerInfo.EntryData>()
+ if (data) data.entries.forEach(d => map.set(d.dataId, d))
+ const names = data ? data.entries.map(d => [d.dataId, d.dataId] as [string, string]) : []
+ const defaultKey = data ? data.entries[0].dataId : ''
+ return {
+ entry: PD.Mapped<EntryParams>(defaultKey, names, name => PD.Group(createEntryParams(map.get(name)!, defaultView, data && data.structure))),
+ bindings: PD.Value(binding || DefaultBindings, { isHidden: true }),
+ }
+ }
+
+ export type EntryParamDefinition = typeof createEntryParams extends (...args: any[]) => (infer T) ? T : never
+ export type EntryParams = EntryParamDefinition extends PD.Params ? PD.Values<EntryParamDefinition> : {}
+
+ export function createEntryParams(entryData?: VolumeServerInfo.EntryData, defaultView?: ViewTypes, structure?: Structure) {
// fake the info
- const info = data || { kind: 'em', header: { sampling: [fakeSampling], availablePrecisions: [{ precision: 0, maxVoxels: 0 }] }, emDefaultContourLevel: VolumeIsoValue.relative(0) };
- const box = (data && data.structure.boundary.box) || Box3D.empty();
+ const info = entryData || { kind: 'em', header: { sampling: [fakeSampling], availablePrecisions: [{ precision: 0, maxVoxels: 0 }] }, emDefaultContourLevel: VolumeIsoValue.relative(0) };
+ const box = (structure && structure.boundary.box) || Box3D.empty();
return {
view: PD.MappedStatic(defaultView || (info.kind === 'em' ? 'cell' : 'selection-box'), {
- 'off': PD.Group({}),
+ 'off': PD.Group<{}>({}),
'box': PD.Group({
bottomLeft: PD.Vec3(box.min),
topRight: PD.Vec3(box.max),
@@ -85,7 +99,6 @@ export namespace VolumeStreaming {
'fo-fc(+ve)': channelParam('Fo-Fc(+ve)', Color(0x33BB33), VolumeIsoValue.relative(3), info.header.sampling[0].valuesInfo[1]),
'fo-fc(-ve)': channelParam('Fo-Fc(-ve)', Color(0xBB3333), VolumeIsoValue.relative(-3), info.header.sampling[0].valuesInfo[1]),
}, { isFlat: true }),
- bindings: PD.Value(binding || DefaultBindings, { isHidden: true }),
};
}
@@ -118,11 +131,16 @@ export namespace VolumeStreaming {
public params: Params = {} as any;
private lastLoci: StructureElement.Loci | EmptyLoci = EmptyLoci;
private ref: string = '';
+ public infoMap: Map<string, VolumeServerInfo.EntryData>
channels: Channels = {}
+ public get info () {
+ return this.infoMap.get(this.params.entry.name)!
+ }
+
private async queryData(box?: Box3D) {
- let url = urlCombine(this.info.serverUrl, `${this.info.kind}/${this.info.dataId.toLowerCase()}`);
+ let url = urlCombine(this.data.serverUrl, `${this.info.kind}/${this.info.dataId.toLowerCase()}`);
if (box) {
const { min: a, max: b } = box;
@@ -132,7 +150,7 @@ export namespace VolumeStreaming {
} else {
url += `/cell`;
}
- url += `?detail=${this.params.detailLevel}`;
+ url += `?detail=${this.params.entry.params.detailLevel}`;
let data = LRUCache.get(this.cache, url);
if (data) {
@@ -165,24 +183,30 @@ export namespace VolumeStreaming {
const block = parsed.result.blocks[i];
const densityServerCif = CIF.schema.densityServer(block);
- const volume = await this.plugin.runTask(await volumeFromDensityServerData(densityServerCif));
+ const volume = await this.plugin.runTask(volumeFromDensityServerData(densityServerCif));
(ret as any)[block.header as any] = volume;
}
return ret;
}
private updateDynamicBox(box: Box3D) {
- if (this.params.view.name !== 'selection-box') return;
+ if (this.params.entry.params.view.name !== 'selection-box') return;
const state = this.plugin.state.dataState;
const newParams: Params = {
...this.params,
- view: {
- name: 'selection-box' as 'selection-box',
+ entry: {
+ name: this.params.entry.name,
params: {
- radius: this.params.view.params.radius,
- bottomLeft: box.min,
- topRight: box.max
+ ...this.params.entry.params,
+ view: {
+ name: 'selection-box' as 'selection-box',
+ params: {
+ radius: this.params.entry.params.view.params.radius,
+ bottomLeft: box.min,
+ topRight: box.max
+ }
+ }
}
}
};
@@ -214,7 +238,7 @@ export namespace VolumeStreaming {
this.subscribeObservable(this.plugin.behaviors.interaction.click, ({ current, buttons, modifiers }) => {
if (!Binding.match((this.params.bindings && this.params.bindings.clickVolumeAroundOnly) || DefaultBindings.clickVolumeAroundOnly, buttons, modifiers)) return;
- if (this.params.view.name !== 'selection-box') {
+ if (this.params.entry.params.view.name !== 'selection-box') {
this.lastLoci = this.getNormalizedLoci(current.loci);
} else {
this.updateInteraction(current);
@@ -272,34 +296,34 @@ export namespace VolumeStreaming {
}
async update(params: Params) {
- const switchedToSelection = params.view.name === 'selection-box' && this.params && this.params.view && this.params.view.name !== 'selection-box';
+ const switchedToSelection = params.entry.params.view.name === 'selection-box' && this.params && this.params.entry && this.params.entry.params && this.params.entry.params.view && this.params.entry.params.view.name !== 'selection-box';
this.params = params;
let box: Box3D | undefined = void 0, emptyData = false;
- switch (params.view.name) {
+ switch (params.entry.params.view.name) {
case 'off':
emptyData = true;
break;
case 'box':
- box = Box3D.create(params.view.params.bottomLeft, params.view.params.topRight);
+ box = Box3D.create(params.entry.params.view.params.bottomLeft, params.entry.params.view.params.topRight);
emptyData = Box3D.volume(box) < 0.0001;
break;
case 'selection-box': {
if (switchedToSelection) {
box = this.getBoxFromLoci(this.lastLoci) || Box3D.empty();
} else {
- box = Box3D.create(Vec3.clone(params.view.params.bottomLeft), Vec3.clone(params.view.params.topRight));
+ box = Box3D.create(Vec3.clone(params.entry.params.view.params.bottomLeft), Vec3.clone(params.entry.params.view.params.topRight));
}
- const r = params.view.params.radius;
+ const r = params.entry.params.view.params.radius;
emptyData = Box3D.volume(box) < 0.0001;
Box3D.expand(box, box, Vec3.create(r, r, r));
break;
}
case 'cell':
box = this.info.kind === 'x-ray'
- ? this.info.structure.boundary.box
+ ? this.data.structure.boundary.box
: void 0;
break;
}
@@ -308,7 +332,7 @@ export namespace VolumeStreaming {
if (!data) return false;
- const info = params.channels as ChannelsInfo;
+ const info = params.entry.params.channels as ChannelsInfo;
if (this.info.kind === 'x-ray') {
this.channels['2fo-fc'] = this.createChannel(data['2FO-FC'] || VolumeData.One, info['2fo-fc'], this.info.header.sampling[0].valuesInfo[0]);
@@ -333,14 +357,17 @@ export namespace VolumeStreaming {
}
getDescription() {
- if (this.params.view.name === 'selection-box') return 'Selection';
- if (this.params.view.name === 'box') return 'Static Box';
- if (this.params.view.name === 'cell') return 'Cell';
+ if (this.params.entry.params.view.name === 'selection-box') return 'Selection';
+ if (this.params.entry.params.view.name === 'box') return 'Static Box';
+ if (this.params.entry.params.view.name === 'cell') return 'Cell';
return '';
}
- constructor(public plugin: PluginContext, public info: VolumeServerInfo.Data) {
+ constructor(public plugin: PluginContext, public data: VolumeServerInfo.Data) {
super(plugin, {} as any);
+
+ this.infoMap = new Map<string, VolumeServerInfo.EntryData>()
+ this.data.entries.forEach(info => this.infoMap.set(info.dataId, info))
}
}
}
\ No newline at end of file
diff --git a/src/mol-plugin/behavior/dynamic/volume-streaming/model.ts b/src/mol-plugin/behavior/dynamic/volume-streaming/model.ts
index 213f0b1a9911dd4c57818d31113eb9ec9b4180ce..71a0fac83b6405862e96bfa91e1905e7ed925a06 100644
--- a/src/mol-plugin/behavior/dynamic/volume-streaming/model.ts
+++ b/src/mol-plugin/behavior/dynamic/volume-streaming/model.ts
@@ -2,6 +2,7 @@
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { PluginStateObject } from '../../../state/objects';
@@ -12,13 +13,16 @@ export class VolumeServerInfo extends PluginStateObject.Create<VolumeServerInfo.
export namespace VolumeServerInfo {
export type Kind = 'x-ray' | 'em'
- export interface Data {
- serverUrl: string,
+ export interface EntryData {
kind: Kind,
// for em, the EMDB access code, for x-ray, the PDB id
dataId: string,
header: VolumeServerHeader,
emDefaultContourLevel?: VolumeIsoValue,
+ }
+ export interface Data {
+ serverUrl: string,
+ entries: EntryData[],
structure: Structure
}
}
diff --git a/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts b/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts
index 0dd7284e7599ece28cd25786efda08053e1d6cce..505956c1969d371aaadce8cbcebea2ab85530ead 100644
--- a/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts
+++ b/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts
@@ -14,7 +14,7 @@ import { urlCombine } from '../../../../mol-util/url';
import { createIsoValueParam } from '../../../../mol-repr/volume/isosurface';
import { VolumeIsoValue } from '../../../../mol-model/volume';
import { StateAction, StateObject, StateTransformer } from '../../../../mol-state';
-import { getStreamingMethod, getId, getContourLevel, getEmdbId } from './util';
+import { getStreamingMethod, getIds, getContourLevel, getEmdbIds } from './util';
import { VolumeStreaming } from './behavior';
import { VolumeRepresentation3DHelpers } from '../../../../mol-plugin/state/transforms/representation';
import { BuiltInVolumeRepresentations } from '../../../../mol-repr/volume/registry';
@@ -22,15 +22,24 @@ import { createTheme } from '../../../../mol-theme/theme';
import { Box3D } from '../../../../mol-math/geometry';
import { Vec3 } from '../../../../mol-math/linear-algebra';
+function addEntry(entries: InfoEntryProps[], method: VolumeServerInfo.Kind, dataId: string, emDefaultContourLevel: number) {
+ entries.push({
+ source: method === 'em'
+ ? { name: 'em', params: { isoValue: VolumeIsoValue.absolute(emDefaultContourLevel || 0) } }
+ : { name: 'x-ray', params: { } },
+ dataId
+ })
+}
+
export const InitVolumeStreaming = StateAction.build({
display: { name: 'Volume Streaming' },
from: SO.Molecule.Structure,
params(a) {
const method = getStreamingMethod(a && a.data);
- const id = getId(a && a.data);
+ const ids = getIds(method, a && a.data);
return {
method: PD.Select<VolumeServerInfo.Kind>(method, [['em', 'EM'], ['x-ray', 'X-Ray']]),
- id: PD.Text(id),
+ entries: PD.ObjectList({ id: PD.Text(ids[0] || '') }, ({ id }) => id, { defaultValue: ids.map(id => ({ id })) }),
serverUrl: PD.Text('https://ds.litemol.org'),
defaultView: PD.Select<VolumeStreaming.ViewTypes>(method === 'em' ? 'cell' : 'selection-box', VolumeStreaming.ViewTypeOptions as any),
behaviorRef: PD.Text('', { isHidden: true }),
@@ -40,23 +49,35 @@ export const InitVolumeStreaming = StateAction.build({
},
isApplicable: (a) => a.data.models.length === 1
})(({ ref, state, params }, plugin: PluginContext) => Task.create('Volume Streaming', async taskCtx => {
- let dataId = params.id.toLowerCase(), emDefaultContourLevel: number | undefined;
- if (params.method === 'em') {
- await taskCtx.update('Getting EMDB info...');
- if (!dataId.toUpperCase().startsWith('EMD')) {
- dataId = await getEmdbId(plugin, taskCtx, dataId)
+ const entries: InfoEntryProps[] = []
+
+ for (let i = 0, il = params.entries.length; i < il; ++i) {
+ let dataId = params.entries[i].id.toLowerCase()
+ let emDefaultContourLevel: number | undefined;
+
+ if (params.method === 'em') {
+ // if pdb ids are given for method 'em', get corresponding emd ids
+ // and continue the loop
+ if (!dataId.toUpperCase().startsWith('EMD')) {
+ await taskCtx.update('Getting EMDB info...');
+ const emdbIds = await getEmdbIds(plugin, taskCtx, dataId)
+ for (let j = 0, jl = emdbIds.length; j < jl; ++j) {
+ const emdbId = emdbIds[j]
+ const contourLevel = await getContourLevel(params.emContourProvider, plugin, taskCtx, emdbId)
+ addEntry(entries, params.method, emdbId, contourLevel || 0)
+ }
+ continue;
+ }
+ emDefaultContourLevel = await getContourLevel(params.emContourProvider, plugin, taskCtx, dataId);
}
- const contourLevel = await getContourLevel(params.emContourProvider, plugin, taskCtx, dataId);
- emDefaultContourLevel = contourLevel || 0;
+
+ addEntry(entries, params.method, dataId, emDefaultContourLevel || 0)
}
const infoTree = state.build().to(ref)
.apply(CreateVolumeStreamingInfo, {
serverUrl: params.serverUrl,
- source: params.method === 'em'
- ? { name: 'em', params: { isoValue: VolumeIsoValue.absolute(emDefaultContourLevel || 0) } }
- : { name: 'x-ray', params: { } },
- dataId
+ entries
});
const infoObj = await state.updateTree(infoTree).runInContext(taskCtx);
@@ -78,26 +99,43 @@ export const InitVolumeStreaming = StateAction.build({
export const BoxifyVolumeStreaming = StateAction.build({
display: { name: 'Boxify Volume Streaming', description: 'Make the current box permanent.' },
from: VolumeStreaming,
- isApplicable: (a) => a.data.params.view.name === 'selection-box'
+ isApplicable: (a) => a.data.params.entry.params.view.name === 'selection-box'
})(({ a, ref, state }, plugin: PluginContext) => {
const params = a.data.params;
- if (params.view.name !== 'selection-box') return;
- const box = Box3D.create(Vec3.clone(params.view.params.bottomLeft), Vec3.clone(params.view.params.topRight));
- const r = params.view.params.radius;
+ if (params.entry.params.view.name !== 'selection-box') return;
+ const box = Box3D.create(Vec3.clone(params.entry.params.view.params.bottomLeft), Vec3.clone(params.entry.params.view.params.topRight));
+ const r = params.entry.params.view.params.radius;
Box3D.expand(box, box, Vec3.create(r, r, r));
const newParams: VolumeStreaming.Params = {
...params,
- view: {
- name: 'box' as 'box',
+ entry: {
+ name: params.entry.name,
params: {
- bottomLeft: box.min,
- topRight: box.max
+ ...params.entry.params,
+ view: {
+ name: 'box' as 'box',
+ params: {
+ bottomLeft: box.min,
+ topRight: box.max
+ }
+ }
}
}
};
return state.updateTree(state.build().to(ref).update(newParams));
});
+const InfoEntryParams = {
+ dataId: PD.Text(''),
+ source: PD.MappedStatic('x-ray', {
+ 'em': PD.Group({
+ isoValue: createIsoValueParam(VolumeIsoValue.relative(1))
+ }),
+ 'x-ray': PD.Group({ })
+ })
+}
+type InfoEntryProps = PD.Values<typeof InfoEntryParams>
+
export { CreateVolumeStreamingInfo }
type CreateVolumeStreamingInfo = typeof CreateVolumeStreamingInfo
const CreateVolumeStreamingInfo = PluginStateTransform.BuiltIn({
@@ -108,30 +146,34 @@ const CreateVolumeStreamingInfo = PluginStateTransform.BuiltIn({
params(a) {
return {
serverUrl: PD.Text('https://ds.litemol.org'),
- source: PD.MappedStatic('x-ray', {
- 'em': PD.Group({
- isoValue: createIsoValueParam(VolumeIsoValue.relative(1))
- }),
- 'x-ray': PD.Group({ })
+ entries: PD.ObjectList<InfoEntryProps>(InfoEntryParams, ({ dataId }) => dataId, {
+ defaultValue: [{ dataId: '', source: { name: 'x-ray', params: {} } }]
}),
- dataId: PD.Text('')
};
}
})({
apply: ({ a, params }, plugin: PluginContext) => Task.create('', async taskCtx => {
- const dataId = params.dataId;
- const emDefaultContourLevel = params.source.name === 'em' ? params.source.params.isoValue : VolumeIsoValue.relative(1);
- await taskCtx.update('Getting server header...');
- const header = await plugin.fetch<VolumeServerHeader>({ url: urlCombine(params.serverUrl, `${params.source.name}/${dataId.toLocaleLowerCase()}`), type: 'json' }).runInContext(taskCtx);
+ const entries: VolumeServerInfo.EntryData[] = []
+ for (let i = 0, il = params.entries.length; i < il; ++i) {
+ const e = params.entries[i]
+ const dataId = e.dataId;
+ const emDefaultContourLevel = e.source.name === 'em' ? e.source.params.isoValue : VolumeIsoValue.relative(1);
+ await taskCtx.update('Getting server header...');
+ const header = await plugin.fetch<VolumeServerHeader>({ url: urlCombine(params.serverUrl, `${e.source.name}/${dataId.toLocaleLowerCase()}`), type: 'json' }).runInContext(taskCtx);
+ entries.push({
+ dataId,
+ kind: e.source.name,
+ header,
+ emDefaultContourLevel
+ })
+ }
+
const data: VolumeServerInfo.Data = {
serverUrl: params.serverUrl,
- dataId,
- kind: params.source.name,
- header,
- emDefaultContourLevel,
+ entries,
structure: a.data
};
- return new VolumeServerInfo(data, { label: `Volume Server: ${dataId}` });
+ return new VolumeServerInfo(data, { label: 'Volume Server', description: `${entries.map(e => e.dataId). join(', ')}` });
})
});
@@ -147,17 +189,25 @@ const CreateVolumeStreamingBehavior = PluginStateTransform.BuiltIn({
}
})({
canAutoUpdate: ({ oldParams, newParams }) => {
- return oldParams.view === newParams.view
- || newParams.view.name === 'selection-box'
- || newParams.view.name === 'off';
+ return oldParams.entry.params.view === newParams.entry.params.view
+ || newParams.entry.params.view.name === 'selection-box'
+ || newParams.entry.params.view.name === 'off';
},
apply: ({ a, params }, plugin: PluginContext) => Task.create('Volume streaming', async _ => {
const behavior = new VolumeStreaming.Behavior(plugin, a.data);
await behavior.update(params);
return new VolumeStreaming(behavior, { label: 'Volume Streaming', description: behavior.getDescription() });
}),
- update({ b, newParams }) {
+ update({ a, b, oldParams, newParams }) {
return Task.create('Update Volume Streaming', async _ => {
+ if (oldParams.entry.name !== newParams.entry.name) {
+ if ('em' in newParams.entry.params.channels) {
+ const { emDefaultContourLevel } = b.data.infoMap.get(newParams.entry.name)!
+ if (emDefaultContourLevel) {
+ newParams.entry.params.channels['em'].isoValue = emDefaultContourLevel
+ }
+ }
+ }
const ret = await b.data.update(newParams) ? StateTransformer.UpdateResult.Updated : StateTransformer.UpdateResult.Unchanged;
b.description = b.data.getDescription();
return ret;
diff --git a/src/mol-plugin/behavior/dynamic/volume-streaming/util.ts b/src/mol-plugin/behavior/dynamic/volume-streaming/util.ts
index 2e1c5aa4963a47ff49e16f360b34809e802bb144..9ad445c750b1b194f9acfba30b70a3627f587d5a 100644
--- a/src/mol-plugin/behavior/dynamic/volume-streaming/util.ts
+++ b/src/mol-plugin/behavior/dynamic/volume-streaming/util.ts
@@ -5,7 +5,7 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
-import { Structure } from '../../../../mol-model/structure';
+import { Structure, Model } from '../../../../mol-model/structure';
import { VolumeServerInfo } from './model';
import { PluginContext } from '../../../../mol-plugin/context';
import { RuntimeContext } from '../../../../mol-task';
@@ -25,20 +25,34 @@ export function getStreamingMethod(s?: Structure, defaultKind: VolumeServerInfo.
return 'x-ray';
}
-export function getId(s?: Structure): string {
- if (!s) return ''
+/** Returns EMD ID when available, otherwise falls back to PDB ID */
+export function getEmIds(model: Model): string[] {
+ const ids: string[] = []
+ if (model.sourceData.kind !== 'mmCIF') return [ model.entryId ]
- const model = s.models[0]
- if (model.sourceData.kind !== 'mmCIF') return ''
+ const { db_id, db_name, content_type } = model.sourceData.data.pdbx_database_related
+ if (!db_name.isDefined) return [ model.entryId ]
- const d = model.sourceData.data
- for (let i = 0, il = d.pdbx_database_related._rowCount; i < il; ++i) {
- if (d.pdbx_database_related.db_name.value(i).toUpperCase() === 'EMDB') {
- return d.pdbx_database_related.db_id.value(i)
+ for (let i = 0, il = db_name.rowCount; i < il; ++i) {
+ if (db_name.value(i).toUpperCase() === 'EMDB' && content_type.value(i) === 'associated EM volume') {
+ ids.push(db_id.value(i))
}
}
- return s.models.length > 0 ? s.models[0].entryId : ''
+ return ids
+}
+
+export function getXrayIds(model: Model): string[] {
+ return [ model.entryId ]
+}
+
+export function getIds(method: VolumeServerInfo.Kind, s?: Structure): string[] {
+ if (!s || !s.models.length) return []
+ const model = s.models[0]
+ switch (method) {
+ case 'em': return getEmIds(model)
+ case 'x-ray': return getXrayIds(model)
+ }
}
export async function getContourLevel(provider: 'wwpdb' | 'pdbe', plugin: PluginContext, taskCtx: RuntimeContext, emdbId: string) {
@@ -71,21 +85,21 @@ export async function getContourLevelPdbe(plugin: PluginContext, taskCtx: Runtim
return contourLevel;
}
-export async function getEmdbId(plugin: PluginContext, taskCtx: RuntimeContext, pdbId: string) {
+export async function getEmdbIds(plugin: PluginContext, taskCtx: RuntimeContext, pdbId: string) {
// TODO: parametrize to a differnt URL? in plugin settings perhaps
const summary = await plugin.fetch({ url: `https://www.ebi.ac.uk/pdbe/api/pdb/entry/summary/${pdbId}`, type: 'json' }).runInContext(taskCtx);
const summaryEntry = summary && summary[pdbId];
- let emdbId: string;
+ let emdbIds: string[] = [];
if (summaryEntry && summaryEntry[0] && summaryEntry[0].related_structures) {
- const emdb = summaryEntry[0].related_structures.filter((s: any) => s.resource === 'EMDB');
+ const emdb = summaryEntry[0].related_structures.filter((s: any) => s.resource === 'EMDB' && s.relationship === 'associated EM volume');
if (!emdb.length) {
throw new Error(`No related EMDB entry found for '${pdbId}'.`);
}
- emdbId = emdb[0].accession;
+ emdbIds.push(...emdb.map((e: { accession: string }) => e.accession));
} else {
throw new Error(`No related EMDB entry found for '${pdbId}'.`);
}
- return emdbId
+ return emdbIds
}
\ No newline at end of file
diff --git a/src/mol-plugin/ui/custom/volume.tsx b/src/mol-plugin/ui/custom/volume.tsx
index 2273f16dedc980fc5e62db57fce45b8a8a5997aa..e4292185e7753c9b33a88c3851de28aae362b91c 100644
--- a/src/mol-plugin/ui/custom/volume.tsx
+++ b/src/mol-plugin/ui/custom/volume.tsx
@@ -61,14 +61,20 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
}
changeIso = (name: string, value: number, isRelative: boolean) => {
- const old = this.props.params;
+ const old = this.props.params as VolumeStreaming.Params
this.newParams({
...old,
- channels: {
- ...old.channels,
- [name]: {
- ...old.channels[name],
- isoValue: isRelative ? VolumeIsoValue.relative(value) : VolumeIsoValue.absolute(value)
+ entry: {
+ name: old.entry.name,
+ params: {
+ ...old.entry.params,
+ channels: {
+ ...old.entry.params.channels,
+ [name]: {
+ ...(old.entry.params.channels as any)[name],
+ isoValue: isRelative ? VolumeIsoValue.relative(value) : VolumeIsoValue.absolute(value)
+ }
+ }
}
}
});
@@ -78,11 +84,17 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
const old = this.props.params;
this.newParams({
...old,
- channels: {
- ...old.channels,
- [name]: {
- ...old.channels[name],
- [param]: value
+ entry: {
+ name: old.entry.name,
+ params: {
+ ...old.entry.params,
+ channels: {
+ ...old.entry.params.channels,
+ [name]: {
+ ...(old.entry.params.channels as any)[name],
+ [param]: value
+ }
+ }
}
}
});
@@ -94,41 +106,62 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
: VolumeIsoValue.toAbsolute(channel.isoValue, stats) }
}
- changeOption: ParamOnChange = ({ value }) => {
- const b = (this.props.b as VolumeStreaming).data;
- const isEM = b.info.kind === 'em';
+ changeOption: ParamOnChange = ({ name, value }) => {
+ const old = this.props.params as VolumeStreaming.Params
- const isRelative = value.params.isRelative;
- const sampling = b.info.header.sampling[0];
- const old = this.props.params as VolumeStreaming.Params, oldChannels = old.channels as any;
-
- const oldView = old.view.name === value.name
- ? old.view.params
- : (this.props.info.params as VolumeStreaming.ParamDefinition).view.map(value.name).defaultValue;
-
- const viewParams = { ...oldView };
- if (value.name === 'selection-box') {
- viewParams.radius = value.params.radius;
- } else if (value.name === 'box') {
- viewParams.bottomLeft = value.params.bottomLeft;
- viewParams.topRight = value.params.topRight;
- }
+ if (name === 'entry') {
+ this.newParams({
+ ...old,
+ entry: {
+ name: value,
+ params: old.entry.params,
+ }
+ });
+ } else {
+ const b = (this.props.b as VolumeStreaming).data;
+ const isEM = b.info.kind === 'em';
+
+ const isRelative = value.params.isRelative;
+ const sampling = b.info.header.sampling[0];
+ const oldChannels = old.entry.params.channels as any;
+
+ const oldView = old.entry.params.view.name === value.name
+ ? old.entry.params.view.params
+ : (((this.props.info.params as VolumeStreaming.ParamDefinition)
+ .entry.map(old.entry.name) as PD.Group<VolumeStreaming.EntryParamDefinition>)
+ .params as VolumeStreaming.EntryParamDefinition)
+ .view.map(value.name).defaultValue;
+
+ const viewParams = { ...oldView };
+ if (value.name === 'selection-box') {
+ viewParams.radius = value.params.radius;
+ } else if (value.name === 'box') {
+ viewParams.bottomLeft = value.params.bottomLeft;
+ viewParams.topRight = value.params.topRight;
+ }
- this.newParams({
- ...old,
- view: {
- name: value.name,
- params: viewParams
- },
- detailLevel: value.params.detailLevel,
- channels: isEM
- ? { em: this.convert(oldChannels.em, sampling.valuesInfo[0], isRelative) }
- : {
- '2fo-fc': this.convert(oldChannels['2fo-fc'], sampling.valuesInfo[0], isRelative),
- 'fo-fc(+ve)': this.convert(oldChannels['fo-fc(+ve)'], sampling.valuesInfo[1], isRelative),
- 'fo-fc(-ve)': this.convert(oldChannels['fo-fc(-ve)'], sampling.valuesInfo[1], isRelative)
+ this.newParams({
+ ...old,
+ entry: {
+ name: old.entry.name,
+ params: {
+ ...old.entry.params,
+ view: {
+ name: value.name,
+ params: viewParams
+ },
+ detailLevel: value.params.detailLevel,
+ channels: isEM
+ ? { em: this.convert(oldChannels.em, sampling.valuesInfo[0], isRelative) }
+ : {
+ '2fo-fc': this.convert(oldChannels['2fo-fc'], sampling.valuesInfo[0], isRelative),
+ 'fo-fc(+ve)': this.convert(oldChannels['fo-fc(+ve)'], sampling.valuesInfo[1], isRelative),
+ 'fo-fc(-ve)': this.convert(oldChannels['fo-fc(-ve)'], sampling.valuesInfo[1], isRelative)
+ }
+ }
}
- });
+ });
+ }
};
render() {
@@ -139,50 +172,54 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
const pivot = isEM ? 'em' : '2fo-fc';
const params = this.props.params as VolumeStreaming.Params;
- const isRelative = ((params.channels as any)[pivot].isoValue as VolumeIsoValue).kind === 'relative';
+ const detailLevel = ((this.props.info.params as VolumeStreaming.ParamDefinition)
+ .entry.map(params.entry.name) as PD.Group<VolumeStreaming.EntryParamDefinition>).params.detailLevel
+ const isRelative = ((params.entry.params.channels as any)[pivot].isoValue as VolumeIsoValue).kind === 'relative';
const sampling = b.info.header.sampling[0];
// TODO: factor common things out
const OptionsParams = {
- view: PD.MappedStatic(params.view.name, {
+ entry: PD.Select(params.entry.name, b.data.entries.map(info => [info.dataId, info.dataId] as [string, string])),
+ view: PD.MappedStatic(params.entry.params.view.name, {
'off': PD.Group({}, { description: 'Display off.' }),
'box': PD.Group({
bottomLeft: PD.Vec3(Vec3.zero()),
topRight: PD.Vec3(Vec3.zero()),
- detailLevel: this.props.info.params.detailLevel,
+ detailLevel,
isRelative: PD.Boolean(isRelative, { description: 'Use relative or absolute iso values.' })
}, { description: 'Static box defined by cartesian coords.' }),
'selection-box': PD.Group({
radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }),
- detailLevel: this.props.info.params.detailLevel,
+ detailLevel,
isRelative: PD.Boolean(isRelative, { description: 'Use relative or absolute iso values.' })
}, { description: 'Box around last-interacted element.' }),
'cell': PD.Group({
- detailLevel: this.props.info.params.detailLevel,
+ detailLevel,
isRelative: PD.Boolean(isRelative, { description: 'Use relative or absolute iso values.' })
}, { description: 'Box around the structure\'s bounding box.' }),
// 'auto': PD.Group({ }), // TODO based on camera distance/active selection/whatever, show whole structure or slice.
}, { options: [['off', 'Off'], ['box', 'Bounded Box'], ['selection-box', 'Surroundings'], ['cell', 'Whole Structure']] })
};
const options = {
+ entry: params.entry.name,
view: {
- name: params.view.name,
+ name: params.entry.params.view.name,
params: {
- detailLevel: params.detailLevel,
- radius: (params.view.params as any).radius,
- bottomLeft: (params.view.params as any).bottomLeft,
- topRight: (params.view.params as any).topRight,
+ detailLevel: params.entry.params.detailLevel,
+ radius: (params.entry.params.view.params as any).radius,
+ bottomLeft: (params.entry.params.view.params as any).bottomLeft,
+ topRight: (params.entry.params.view.params as any).topRight,
isRelative
}
}
};
return <>
- {!isEM && <Channel label='2Fo-Fc' name='2fo-fc' channels={params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[0]} />}
- {!isEM && <Channel label='Fo-Fc(+ve)' name='fo-fc(+ve)' channels={params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[1]} />}
- {!isEM && <Channel label='Fo-Fc(-ve)' name='fo-fc(-ve)' channels={params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[1]} />}
- {isEM && <Channel label='EM' name='em' channels={params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[0]} />}
+ {!isEM && <Channel label='2Fo-Fc' name='2fo-fc' channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[0]} />}
+ {!isEM && <Channel label='Fo-Fc(+ve)' name='fo-fc(+ve)' channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[1]} />}
+ {!isEM && <Channel label='Fo-Fc(-ve)' name='fo-fc(-ve)' channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[1]} />}
+ {isEM && <Channel label='EM' name='em' channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[0]} />}
<ParameterControls onChange={this.changeOption} params={OptionsParams} values={options} onEnter={this.props.events.onEnter} />
</>