Browse Source

AssignColorVolume
+ UI bugfix

David Sehnal 5 years ago
parent
commit
5480805754

+ 2 - 1
src/mol-model-formats/volume/ccp4.ts

@@ -38,7 +38,7 @@ function getTypedArrayCtor(header: Ccp4Header) {
     throw Error(`${valueType} is not a supported value format.`);
 }
 
-export function volumeFromCcp4(source: Ccp4File, params?: { voxelSize?: Vec3, offset?: Vec3 }): Task<VolumeData> {
+export function volumeFromCcp4(source: Ccp4File, params?: { voxelSize?: Vec3, offset?: Vec3, label?: string }): Task<VolumeData> {
     return Task.create<VolumeData>('Create Volume Data', async ctx => {
         const { header, values } = source;
         const size = Vec3.create(header.xLength, header.yLength, header.zLength);
@@ -67,6 +67,7 @@ export function volumeFromCcp4(source: Ccp4File, params?: { voxelSize?: Vec3, of
         // These, however, calculate sigma, so no data on that.
 
         return {
+            label: params?.label,
             transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3.zero(), origin_frac, dimensions_frac)) },
             data,
             dataStats: {

+ 2 - 1
src/mol-model-formats/volume/cube.ts

@@ -10,7 +10,7 @@ import { VolumeData } from '../../mol-model/volume/data';
 import { Task } from '../../mol-task';
 import { arrayMax, arrayMean, arrayMin, arrayRms } from '../../mol-util/array';
 
-export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number }): Task<VolumeData> {
+export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number, label?: string }): Task<VolumeData> {
     return Task.create<VolumeData>('Create Volume Data', async () => {
         const { header, values: sourceValues } = source;
         const space = Tensor.Space(header.dim, [0, 1, 2], Float64Array);
@@ -43,6 +43,7 @@ export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number }
         Mat4.mul(matrix, matrix, basis);
 
         return {
+            label: params?.label,
             transform: { kind: 'matrix', matrix },
             data,
             dataStats: {

+ 2 - 1
src/mol-model-formats/volume/dsn6.ts

@@ -12,7 +12,7 @@ import { degToRad } from '../../mol-math/misc';
 import { Dsn6File } from '../../mol-io/reader/dsn6/schema';
 import { arrayMin, arrayMax, arrayMean, arrayRms } from '../../mol-util/array';
 
-function volumeFromDsn6(source: Dsn6File, params?: { voxelSize?: Vec3 }): Task<VolumeData> {
+function volumeFromDsn6(source: Dsn6File, params?: { voxelSize?: Vec3, label?: string }): Task<VolumeData> {
     return Task.create<VolumeData>('Create Volume Data', async ctx => {
         const { header, values } = source;
         const size = Vec3.create(header.xlen, header.ylen, header.zlen);
@@ -32,6 +32,7 @@ function volumeFromDsn6(source: Dsn6File, params?: { voxelSize?: Vec3 }): Task<V
         const data = Tensor.create(space, Tensor.Data1(values));
 
         return {
+            label: params?.label,
             transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3.zero(), origin_frac, dimensions_frac)) },
             data,
             dataStats: {

+ 2 - 1
src/mol-model-formats/volume/dx.ts

@@ -10,7 +10,7 @@ import { VolumeData } from '../../mol-model/volume/data';
 import { Task } from '../../mol-task';
 import { arrayMax, arrayMean, arrayMin, arrayRms } from '../../mol-util/array';
 
-export function volumeFromDx(source: DxFile): Task<VolumeData> {
+export function volumeFromDx(source: DxFile, params?: { label?: string }): Task<VolumeData> {
     return Task.create<VolumeData>('Create Volume Data', async () => {
         const { header, values } = source;
         const space = Tensor.Space(header.dim, [0, 1, 2], Float64Array);
@@ -20,6 +20,7 @@ export function volumeFromDx(source: DxFile): Task<VolumeData> {
         Mat4.mul(matrix, matrix, basis);
 
         return {
+            label: params?.label,
             transform: { kind: 'matrix', matrix },
             data,
             dataStats: {

+ 16 - 2
src/mol-plugin-state/actions/volume.ts

@@ -6,7 +6,7 @@
  */
 
 import { PluginContext } from '../../mol-plugin/context';
-import { StateAction, StateTransformer } from '../../mol-state';
+import { StateAction, StateTransformer, StateSelection } from '../../mol-state';
 import { Task } from '../../mol-task';
 import { getFileInfo } from '../../mol-util/file-info';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
@@ -14,6 +14,7 @@ import { PluginStateObject } from '../objects';
 import { Download } from '../transforms/data';
 import { DataFormatProvider } from '../formats/provider';
 import { Asset } from '../../mol-util/assets';
+import { StateTransforms } from '../transforms';
 
 export { DownloadDensity };
 type DownloadDensity = typeof DownloadDensity
@@ -135,4 +136,17 @@ const DownloadDensity = StateAction.build({
 
     const volumes = await provider.parse(plugin, data);
     await provider.visuals?.(plugin, volumes);
-}));
+}));
+
+export const AssignColorVolume = StateAction.build({
+    display: { name: 'Assign Volume Colors', description: 'Assigns another volume to be available for coloring.' },
+    from: PluginStateObject.Volume.Data,
+    isApplicable(a) { return !a.data.colorVolume; },
+    params(a, plugin: PluginContext) {
+        const cells = plugin.state.data.select(StateSelection.Generators.root.subtree().ofType(PluginStateObject.Volume.Data).filter(cell => !!cell.obj && !cell.obj?.data.colorVolume && cell.obj !== a));
+        if (cells.length === 0) return { ref: PD.Text('', { isHidden: true, label: 'Volume' }) };
+        return { ref: PD.Select(cells[0].transform.ref, cells.map(c => [c.transform.ref, c.obj!.label]), { label: 'Volume' }) };
+    }
+})(({ ref, params, state }, plugin: PluginContext) => {
+    return plugin.build().to(ref).apply(StateTransforms.Volume.AssignColorVolume, { ref: params.ref }, { dependsOn: [ params.ref ] }).commit();
+});

+ 1 - 1
src/mol-plugin-state/formats/volume.ts

@@ -70,7 +70,7 @@ export const DxProvider = DataFormatProvider({
     parse: async (plugin, data) => {
         const volume = plugin.build()
             .to(data)
-            .apply(StateTransforms.Volume.VolumeFromDx, {}, { state: { isGhost: true } });
+            .apply(StateTransforms.Volume.VolumeFromDx);
 
         await volume.commit({ revertOnError: true });
 

+ 3 - 3
src/mol-plugin-state/manager/volume/hierarchy-state.ts

@@ -84,14 +84,14 @@ type TestCell = (cell: StateObjectCell, state: BuildState) => boolean
 type ApplyRef = (state: BuildState, cell: StateObjectCell) => boolean | void
 type LeaveRef = (state: BuildState) => any
 
-function isType(t: StateObject.Ctor): TestCell {
-    return (cell) => t.is(cell.obj);
+function isTypeRoot(t: StateObject.Ctor, target: (state: BuildState) => any): TestCell {
+    return (cell, state) => !target(state) && t.is(cell.obj);
 }
 
 function noop() { }
 
 const Mapping: [TestCell, ApplyRef, LeaveRef][] = [
-    [isType(SO.Volume.Data), (state, cell) => {
+    [isTypeRoot(SO.Volume.Data, t => t.currentVolume), (state, cell) => {
         state.currentVolume = createOrUpdateRefList(state, cell, state.hierarchy.volumes, VolumeRef, cell);
     }, state => state.currentVolume = void 0],
 

+ 1 - 0
src/mol-plugin-state/manager/volume/hierarchy.ts

@@ -78,6 +78,7 @@ export class VolumeHierarchyManager extends PluginComponent {
     }
 
     setCurrent(volume?: VolumeRef) {
+        this.state.selection = volume || this.state.hierarchy.volumes[0];
         this.behaviors.selection.next({ hierarchy: this.state.hierarchy, volume: volume || this.state.hierarchy.volumes[0] });
     }
 

+ 43 - 10
src/mol-plugin-state/transforms/volume.ts

@@ -16,11 +16,15 @@ import { PluginStateObject as SO, PluginStateTransform } from '../objects';
 import { volumeFromCube } from '../../mol-model-formats/volume/cube';
 import { parseDx } from '../../mol-io/reader/dx/parser';
 import { volumeFromDx } from '../../mol-model-formats/volume/dx';
+import { VolumeData } from '../../mol-model/volume';
+import { PluginContext } from '../../mol-plugin/context';
+import { StateSelection } from '../../mol-state';
 
 export { VolumeFromCcp4 };
 export { VolumeFromDsn6 };
 export { VolumeFromCube };
 export { VolumeFromDx };
+export { AssignColorVolume };
 export { VolumeFromDensityServerCif };
 
 type VolumeFromCcp4 = typeof VolumeFromCcp4
@@ -38,8 +42,8 @@ const VolumeFromCcp4 = PluginStateTransform.BuiltIn({
 })({
     apply({ a, params }) {
         return Task.create('Create volume from CCP4/MRC/MAP', async ctx => {
-            const volume = await volumeFromCcp4(a.data, params).runInContext(ctx);
-            const props = { label: 'Volume' };
+            const volume = await volumeFromCcp4(a.data, { ...params, label: a.label }).runInContext(ctx);
+            const props = { label: volume.label || 'Volume', description: 'Volume' };
             return new SO.Volume.Data(volume, props);
         });
     }
@@ -59,8 +63,8 @@ const VolumeFromDsn6 = PluginStateTransform.BuiltIn({
 })({
     apply({ a, params }) {
         return Task.create('Create volume from DSN6/BRIX', async ctx => {
-            const volume = await volumeFromDsn6(a.data, params).runInContext(ctx);
-            const props = { label: 'Volume' };
+            const volume = await volumeFromDsn6(a.data, { ...params, label: a.label }).runInContext(ctx);
+            const props = { label: volume.label || 'Volume', description: 'Volume' };
             return new SO.Volume.Data(volume, props);
         });
     }
@@ -81,8 +85,8 @@ const VolumeFromCube = PluginStateTransform.BuiltIn({
 })({
     apply({ a, params }) {
         return Task.create('Create volume from Cube', async ctx => {
-            const volume = await volumeFromCube(a.data, params).runInContext(ctx);
-            const props = { label: 'Volume' };
+            const volume = await volumeFromCube(a.data, { ...params, label: a.label }).runInContext(ctx);
+            const props = { label: volume.label || 'Volume', description: 'Volume' };
             return new SO.Volume.Data(volume, props);
         });
     }
@@ -99,14 +103,13 @@ const VolumeFromDx = PluginStateTransform.BuiltIn({
         return Task.create('Parse DX', async ctx => {
             const parsed = await parseDx(a.data).runInContext(ctx);
             if (parsed.isError) throw new Error(parsed.message);
-            const volume = await volumeFromDx(parsed.result).runInContext(ctx);
-            const props = { label: `Volume` };
+            const volume = await volumeFromDx(parsed.result, { label: a.label }).runInContext(ctx);
+            const props = { label: volume.label || 'Volume', description: 'Volume' };
             return new SO.Volume.Data(volume, props);
         });
     }
 });
 
-
 type VolumeFromDensityServerCif = typeof VolumeFromDensityServerCif
 const VolumeFromDensityServerCif = PluginStateTransform.BuiltIn({
     name: 'volume-from-density-server-cif',
@@ -137,4 +140,34 @@ const VolumeFromDensityServerCif = PluginStateTransform.BuiltIn({
             return new SO.Volume.Data(volume, props);
         });
     }
-});
+});
+
+type AssignColorVolume = typeof AssignColorVolume
+const AssignColorVolume = PluginStateTransform.BuiltIn({
+    name: 'assign-color-volume',
+    display: { name: 'Assign Color Volume', description: 'Assigns another volume to be available for coloring.' },
+    from: SO.Volume.Data,
+    to: SO.Volume.Data,
+    isDecorator: true,
+    params(a, plugin: PluginContext) {
+        if (!a) return { ref: PD.Text() };
+        const cells = plugin.state.data.select(StateSelection.Generators.root.subtree().ofType(SO.Volume.Data).filter(cell => !!cell.obj && !cell.obj?.data.colorVolume && cell.obj !== a));
+        if (cells.length === 0) return { ref: PD.Text('', { isHidden: true }) };
+        return { ref: PD.Select(cells[0].transform.ref, cells.map(c => [c.transform.ref, c.obj!.label])) };
+    }
+})({
+    apply({ a, params, dependencies }) {
+        return Task.create('Assign Color Volume', async ctx => {
+            if (!dependencies || !dependencies[params.ref]) {
+                throw new Error('Dependency not available.');
+            }
+            const colorVolume = dependencies[params.ref].data as VolumeData;
+            const volume: VolumeData = {
+                ...a.data,
+                colorVolume
+            };
+            const props = { label: a.label, description: 'Volume + Colors' };
+            return new SO.Volume.Data(volume, props);
+        });
+    }
+});

+ 3 - 1
src/mol-plugin-ui/state/tree.tsx

@@ -245,7 +245,9 @@ class StateTreeNodeLabel extends PluginUIComponent<{ cell: StateObjectCell, dept
         e.currentTarget.blur();
     }
 
-    hideApply = () => this.setState({ action: 'options', currentAction: void 0 });
+    hideApply = () => {
+        this.setCurrentRoot();
+    }
 
     get actions() {
         const cell = this.props.cell;

+ 2 - 0
src/mol-plugin/index.ts

@@ -18,6 +18,7 @@ import { BoxifyVolumeStreaming, CreateVolumeStreamingBehavior, InitVolumeStreami
 import { PluginConfig } from './config';
 import { PluginContext } from './context';
 import { PluginSpec } from './spec';
+import { AssignColorVolume } from '../mol-plugin-state/actions/volume';
 
 export const DefaultPluginSpec: PluginSpec = {
     actions: [
@@ -59,6 +60,7 @@ export const DefaultPluginSpec: PluginSpec = {
         PluginSpec.Action(StateTransforms.Representation.OverpaintStructureRepresentation3DFromScript),
         PluginSpec.Action(StateTransforms.Representation.TransparencyStructureRepresentation3DFromScript),
 
+        PluginSpec.Action(AssignColorVolume),
         PluginSpec.Action(StateTransforms.Volume.VolumeFromCcp4),
         PluginSpec.Action(StateTransforms.Volume.VolumeFromDsn6),
         PluginSpec.Action(StateTransforms.Volume.VolumeFromCube),