diff --git a/src/mol-plugin/ui/controls/parameters.tsx b/src/mol-plugin/ui/controls/parameters.tsx index 7d26c184cf3ede4e2d346d6f252e7cbbe12b3243..ed890ef130c3ad3b61751ffc40f77ee2e80a38b6 100644 --- a/src/mol-plugin/ui/controls/parameters.tsx +++ b/src/mol-plugin/ui/controls/parameters.tsx @@ -47,6 +47,7 @@ function controlFor(param: PD.Any): ParamControl | undefined { case 'number': return typeof param.min !== 'undefined' && typeof param.max !== 'undefined' ? NumberRangeControl : NumberInputControl; case 'converted': return ConvertedControl; + case 'conditioned': return ConditionedControl; case 'multi-select': return MultiSelectControl; case 'color': return ColorControl; case 'color-scale': return ColorScaleControl; @@ -456,6 +457,41 @@ export class MappedControl extends React.PureComponent<ParamProps<PD.Mapped<any> } } +export class ConditionedControl extends React.PureComponent<ParamProps<PD.Conditioned<any, any, any>>> { + change(value: PD.Conditioned<any, any, any>['defaultValue'] ) { + this.props.onChange({ name: this.props.name, param: this.props.param, value }); + } + + onChangeCondition: ParamOnChange = e => { + this.change(this.props.param.conditionedValue(this.props.value, e.value)); + } + + onChangeParam: ParamOnChange = e => { + this.change(e.value); + } + + render() { + const value = this.props.value; + const condition = this.props.param.conditionForValue(value) as string + const param = this.props.param.conditionParams[condition]; + const label = this.props.param.label || camelCaseToWords(this.props.name); + const Conditioned = controlFor(param); + + const select = <SelectControl param={this.props.param.select} + isDisabled={this.props.isDisabled} onChange={this.onChangeCondition} onEnter={this.props.onEnter} + name={`${label} Kind`} value={condition} /> + + if (!Conditioned) { + return select; + } + + return <div> + {select} + <Conditioned param={param} value={value} name={label} onChange={this.onChangeParam} onEnter={this.props.onEnter} isDisabled={this.props.isDisabled} /> + </div> + } +} + export class ConvertedControl extends React.PureComponent<ParamProps<PD.Converted<any, any>>> { onChange: ParamOnChange = e => { this.props.onChange({ diff --git a/src/mol-repr/volume/isosurface-mesh.ts b/src/mol-repr/volume/isosurface-mesh.ts index 94c7200386880ad4aac2b7353d53e34c28392d96..778b82946e5033f85e5d01e1907cb8c6024c70cd 100644 --- a/src/mol-repr/volume/isosurface-mesh.ts +++ b/src/mol-repr/volume/isosurface-mesh.ts @@ -5,7 +5,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { VolumeData } from 'mol-model/volume' +import { VolumeData, VolumeIsoValue } from 'mol-model/volume' import { VolumeVisual, VolumeRepresentation, VolumeRepresentationProvider } from './representation'; import { EmptyLoci } from 'mol-model/loci'; import { ParamDefinition as PD } from 'mol-util/param-definition'; @@ -19,9 +19,30 @@ import { VisualContext } from 'mol-repr/visual'; import { NullLocation } from 'mol-model/location'; import { Lines } from 'mol-geo/geometry/lines/lines'; -interface VolumeIsosurfaceProps { - isoValue: number +const IsoValueParam = PD.Conditioned( + VolumeIsoValue.relative(VolumeData.Empty.dataStats, 2), + { + 'absolute': PD.Converted( + (v: VolumeIsoValue) => VolumeIsoValue.toAbsolute(v).absoluteValue, + (v: number) => VolumeIsoValue.absolute(VolumeData.Empty.dataStats, v), + PD.Numeric(0, { min: -1, max: 1, step: 0.01 }) + ), + 'relative': PD.Converted( + (v: VolumeIsoValue) => VolumeIsoValue.toRelative(v).relativeValue, + (v: number) => VolumeIsoValue.relative(VolumeData.Empty.dataStats, v), + PD.Numeric(0, { min: -1, max: 1, step: 0.01 }) + ) + }, + (v: VolumeIsoValue) => v.kind === 'absolute' ? 'absolute' : 'relative', + (v: VolumeIsoValue, c: 'absolute' | 'relative') => c === 'absolute' ? VolumeIsoValue.toAbsolute(v) : VolumeIsoValue.toRelative(v) +) +type IsoValueParam = typeof IsoValueParam + +export const VolumeIsosurfaceParams = { + isoValue: IsoValueParam } +export type VolumeIsosurfaceParams = typeof VolumeIsosurfaceParams +export type VolumeIsosurfaceProps = PD.Values<VolumeIsosurfaceParams> // @@ -29,7 +50,7 @@ export async function createVolumeIsosurfaceMesh(ctx: VisualContext, volume: Vol ctx.runtime.update({ message: 'Marching cubes...' }); const surface = await computeMarchingCubesMesh({ - isoLevel: props.isoValue, + isoLevel: VolumeIsoValue.toAbsolute(props.isoValue).absoluteValue, scalarField: volume.data }, mesh).runAsChild(ctx.runtime); @@ -43,7 +64,7 @@ export async function createVolumeIsosurfaceMesh(ctx: VisualContext, volume: Vol export const IsosurfaceMeshParams = { ...Mesh.Params, - isoValue: PD.Numeric(0.22, { min: -1, max: 1, step: 0.01 }), + ...VolumeIsosurfaceParams } export type IsosurfaceMeshParams = typeof IsosurfaceMeshParams @@ -66,11 +87,10 @@ export function IsosurfaceMeshVisual(): VolumeVisual<IsosurfaceMeshParams> { export async function createVolumeIsosurfaceWireframe(ctx: VisualContext, volume: VolumeData, theme: Theme, props: VolumeIsosurfaceProps, lines?: Lines) { ctx.runtime.update({ message: 'Marching cubes...' }); - const params = { - isoLevel: props.isoValue, + const wireframe = await computeMarchingCubesLines({ + isoLevel: VolumeIsoValue.toAbsolute(props.isoValue).absoluteValue, scalarField: volume.data - } - const wireframe = await computeMarchingCubesLines(params, lines).runAsChild(ctx.runtime) + }, lines).runAsChild(ctx.runtime) const transform = VolumeData.getGridToCartesianTransform(volume); Lines.transformImmediate(wireframe, transform) @@ -80,7 +100,7 @@ export async function createVolumeIsosurfaceWireframe(ctx: VisualContext, volume export const IsosurfaceWireframeParams = { ...Lines.Params, - isoValue: PD.Numeric(0.22, { min: -1, max: 1, step: 0.01 }), + ...VolumeIsosurfaceParams } export type IsosurfaceWireframeParams = typeof IsosurfaceWireframeParams @@ -114,7 +134,26 @@ export const IsosurfaceParams = { } export type IsosurfaceParams = typeof IsosurfaceParams export function getIsosurfaceParams(ctx: ThemeRegistryContext, volume: VolumeData) { - return PD.clone(IsosurfaceParams) + const p = PD.clone(IsosurfaceParams) + const { min, max, mean, sigma } = volume.dataStats + p.isoValue = PD.Conditioned( + VolumeIsoValue.relative(volume.dataStats, 2), + { + 'absolute': PD.Converted( + (v: VolumeIsoValue) => VolumeIsoValue.toAbsolute(v).absoluteValue, + (v: number) => VolumeIsoValue.absolute(volume.dataStats, v), + PD.Numeric(mean, { min, max, step: sigma / 100 }) + ), + 'relative': PD.Converted( + (v: VolumeIsoValue) => VolumeIsoValue.toRelative(v).relativeValue, + (v: number) => VolumeIsoValue.relative(volume.dataStats, v), + PD.Numeric(2, { min: -10, max: 10, step: 0.001 }) + ) + }, + (v: VolumeIsoValue) => v.kind === 'absolute' ? 'absolute' : 'relative', + (v: VolumeIsoValue, c: 'absolute' | 'relative') => c === 'absolute' ? VolumeIsoValue.toAbsolute(v) : VolumeIsoValue.toRelative(v) + ) + return p } export type IsosurfaceRepresentation = VolumeRepresentation<IsosurfaceParams> diff --git a/src/mol-repr/volume/representation.ts b/src/mol-repr/volume/representation.ts index c38081926277f6a485f9df9eb86382c2acdace97..0a68fe847a044604d440fcd39389d2435fc1ebeb 100644 --- a/src/mol-repr/volume/representation.ts +++ b/src/mol-repr/volume/representation.ts @@ -205,7 +205,6 @@ export type VolumeRepresentationProvider<P extends VolumeParams> = Representatio export const VolumeParams = { ...BaseGeometry.Params, - isoValue: PD.Numeric(0.22, { min: -1, max: 1, step: 0.01 }), } export type VolumeParams = typeof VolumeParams diff --git a/src/mol-util/param-definition.ts b/src/mol-util/param-definition.ts index ec68757b0c2ce572a14884f91bc388c35629c194..746c9a62bc965f4110c7d521018506377636b9dc 100644 --- a/src/mol-util/param-definition.ts +++ b/src/mol-util/param-definition.ts @@ -190,7 +190,7 @@ export namespace ParamDefinition { export interface Converted<T, C> extends Base<T> { type: 'converted', converted: Any, - /** converts from props value to display value */ + /** converts from prop value to display value */ fromValue(v: T): C, /** converts from display value to prop value */ toValue(v: C): T @@ -199,7 +199,19 @@ export namespace ParamDefinition { return { type: 'converted', defaultValue: toValue(converted.defaultValue), converted, fromValue, toValue }; } - export type Any = Value<any> | Select<any> | MultiSelect<any> | Boolean | Text | Color | Vec3 | Numeric | FileParam | Interval | LineGraph | ColorScale<any> | Group<any> | Mapped<any> | Converted<any, any> + export interface Conditioned<T, P extends Base<T>, C = { [k: string]: P }> extends Base<T> { + type: 'conditioned', + select: Select<string>, + conditionParams: C + conditionForValue(v: T): keyof C + conditionedValue(v: T, condition: keyof C): T, + } + export function Conditioned<T, P extends Base<T>, C = { [k: string]: P }>(defaultValue: T, conditionParams: C, conditionForValue: (v: T) => keyof C, conditionedValue: (v: T, condition: keyof C) => T): Conditioned<T, P, C> { + const options = Object.keys(conditionParams).map(k => [k, k]) as [string, string][]; + return { type: 'conditioned', select: Select<string>(conditionForValue(defaultValue) as string, options), defaultValue, conditionParams, conditionForValue, conditionedValue }; + } + + export type Any = Value<any> | Select<any> | MultiSelect<any> | Boolean | Text | Color | Vec3 | Numeric | FileParam | Interval | LineGraph | ColorScale<any> | Group<any> | Mapped<any> | Converted<any, any> | Conditioned<any, any, any> export type Params = { [k: string]: Any } export type Values<T extends Params> = { [k in keyof T]: T[k]['defaultValue'] }