Переглянути джерело

VolumeStreamingCustomControls bounded ranges for fine grained control

David Sehnal 5 роки тому
батько
коміт
2f71c4c5e4
1 змінених файлів з 48 додано та 14 видалено
  1. 48 14
      src/mol-plugin-ui/custom/volume.tsx

+ 48 - 14
src/mol-plugin-ui/custom/volume.tsx

@@ -28,6 +28,13 @@ const ChannelParams = {
 };
 type ChannelParams = PD.Values<typeof ChannelParams>
 
+const Bounds = new Map<VolumeStreaming.ChannelType, [number, number]>([
+    ['em', [-5, 5]],
+    ['2fo-fc', [0, 3]],
+    ['fo-fc(+ve)', [1, 5]],
+    ['fo-fc(-ve)', [-5, -1]],
+]);
+
 class Channel extends PluginUIComponent<{
     label: string,
     name: VolumeStreaming.ChannelType,
@@ -38,7 +45,8 @@ class Channel extends PluginUIComponent<{
     changeIso: (name: string, value: number, isRelative: boolean) => void,
     changeParams: (name: string, param: string, value: any) => void,
     bCell: StateObjectCell,
-    isDisabled?: boolean
+    isDisabled?: boolean,
+    isUnbounded?: boolean
 }> {
     private ref = StateSelection.findTagInSubtree(this.plugin.state.data.tree, this.props.bCell!.transform.ref, this.props.name);
 
@@ -73,15 +81,31 @@ class Channel extends PluginUIComponent<{
 
         const { min, max, mean, sigma } = stats;
         const value = Math.round(100 * (channel.isoValue.kind === 'relative' ? channel.isoValue.relativeValue : channel.isoValue.absoluteValue)) / 100;
-        const relMin = (min - mean) / sigma;
-        const relMax = (max - mean) / sigma;
-        const step = toPrecision(isRelative ? Math.round(((max - min) / sigma)) / 100 : sigma / 100, 2);
+        let relMin = (min - mean) / sigma;
+        let relMax = (max - mean) / sigma;
+
+        if (!this.props.isUnbounded) {
+            const bounds = Bounds.get(this.props.name)!;
+            if (this.props.name === 'em') {
+                relMin = Math.max(bounds[0], relMin);
+                relMax = Math.min(bounds[1], relMax);
+            } else {
+                relMin = bounds[0];
+                relMax = bounds[1];
+            }
+        }
+
+        const vMin = mean + sigma * relMin, vMax = mean + sigma * relMax;
+
+        const step = toPrecision(isRelative ? Math.round(((vMax - vMin) / sigma)) / 100 : sigma / 100, 2);
+        const ctrlMin = isRelative ? relMin : vMin;
+        const ctrlMax = isRelative ? relMax : vMax;
 
         return <ExpandableControlRow
             label={props.label + (props.isRelative ? ' \u03C3' : '')}
             colorStripe={channel.color}
             pivot={<div className='msp-volume-channel-inline-controls'>
-                <Slider value={value} min={isRelative ? relMin : min} max={isRelative ? relMax : max} step={step}
+                <Slider value={value} min={ctrlMin} max={ctrlMax} step={step}
                     onChange={v => props.changeIso(props.name, v, isRelative)} disabled={props.params.isDisabled} onEnter={props.params.events.onEnter} />
                 <IconButton svg={this.getVisible() ? VisibilityOutlined : VisibilityOffOutlined} onClick={this.toggleVisible} toggleState={false} disabled={props.params.isDisabled} />
             </div>}
@@ -163,6 +187,7 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
             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;
 
@@ -180,6 +205,7 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
                 viewParams.bottomLeft = value.params.bottomLeft;
                 viewParams.topRight = value.params.topRight;
             }
+            viewParams.isUnbounded = !!value.params.isUnbounded;
 
             this.newParams({
                 ...old,
@@ -221,28 +247,35 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
 
         const isRelativeParam = PD.Boolean(isRelative, { description: 'Use normalized or absolute isocontour scale.', label: 'Normalized' });
 
+        const isUnbounded = !!(params.entry.params.view.params as any).isUnbounded;
+        const isUnboundedParam = PD.Boolean(isUnbounded, { description: 'Show full/limited range of iso-values for more fine-grained control.', label: 'Unbounded' });
+
         const isOff = params.entry.params.view.name === 'off';
         // TODO: factor common things out, cache
         const OptionsParams = {
             entry: PD.Select(params.entry.name, b.data.entries.map(info => [info.dataId, info.dataId] as [string, string]), { isHidden: isOff, description: 'Which entry with volume data to display.' }),
             view: PD.MappedStatic(params.entry.params.view.name, {
                 'off': PD.Group({
-                    isRelative: PD.Boolean(isRelative, { isHidden: true })
+                    isRelative: PD.Boolean(isRelative, { isHidden: true }),
+                    isUnbounded: PD.Boolean(isUnbounded, { isHidden: true }),
                 }, { description: 'Display off.' }),
                 'box': PD.Group({
                     bottomLeft: PD.Vec3(Vec3.zero()),
                     topRight: PD.Vec3(Vec3.zero()),
                     detailLevel,
-                    isRelative: isRelativeParam
+                    isRelative: isRelativeParam,
+                    isUnbounded: isUnboundedParam,
                 }, { description: 'Static box defined by cartesian coords.' }),
                 'selection-box': PD.Group({
                     radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }, { description: 'Radius in \u212B within which the volume is shown.' }),
                     detailLevel,
-                    isRelative: isRelativeParam
+                    isRelative: isRelativeParam,
+                    isUnbounded: isUnboundedParam,
                 }, { description: 'Box around focused element.' }),
                 'cell': PD.Group({
                     detailLevel,
-                    isRelative: isRelativeParam
+                    isRelative: isRelativeParam,
+                    isUnbounded: isUnboundedParam,
                 }, { 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: VolumeStreaming.ViewTypeOptions, description: 'Controls what of the volume is displayed. "Off" hides the volume alltogether. "Bounded box" shows the volume inside the given box. "Around Focus" shows the volume around the element/atom last interacted with. "Whole Structure" shows the volume for the whole structure.' })
@@ -256,7 +289,8 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
                     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
+                    isRelative,
+                    isUnbounded
                 }
             }
         };
@@ -266,10 +300,10 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
         }
 
         return <>
-            {!isEM && <Channel label='2Fo-Fc' name='2fo-fc' bCell={this.props.bCell!} 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)' bCell={this.props.bCell!} 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)' bCell={this.props.bCell!} 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' bCell={this.props.bCell!} channels={params.entry.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' bCell={this.props.bCell!} channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[0]} isUnbounded={isUnbounded} />}
+            {!isEM && <Channel label='Fo-Fc(+ve)' name='fo-fc(+ve)' bCell={this.props.bCell!} channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[1]} isUnbounded={isUnbounded} />}
+            {!isEM && <Channel label='Fo-Fc(-ve)' name='fo-fc(-ve)' bCell={this.props.bCell!} channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[1]} isUnbounded={isUnbounded} />}
+            {isEM && <Channel label='EM' name='em' bCell={this.props.bCell!} channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[0]} isUnbounded={isUnbounded} />}
 
             <ParameterControls onChange={this.changeOption} params={OptionsParams} values={options} onEnter={this.props.events.onEnter} isDisabled={this.props.isDisabled} />
         </>;