Procházet zdrojové kódy

RO-2751: hide all but 2Fo-Fc map

JonStargaryen před 3 roky
rodič
revize
74f8eaec92
2 změnil soubory, kde provedl 218 přidání a 6 odebrání
  1. 202 0
      src/viewer/helpers/density.ts
  2. 16 6
      src/viewer/helpers/preset.ts

+ 202 - 0
src/viewer/helpers/density.ts

@@ -0,0 +1,202 @@
+/**
+ * Copyright (c) 2019-2020 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 as SO, PluginStateTransform } from 'molstar/lib/mol-plugin-state/objects';
+import { ParamDefinition as PD } from 'molstar/lib/mol-util/param-definition';
+import { Task } from 'molstar/lib/mol-task';
+import { PluginContext } from 'molstar/lib/mol-plugin/context';
+import { Volume } from 'molstar/lib/mol-model/volume';
+import { StateAction, StateObject, StateTransformer } from 'molstar/lib/mol-state';
+import { VolumeRepresentation3DHelpers } from 'molstar/lib/mol-plugin-state/transforms/representation';
+import { VolumeRepresentationRegistry } from 'molstar/lib/mol-repr/volume/registry';
+import { Theme } from 'molstar/lib/mol-theme/theme';
+import { Model } from 'molstar/lib/mol-model/structure';
+import { GlobalModelTransformInfo } from 'molstar/lib/mol-model/structure/model/properties/global-transform';
+import {
+    getContourLevel,
+    getEmdbIds,
+    getIds,
+    getStreamingMethod
+} from 'molstar/lib/mol-plugin/behavior/dynamic/volume-streaming/util';
+import { VolumeStreaming } from 'molstar/lib/mol-plugin/behavior/dynamic/volume-streaming/behavior';
+import { PluginConfig } from 'molstar/lib/mol-plugin/config';
+import { VolumeServerInfo } from 'molstar/lib/mol-plugin/behavior/dynamic/volume-streaming/model';
+import {
+    CreateVolumeStreamingBehavior,
+    CreateVolumeStreamingInfo
+} from 'molstar/lib/mol-plugin/behavior/dynamic/volume-streaming/transformers';
+
+// TODO SMB 6/11/21 - this is a temp impl, safe to remove as soon as the next Mol* version after 2.0.6 is released
+
+function addEntry(entries: InfoEntryProps[], method: VolumeServerInfo.Kind, dataId: string, emDefaultContourLevel: number) {
+    entries.push({
+        source: method === 'em'
+            ? { name: 'em', params: { isoValue: Volume.IsoValue.absolute(emDefaultContourLevel || 0) } }
+            : { name: 'x-ray', params: { } },
+        dataId
+    });
+}
+
+export const InitVolumeStreaming = StateAction.build({
+    display: { name: 'Volume Streaming' },
+    from: SO.Molecule.Structure,
+    params(a, plugin: PluginContext) {
+        const method = getStreamingMethod(a && a.data);
+        const ids = getIds(method, a && a.data);
+        return {
+            method: PD.Select<VolumeServerInfo.Kind>(method, [['em', 'EM'], ['x-ray', 'X-Ray']]),
+            entries: PD.ObjectList({ id: PD.Text(ids[0] || '') }, ({ id }) => id, { defaultValue: ids.map(id => ({ id })) }),
+            defaultView: PD.Select<VolumeStreaming.ViewTypes>(method === 'em' ? 'cell' : 'selection-box', VolumeStreaming.ViewTypeOptions as any),
+            options: PD.Group({
+                serverUrl: PD.Text(plugin.config.get(PluginConfig.VolumeStreaming.DefaultServer) || 'https://ds.litemol.org'),
+                behaviorRef: PD.Text('', { isHidden: true }),
+                emContourProvider: PD.Select<'emdb' | 'pdbe'>('emdb', [['emdb', 'EMDB'], ['pdbe', 'PDBe']], { isHidden: true }),
+                channelParams: PD.Value<VolumeStreaming.DefaultChannelParams>({}, { isHidden: true })
+            })
+        };
+    },
+    isApplicable: (a, _, plugin: PluginContext) => {
+        const canStreamTest = plugin.config.get(PluginConfig.VolumeStreaming.CanStream);
+        if (canStreamTest) return canStreamTest(a.data, plugin);
+        return a.data.models.length === 1 && Model.probablyHasDensityMap(a.data.models[0]);
+    }
+})(({ ref, state, params }, plugin: PluginContext) => Task.create('Volume Streaming', async taskCtx => {
+    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];
+                    let contourLevel: number | undefined;
+                    try {
+                        contourLevel = await getContourLevel(params.options.emContourProvider, plugin, taskCtx, emdbId);
+                    } catch (e) {
+                        console.info(`Could not get map info for ${emdbId}: ${e}`);
+                        continue;
+                    }
+                    addEntry(entries, params.method, emdbId, contourLevel || 0);
+                }
+                continue;
+            }
+            try {
+                emDefaultContourLevel = await getContourLevel(params.options.emContourProvider, plugin, taskCtx, dataId);
+            } catch (e) {
+                console.info(`Could not get map info for ${dataId}: ${e}`);
+                continue;
+            }
+        }
+
+        addEntry(entries, params.method, dataId, emDefaultContourLevel || 0);
+    }
+
+    const infoTree = state.build().to(ref)
+        .applyOrUpdateTagged(VolumeStreaming.RootTag, CreateVolumeStreamingInfo, {
+            serverUrl: params.options.serverUrl,
+            entries
+        });
+
+    await infoTree.commit();
+
+    const info = infoTree.selector;
+    if (!info.isOk) return;
+
+    // clear the children in case there were errors
+    const children = state.tree.children.get(info.ref);
+    if (children?.size > 0) await plugin.managers.structure.hierarchy.remove(children?.toArray());
+
+    const infoObj = info.cell!.obj!;
+
+    const behTree = state.build().to(infoTree.ref).apply(CreateVolumeStreamingBehavior,
+        PD.getDefaultValues(VolumeStreaming.createParams({ data: infoObj.data, defaultView: params.defaultView, channelParams: params.options.channelParams })),
+        { ref: params.options.behaviorRef ? params.options.behaviorRef : void 0 });
+
+    if (params.method === 'em') {
+        behTree.apply(VolumeStreamingVisual, { channel: 'em' }, { state: { isGhost: true }, tags: 'em' });
+    } else {
+        behTree.apply(VolumeStreamingVisual, { channel: '2fo-fc' }, { state: { isGhost: true }, tags: '2fo-fc' });
+        behTree.apply(VolumeStreamingVisual, { channel: 'fo-fc(+ve)' }, { state: { isGhost: true }, tags: 'fo-fc(+ve)' });
+        behTree.apply(VolumeStreamingVisual, { channel: 'fo-fc(-ve)' }, { state: { isGhost: true }, tags: 'fo-fc(-ve)' });
+    }
+    await state.updateTree(behTree).runInContext(taskCtx);
+}));
+
+const InfoEntryParams = {
+    dataId: PD.Text(''),
+    source: PD.MappedStatic('x-ray', {
+        'em': PD.Group({
+            isoValue: Volume.createIsoValueParam(Volume.IsoValue.relative(1))
+        }),
+        'x-ray': PD.Group({ })
+    })
+};
+type InfoEntryProps = PD.Values<typeof InfoEntryParams>
+
+export { VolumeStreamingVisual };
+type VolumeStreamingVisual = typeof VolumeStreamingVisual
+const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
+    name: 'create-volume-streaming-visual-custom',
+    display: { name: 'Volume Streaming Visual' },
+    from: VolumeStreaming,
+    to: SO.Volume.Representation3D,
+    params: {
+        channel: PD.Select<VolumeStreaming.ChannelType>('em', VolumeStreaming.ChannelTypeOptions, { isHidden: true })
+    }
+})({
+    apply: ({ a, params: srcParams, spine }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
+        const channel = a.data.channels[srcParams.channel];
+        if (!channel) return StateObject.Null;
+
+        const params = createVolumeProps(a.data, srcParams.channel);
+        const provider = VolumeRepresentationRegistry.BuiltIn.isosurface;
+        const props = params.type.params || {};
+        const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.volume.themes }, provider.getParams);
+        repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: channel.data }, params));
+        const structure = spine.getAncestorOfType(SO.Molecule.Structure)?.data;
+        const transform = structure?.models.length === 0 ? void 0 : GlobalModelTransformInfo.get(structure?.models[0]!);
+        await repr.createOrUpdate(props, channel.data).runInContext(ctx);
+        if (transform) repr.setState({ transform });
+        return new SO.Volume.Representation3D({ repr, sourceData: channel.data }, { label: `${Math.round(channel.isoValue.relativeValue * 100) / 100} σ [${srcParams.channel}]` });
+    }),
+    update: ({ a, b, newParams, spine }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
+        // TODO : check if params/underlying data/etc have changed; maybe will need to export "data" or some other "tag" in the Representation for this to work
+
+        const channel = a.data.channels[newParams.channel];
+        // TODO: is this correct behavior?
+        if (!channel) return StateTransformer.UpdateResult.Unchanged;
+
+        const visible = b.data.repr.state.visible;
+        const params = createVolumeProps(a.data, newParams.channel);
+        const props = { ...b.data.repr.props, ...params.type.params };
+        b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: channel.data }, params));
+        await b.data.repr.createOrUpdate(props, channel.data).runInContext(ctx);
+        b.data.repr.setState({ visible });
+        b.data.sourceData = channel.data;
+
+        // TODO: set the transform here as well in case the structure moves?
+        //       doing this here now breaks the code for some reason...
+        // const structure = spine.getAncestorOfType(SO.Molecule.Structure)?.data;
+        // const transform = structure?.models.length === 0 ? void 0 : GlobalModelTransformInfo.get(structure?.models[0]!);
+        // if (transform) b.data.repr.setState({ transform });
+
+        return StateTransformer.UpdateResult.Updated;
+    })
+});
+
+function createVolumeProps(streaming: VolumeStreaming.Behavior, channelName: VolumeStreaming.ChannelType) {
+    const channel = streaming.channels[channelName]!;
+    return VolumeRepresentation3DHelpers.getDefaultParamsStatic(streaming.plugin,
+        'isosurface', { isoValue: channel.isoValue, alpha: channel.opacity, visuals: channel.wireframe ? ['wireframe'] : ['solid'] },
+        'uniform', { value: channel.color });
+}

+ 16 - 6
src/viewer/helpers/preset.ts

@@ -21,7 +21,6 @@ import {
     StructureElement
 } from 'molstar/lib/mol-model/structure';
 import { compile } from 'molstar/lib/mol-script/runtime/query/compiler';
-import { InitVolumeStreaming } from 'molstar/lib/mol-plugin/behavior/dynamic/volume-streaming/transformers';
 import { ViewerState } from '../types';
 import {
     StateSelection,
@@ -30,7 +29,6 @@ import {
     StateTransformer,
     StateObjectRef
 } from 'molstar/lib/mol-state';
-import { VolumeStreaming } from 'molstar/lib/mol-plugin/behavior/dynamic/volume-streaming/behavior';
 import { Mat4 } from 'molstar/lib/mol-math/linear-algebra';
 import { CustomStructureProperties } from 'molstar/lib/mol-plugin-state/transforms/model';
 import { FlexibleStructureFromModel as FlexibleStructureFromModel } from './superpose/flexible-structure';
@@ -39,6 +37,9 @@ import { StructureSelectionQueries as Q } from 'molstar/lib/mol-plugin-state/hel
 import { PluginCommands } from 'molstar/lib/mol-plugin/commands';
 import { InteractivityManager } from 'molstar/lib/mol-plugin-state/manager/interactivity';
 import { MembraneOrientationPreset } from 'molstar/lib/extensions/anvil/behavior';
+import { setSubtreeVisibility } from 'molstar/lib/mol-plugin/behavior/static/state';
+import { InitVolumeStreaming, VolumeStreamingVisual } from './density';
+import { VolumeStreaming } from 'molstar/lib/mol-plugin/behavior/dynamic/volume-streaming/behavior';
 
 type Target = {
     readonly auth_seq_id?: number
@@ -336,7 +337,7 @@ export const RcsbPreset = TrajectoryHierarchyPresetProvider({
             const target = chainMode ? loci : StructureElement.Loci.firstResidue(loci);
 
             if (p.kind === 'feature-density') {
-                await initVolumeStreaming(plugin, structure, p.radius || 0);
+                await initVolumeStreaming(plugin, structure, { overrideRadius: p.radius || 0, hiddenChannels: p.hiddenChannels || [] });
             }
 
             plugin.managers.structure.focus.setFromLoci(target);
@@ -371,7 +372,7 @@ export const RcsbPreset = TrajectoryHierarchyPresetProvider({
     }
 });
 
-async function initVolumeStreaming(plugin: PluginContext, structure: StructureObject, overrideRadius?: number) {
+async function initVolumeStreaming(plugin: PluginContext, structure: StructureObject, props?: { overrideRadius?: number, hiddenChannels: string[] }) {
     if (!structure?.cell?.parent) return;
 
     const volumeRoot = StateSelection.findTagInSubtree(structure.cell.parent.tree, structure.cell.transform.ref, VolumeStreaming.RootTag);
@@ -381,14 +382,23 @@ async function initVolumeStreaming(plugin: PluginContext, structure: StructureOb
         await plugin.runTask(state.applyAction(InitVolumeStreaming, params, structure.ref));
 
         // RO-2751: allow to specify radius of shown density
-        if (overrideRadius !== void 0) {
+        if (props?.overrideRadius !== void 0) {
             const { params, transform } = state.select(StateSelection.Generators.ofType(VolumeStreaming))[0];
 
             const p = params?.values;
-            (p.entry.params.view.params as any).radius = overrideRadius;
+            (p.entry.params.view.params as any).radius = props.overrideRadius;
 
             await state.build().to(transform.ref).update(p).commit();
         }
+        // RO-2751: hide all but 2Fo-Fc map
+        if (props?.hiddenChannels?.length) {
+            const cells = state.select(StateSelection.Generators.ofTransformer(VolumeStreamingVisual));
+            for (const cell of cells) {
+                if (props.hiddenChannels.indexOf(cell.obj!.tags![0]) !== -1) {
+                    setSubtreeVisibility(state, cell.transform.ref, true);
+                }
+            }
+        }
     }
 
     ViewerState(plugin).collapsed.next({