From 1b382653f23854dafda33565b897c7de9adc7e57 Mon Sep 17 00:00:00 2001 From: dsehnal <david.sehnal@gmail.com> Date: Mon, 19 Dec 2022 16:49:30 +0100 Subject: [PATCH] add volume controls to Volseg extension --- CHANGELOG.md | 2 +- .../volumes-and-segmentations/entry-root.ts | 13 ++++++++- .../volumes-and-segmentations/entry-volume.ts | 2 +- .../volumes-and-segmentations/ui.tsx | 28 +++++++++++++------ 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82f75dd10..fb0728b21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ Note that since we don't clearly distinguish between a public and private interf - Show histogram in direct volume control point settings - Add `solidInterior` parameter to sphere/cylinder impostors -- Add `meshes` and `volseg` extensions +- Add `meshes` and `volumes-and-segmentations` extensions ## [v3.27.0] - 2022-12-15 diff --git a/src/extensions/volumes-and-segmentations/entry-root.ts b/src/extensions/volumes-and-segmentations/entry-root.ts index 76971ac66..0918145b1 100644 --- a/src/extensions/volumes-and-segmentations/entry-root.ts +++ b/src/extensions/volumes-and-segmentations/entry-root.ts @@ -27,11 +27,12 @@ import { VolsegMeshSegmentationData } from './entry-meshes'; import { VolsegModelData } from './entry-models'; import { VolsegLatticeSegmentationData } from './entry-segmentation'; import { VolsegState, VolsegStateData, VolsegStateParams } from './entry-state'; -import { VolsegVolumeData, SimpleVolumeParamValues } from './entry-volume'; +import { VolsegVolumeData, SimpleVolumeParamValues, VOLUME_VISUAL_TAG } from './entry-volume'; import * as ExternalAPIs from './external-api'; import { VolsegGlobalStateData } from './global-state'; import { applyEllipsis, Choice, isDefined, lazyGetter, splitEntryId } from './helpers'; import { type VolsegStateFromEntry } from './transformers'; +import { StateTransforms } from '../../mol-plugin-state/transforms'; export const MAX_VOXELS = 10 ** 7; @@ -90,6 +91,7 @@ export namespace VolsegEntryParamValues { export class VolsegEntry extends PluginStateObject.CreateBehavior<VolsegEntryData>({ name: 'Vol & Seg Entry' }) { } +type VolRepr3DT = typeof StateTransforms.Representation.VolumeRepresentation3D export class VolsegEntryData extends PluginBehavior.WithSubscribers<VolsegEntryParamValues> { plugin: PluginContext; @@ -111,6 +113,7 @@ export class VolsegEntryData extends PluginBehavior.WithSubscribers<VolsegEntryP private getStateNode = lazyGetter(() => this.plugin.state.data.selectQ(q => q.byRef(this.ref).subtree().ofType(VolsegState))[0] as StateObjectCell<VolsegState, StateTransform<typeof VolsegStateFromEntry>>, 'Missing VolsegState node. Must first create VolsegState for this VolsegEntry.'); public currentState = new BehaviorSubject(ParamDefinition.getDefaultValues(VolsegStateParams)); + public currentVolume = new BehaviorSubject<StateTransform<VolRepr3DT> | undefined>(undefined); private constructor(plugin: PluginContext, params: VolsegEntryParamValues) { @@ -158,6 +161,14 @@ export class VolsegEntryData extends PluginBehavior.WithSubscribers<VolsegEntryP } }); + this.subscribeObservable(this.plugin.state.data.events.cell.stateUpdated, e => { + // TODO: subscribe cell.removed event as well to set the current volume to undefined + if (e.cell.transform.tags?.includes(VOLUME_VISUAL_TAG)) { + // TODO: make sure this belongs to the "current entry subtree" + this.currentVolume.next(e.cell.transform); + } + }); + this.subscribeObservable(this.plugin.behaviors.interaction.click, async e => { const loci = e.current.loci; const clickedSegment = this.getSegmentIdFromLoci(loci); diff --git a/src/extensions/volumes-and-segmentations/entry-volume.ts b/src/extensions/volumes-and-segmentations/entry-volume.ts index 1ba4184ac..930edc5cc 100644 --- a/src/extensions/volumes-and-segmentations/entry-volume.ts +++ b/src/extensions/volumes-and-segmentations/entry-volume.ts @@ -24,7 +24,7 @@ import { VolsegGlobalStateData } from './global-state'; const GROUP_TAG = 'volume-group'; -const VOLUME_VISUAL_TAG = 'volume-visual'; +export const VOLUME_VISUAL_TAG = 'volume-visual'; const DIRECT_VOLUME_RELATIVE_PEAK_HALFWIDTH = 0.5; diff --git a/src/extensions/volumes-and-segmentations/ui.tsx b/src/extensions/volumes-and-segmentations/ui.tsx index 65df31a57..0d8a24cb0 100644 --- a/src/extensions/volumes-and-segmentations/ui.tsx +++ b/src/extensions/volumes-and-segmentations/ui.tsx @@ -12,6 +12,7 @@ import * as Icons from '../../mol-plugin-ui/controls/icons'; import { ParameterControls } from '../../mol-plugin-ui/controls/parameters'; import { Slider } from '../../mol-plugin-ui/controls/slider'; import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior'; +import { UpdateTransformControl } from '../../mol-plugin-ui/state/update-transform'; import { PluginContext } from '../../mol-plugin/context'; import { shallowEqualArrays } from '../../mol-util'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; @@ -117,11 +118,6 @@ function VolsegEntryControls({ entryData }: { entryData: VolsegEntryData }) { const visibleModels = state.visibleModels.map(model => model.pdbId); const allPdbs = entryData.pdbs; - const volumeValues: SimpleVolumeParamValues = { - volumeType: state.volumeType, - opacity: state.volumeOpacity, - }; - return <> {/* Title */} <div style={{ fontWeight: 'bold', padding: 8, paddingTop: 6, paddingBottom: 4, overflow: 'hidden' }}> @@ -139,10 +135,7 @@ function VolsegEntryControls({ entryData }: { entryData: VolsegEntryData }) { </ExpandGroup>} {/* Volume */} - <ExpandGroup header='Volume data' initiallyExpanded> - <WaitingParameterControls params={SimpleVolumeParams} values={volumeValues} onChangeValues={async next => { await sleep(20); await entryData.actionUpdateVolumeVisual(next); }} /> - </ExpandGroup> - + <VolumeControls entryData={entryData} /> <ExpandGroup header='Segmentation data' initiallyExpanded> {/* Segment opacity slider */} <ControlRow label='Opacity' control={ @@ -189,6 +182,23 @@ function VolsegEntryControls({ entryData }: { entryData: VolsegEntryData }) { </>; } +function VolumeControls({ entryData }: { entryData: VolsegEntryData }) { + const vol = useBehavior(entryData.currentVolume); + if (!vol) return null; + + const volumeValues: SimpleVolumeParamValues = { + volumeType: vol.state.isHidden ? 'off' : vol.params?.type.name as any, + opacity: vol.params?.type.params.alpha, + }; + + return <ExpandGroup header='Volume data' initiallyExpanded> + <WaitingParameterControls params={SimpleVolumeParams} values={volumeValues} onChangeValues={async next => { await sleep(20); await entryData.actionUpdateVolumeVisual(next); }} /> + <ExpandGroup header='Detailed Volume Params' headerStyle={{ marginTop: 1 }}> + <UpdateTransformControl state={entryData.plugin.state.data} transform={vol} customHeader='none' /> + </ExpandGroup> + </ExpandGroup>; +} + type ComponentParams<T extends React.Component<any, any, any> | ((props: any) => JSX.Element)> = T extends React.Component<infer P, any, any> ? P : T extends (props: infer P) => JSX.Element ? P : never; -- GitLab