Pārlūkot izejas kodu

Support volumeIndex in Viewer.loadVolumeFromUrl

dsehnal 3 gadi atpakaļ
vecāks
revīzija
84fb42a161

+ 2 - 0
CHANGELOG.md

@@ -5,6 +5,8 @@ Note that since we don't clearly distinguish between a public and private interf
 
 ## [Unreleased]
 
+- Add ability to specify ``volumeIndex`` in ``Viewer.loadVolumeFromUrl`` to better support Volume Server inputs.
+
 ## [v2.0.6] - 2021-06-01
 
 - Add glTF (GLB) and STL support to ``geo-export`` extension.

+ 0 - 13
src/apps/viewer/embedded.html

@@ -38,19 +38,6 @@
             viewer.loadPdb('7bv2');
             viewer.loadEmdb('EMD-30210', { detail: 6 });
 
-            // viewer.loadVolumeFromUrl({
-            //     url: 'https://maps.rcsb.org/em/emd-30210/cell?detail=6',
-            //     format: 'dscif',
-            //     isBinary: true 
-            // }, [{
-            //     type: 'relative',
-            //     value: 1,
-            //     color: 0x3377aa
-            // }], {
-            //     entryId: 'EMD-30210',
-            //     isLazy: true
-            // });
-
             // viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
         </script>
     </body>

+ 55 - 13
src/apps/viewer/index.ts

@@ -243,7 +243,46 @@ export class Viewer {
         }));
     }
 
-    async loadVolumeFromUrl({ url, format, isBinary }: { url: string, format: BuildInVolumeFormat, isBinary: boolean }, isovalues: VolumeIsovalueInfo[], options?: { entryId?: string, isLazy?: boolean }) {
+    /**
+     * @example Load X-ray density from volume server
+        viewer.loadVolumeFromUrl({
+            url: 'https://www.ebi.ac.uk/pdbe/densities/x-ray/1tqn/cell?detail=3',
+            format: 'dscif',
+            isBinary: true
+        }, [{
+            type: 'relative',
+            value: 1.5,
+            color: 0x3362B2
+        }, {
+            type: 'relative',
+            value: 3,
+            color: 0x33BB33,
+            volumeIndex: 1
+        }, {
+            type: 'relative',
+            value: -3,
+            color: 0xBB3333,
+            volumeIndex: 1
+        }], {
+            entryId: ['2FO-FC', 'FO-FC'],
+            isLazy: true
+        });
+     * *********************
+     * @example Load EM density from volume server
+        viewer.loadVolumeFromUrl({
+            url: 'https://maps.rcsb.org/em/emd-30210/cell?detail=6',
+            format: 'dscif',
+            isBinary: true
+        }, [{
+            type: 'relative',
+            value: 1,
+            color: 0x3377aa
+        }], {
+            entryId: 'EMD-30210',
+            isLazy: true
+        });
+     */
+    async loadVolumeFromUrl({ url, format, isBinary }: { url: string, format: BuildInVolumeFormat, isBinary: boolean }, isovalues: VolumeIsovalueInfo[], options?: { entryId?: string | string[], isLazy?: boolean }) {
         const plugin = this.plugin;
 
         if (!plugin.dataFormats.get(format)) {
@@ -257,26 +296,28 @@ export class Viewer {
                 format,
                 entryId: options?.entryId,
                 isBinary,
-                isovalues: isovalues.map(v => ({ alpha: 1, ...v }))
+                isovalues: isovalues.map(v => ({ alpha: 1, volumeIndex: 0, ...v }))
             });
             return update.commit();
         }
 
         return plugin.dataTransaction(async () => {
-            const data = await plugin.builders.data.download({ url, isBinary, label: options?.entryId }, { state: { isGhost: true } });
+            const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
 
             const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId: options?.entryId });
-            const volume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
-            if (!volume?.isOk) throw new Error('Failed to parse any volume.');
+            const firstVolume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
+            if (!firstVolume?.isOk) throw new Error('Failed to parse any volume.');
 
-            const repr = plugin.build().to(volume);
+            const repr = plugin.build();
             for (const iso of isovalues) {
-                repr.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, volume.data!, {
-                    type: 'isosurface',
-                    typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
-                    color: 'uniform',
-                    colorParams: { value: iso.color }
-                }));
+                repr
+                    .to(parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume)
+                    .apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, firstVolume.data!, {
+                        type: 'isosurface',
+                        typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
+                        color: 'uniform',
+                        colorParams: { value: iso.color }
+                    }));
             }
 
             await repr.commit();
@@ -296,5 +337,6 @@ export interface VolumeIsovalueInfo {
     type: 'absolute' | 'relative',
     value: number,
     color: Color,
-    alpha?: number
+    alpha?: number,
+    volumeIndex?: number
 }

+ 8 - 2
src/mol-plugin-state/formats/volume.ts

@@ -191,6 +191,9 @@ export const CubeProvider = DataFormatProvider({
     }
 });
 
+
+type DsCifParams = { entryId?: string | string[] };
+
 export const DscifProvider = DataFormatProvider({
     label: 'DensityServer CIF',
     description: 'DensityServer CIF',
@@ -200,7 +203,7 @@ export const DscifProvider = DataFormatProvider({
     isApplicable: (info, data) => {
         return guessCifVariant(info, data) === 'dscif' ? true : false;
     },
-    parse: async (plugin, data, params?: Params) => {
+    parse: async (plugin, data, params?: DsCifParams) => {
         const cifCell = await plugin.build().to(data).apply(StateTransforms.Data.ParseCif).commit();
         const b = plugin.build().to(cifCell);
         const blocks = cifCell.obj!.data.blocks.slice(1); // zero block contains query meta-data
@@ -208,8 +211,11 @@ export const DscifProvider = DataFormatProvider({
         if (blocks.length !== 1 && blocks.length !== 2) throw new Error('unknown number of blocks');
 
         const volumes: StateObjectSelector<PluginStateObject.Volume.Data>[] = [];
+        let i = 0;
         for (const block of blocks) {
-            volumes.push(b.apply(StateTransforms.Volume.VolumeFromDensityServerCif, { blockHeader: block.header, entryId: params?.entryId }).selector);
+            const entryId = Array.isArray(params?.entryId) ? params?.entryId[i] : params?.entryId;
+            volumes.push(b.apply(StateTransforms.Volume.VolumeFromDensityServerCif, { blockHeader: block.header, entryId }).selector);
+            i++;
         }
 
         await b.commit();

+ 3 - 2
src/mol-plugin-state/objects.ts

@@ -125,12 +125,13 @@ export namespace PluginStateObject {
             url: string | Asset.Url,
             isBinary: boolean,
             format: string,
-            entryId?: string,
+            entryId?: string | string[],
             isovalues: {
                 type: 'absolute' | 'relative',
                 value: number,
                 color: Color,
-                alpha?: number
+                alpha?: number,
+                volumeIndex?: number,
             }[]
         }
 

+ 5 - 3
src/mol-plugin-state/transforms/data.ts

@@ -455,18 +455,20 @@ const LazyVolume = PluginStateTransform.BuiltIn({
         url: PD.Url(''),
         isBinary: PD.Boolean(false),
         format: PD.Text('ccp4'), // TODO: use Select based on available formats
-        entryId: PD.Text(''),
+        entryId: PD.Value<string | string[]>('', { isHidden: true }),
         isovalues: PD.ObjectList({
             type: PD.Text<'absolute' | 'relative'>('relative'), // TODO: Select
             value: PD.Numeric(0),
             color: PD.Color(ColorNames.black),
-            alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 })
+            alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
+            volumeIndex: PD.Numeric(0),
         }, e => `${e.type} ${e.value}`)
     }
 })({
     apply({ a, params }) {
         return Task.create('Lazy Volume', async ctx => {
-            return new SO.Volume.Lazy(params, { label: `${params.entryId || params.url}`, description: 'Lazy Volume' });
+            const entryId = Array.isArray(params.entryId) ? params.entryId.join(', ') : params.entryId;
+            return new SO.Volume.Lazy(params, { label: `${entryId || params.url}`, description: 'Lazy Volume' });
         });
     }
 });

+ 12 - 10
src/mol-plugin-ui/structure/volume.tsx

@@ -205,19 +205,21 @@ export class VolumeSourceControls extends CollapsableControls<{}, VolumeSourceCo
         try {
             const plugin = this.plugin;
             await plugin.dataTransaction(async () => {
-                const data = await plugin.builders.data.download({ url, isBinary, label: entryId }, { state: { isGhost: true } });
+                const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
                 const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId });
-                const volume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
-                if (!volume?.isOk) throw new Error('Failed to parse any volume.');
+                const firstVolume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
+                if (!firstVolume?.isOk) throw new Error('Failed to parse any volume.');
 
-                const repr = plugin.build().to(volume);
+                const repr = plugin.build();
                 for (const iso of isovalues) {
-                    repr.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, volume.data!, {
-                        type: 'isosurface',
-                        typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
-                        color: 'uniform',
-                        colorParams: { value: iso.color }
-                    }));
+                    repr
+                        .to(parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume)
+                        .apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, firstVolume.data!, {
+                            type: 'isosurface',
+                            typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
+                            color: 'uniform',
+                            colorParams: { value: iso.color }
+                        }));
                 }
 
                 await repr.commit();