Bläddra i källkod

Show histogram in direct volume control point settings (#666)

David Sehnal 2 år sedan
förälder
incheckning
7afcf0bb68

+ 2 - 0
CHANGELOG.md

@@ -6,6 +6,8 @@ Note that since we don't clearly distinguish between a public and private interf
 
 ## [Unreleased]
 
+- Show histogram in direct volume control point settings
+
 
 ## [v3.27.0] - 2022-12-15
 

+ 34 - 12
src/mol-plugin-ui/controls/line-graph/line-graph-component.tsx

@@ -1,12 +1,15 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Paul Luna <paulluna0215@gmail.com>
+ * @author David Sehnal <david.sehnal@gmail.com>
  */
 import { PointComponent } from './point-component';
 
 import * as React from 'react';
 import { Vec2 } from '../../../mol-math/linear-algebra';
+import { Grid } from '../../../mol-model/volume';
+import { arrayMax } from '../../../mol-util/array';
 
 interface LineGraphComponentState {
     points: Vec2[],
@@ -76,6 +79,7 @@ export class LineGraphComponent extends React.Component<any, LineGraphComponentS
     public render() {
         const points = this.renderPoints();
         const lines = this.renderLines();
+        const histogram = this.renderHistogram();
 
         return ([
             <div key="LineGraph">
@@ -93,6 +97,7 @@ export class LineGraphComponent extends React.Component<any, LineGraphComponentS
                     onDoubleClick={this.handleDoubleClick}>
 
                     <g stroke="black" fill="black">
+                        {histogram}
                         {lines}
                         {points}
                     </g>
@@ -297,11 +302,11 @@ export class LineGraphComponent extends React.Component<any, LineGraphComponentS
     }
 
     private normalizePoint(point: Vec2) {
-        const min = this.padding / 2;
-        const maxX = this.width + min;
-        const maxY = this.height + min;
-        const normalizedX = (point[0] * (maxX - min)) + min;
-        const normalizedY = (point[1] * (maxY - min)) + min;
+        const offset = this.padding / 2;
+        const maxX = this.width + offset;
+        const maxY = this.height + offset;
+        const normalizedX = (point[0] * (maxX - offset)) + offset;
+        const normalizedY = (point[1] * (maxY - offset)) + offset;
         const reverseY = (this.height + this.padding) - normalizedY;
         const newPoint = Vec2.create(normalizedX, reverseY);
         return newPoint;
@@ -325,6 +330,24 @@ export class LineGraphComponent extends React.Component<any, LineGraphComponentS
         }
     }
 
+    private renderHistogram() {
+        if (!this.props.volume) return null;
+
+        const histogram = Grid.getHistogram(this.props.volume.grid, 40);
+        const bars = [];
+        const N = histogram.counts.length;
+        const w = this.width / N;
+        const offset = this.padding / 2;
+        const max = arrayMax(histogram.counts) || 1;
+        for (let i = 0; i < N; i++) {
+            const x = this.width * i / (N - 1) + offset;
+            const y1 = this.height + offset;
+            const y2 = this.height * (1 - histogram.counts[i] / max) + offset;
+            bars.push(<line key={`histogram${i}`} x1={x} x2={x} y1={y1} y2={y2} stroke="#ded9ca" strokeWidth={w} />);
+        }
+        return bars;
+    }
+
     private renderPoints() {
         const points: any[] = [];
         let point: Vec2;
@@ -352,19 +375,18 @@ export class LineGraphComponent extends React.Component<any, LineGraphComponentS
     private renderLines() {
         const points: Vec2[] = [];
         const lines = [];
-        let min: number;
         let maxX: number;
         let maxY: number;
         let normalizedX: number;
         let normalizedY: number;
         let reverseY: number;
 
+        const o = this.padding / 2;
         for (const point of this.state.points) {
-            min = this.padding / 2;
-            maxX = this.width + min;
-            maxY = this.height + min;
-            normalizedX = (point[0] * (maxX - min)) + min;
-            normalizedY = (point[1] * (maxY - min)) + min;
+            maxX = this.width + o;
+            maxY = this.height + this.padding;
+            normalizedX = (point[0] * (maxX - o)) + o;
+            normalizedY = (point[1] * (maxY - o)) + o;
             reverseY = this.height + this.padding - normalizedY;
             points.push(Vec2.create(normalizedX, reverseY));
         }

+ 23 - 6
src/mol-plugin-ui/controls/parameters.tsx

@@ -7,6 +7,7 @@
 
 import * as React from 'react';
 import { Mat4, Vec2, Vec3 } from '../../mol-math/linear-algebra';
+import { Volume } from '../../mol-model/volume';
 import { Script } from '../../mol-script/script';
 import { Asset } from '../../mol-util/assets';
 import { Color } from '../../mol-util/color';
@@ -306,17 +307,32 @@ export class LineGraphControl extends React.PureComponent<ParamProps<PD.LineGrap
         message: `${this.props.param.defaultValue.length} points`,
     };
 
+
+    private pointToLabel(point?: Vec2) {
+        if (!point) return '';
+
+        const volume = this.props.param.getVolume?.() as Volume;
+        if (volume) {
+            const { min, max, mean, sigma } = volume.grid.stats;
+            const v = min + (max - min) * point[0];
+            const s = (v - mean) / sigma;
+            return `(${v.toFixed(2)} | ${s.toFixed(2)}σ, ${point[1].toFixed(2)})`;
+        } else {
+            return `(${point[0].toFixed(2)}, ${point[1].toFixed(2)})`;
+        }
+    }
+
     onHover = (point?: Vec2) => {
         this.setState({ isOverPoint: !this.state.isOverPoint });
         if (point) {
-            this.setState({ message: `(${point[0].toFixed(2)}, ${point[1].toFixed(2)})` });
-            return;
+            this.setState({ message: this.pointToLabel(point) });
+        } else {
+            this.setState({ message: `${this.props.value.length} points` });
         }
-        this.setState({ message: `${this.props.value.length} points` });
     };
 
     onDrag = (point: Vec2) => {
-        this.setState({ message: `(${point[0].toFixed(2)}, ${point[1].toFixed(2)})` });
+        this.setState({ message: this.pointToLabel(point) });
     };
 
     onChange = (value: PD.LineGraph['defaultValue']) => {
@@ -332,9 +348,10 @@ export class LineGraphControl extends React.PureComponent<ParamProps<PD.LineGrap
         const label = this.props.param.label || camelCaseToWords(this.props.name);
         return <>
             <ControlRow label={label} control={<button onClick={this.toggleExpanded} disabled={this.props.isDisabled}>{`${this.state.message}`}</button>} />
-            <div className='msp-control-offset' style={{ display: this.state.isExpanded ? 'block' : 'none' }}>
+            <div className='msp-control-offset' style={{ display: this.state.isExpanded ? 'block' : 'none', marginTop: 1 }}>
                 <LineGraphComponent
-                    data={this.props.param.defaultValue}
+                    data={this.props.value}
+                    volume={this.props.param.getVolume?.()}
                     onChange={this.onChange}
                     onHover={this.onHover}
                     onDrag={this.onDrag} />

+ 3 - 1
src/mol-repr/volume/direct-volume.ts

@@ -129,7 +129,9 @@ export const DirectVolumeParams = {
 };
 export type DirectVolumeParams = typeof DirectVolumeParams
 export function getDirectVolumeParams(ctx: ThemeRegistryContext, volume: Volume) {
-    return PD.clone(DirectVolumeParams);
+    const params = PD.clone(DirectVolumeParams);
+    params.controlPoints.getVolume = () => volume;
+    return params;
 }
 export type DirectVolumeProps = PD.Values<DirectVolumeParams>
 

+ 6 - 3
src/mol-util/param-definition.ts

@@ -216,10 +216,13 @@ export namespace ParamDefinition {
     }
 
     export interface LineGraph extends Base<Vec2Data[]> {
-        type: 'line-graph'
+        type: 'line-graph',
+        getVolume?: () => unknown
     }
-    export function LineGraph(defaultValue: Vec2Data[], info?: Info): LineGraph {
-        return setInfo<LineGraph>({ type: 'line-graph', defaultValue }, info);
+    export function LineGraph(defaultValue: Vec2Data[], info?: Info & { getVolume?: (binCount?: number) => unknown }): LineGraph {
+        const ret = setInfo<LineGraph>({ type: 'line-graph', defaultValue }, info);
+        if (info?.getVolume) ret.getVolume = info.getVolume;
+        return ret;
     }
 
     export interface Group<T> extends Base<T> {