diff --git a/CHANGELOG.md b/CHANGELOG.md index 3818a12b58192d4816d32defda468a265eef4504..fdaf339b1f73fa568cb8c5aa8a290247a240c500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ Note that since we don't clearly distinguish between a public and private interf ## [Unreleased] +- Fixed a bug in mesh visualization (show backfaces when opacity < 1) + ## [v3.28.0] - 2022-12-20 - Show histogram in direct volume control point settings diff --git a/src/extensions/meshes/choice.ts b/src/extensions/meshes/choice.ts deleted file mode 100644 index 0da7350eefbd28fa49bbb75816b064bff8573cee..0000000000000000000000000000000000000000 --- a/src/extensions/meshes/choice.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Adam Midlik <midlik@gmail.com> - */ - -import { ParamDefinition as PD } from '../../mol-util/param-definition'; - - -/** - * Represents a set of values to choose from, with a default value. Example: - * ``` - * export const MyChoice = new Choice({ yes: 'I agree', no: 'Nope' }, 'yes'); - * export type MyChoiceType = Choice.Values<typeof MyChoice>; // 'yes'|'no' - * ``` - */ -export class Choice<T extends string, D extends T> { - readonly defaultValue: D; - readonly options: [T, string][]; - private readonly nameDict: { [value in T]: string }; - constructor(opts: { [value in T]: string }, defaultValue: D) { - this.defaultValue = defaultValue; - this.options = Object.keys(opts).map(k => [k as T, opts[k as T]]); - this.nameDict = opts; - } - PDSelect(defaultValue?: T, info?: PD.Info): PD.Select<T> { - return PD.Select<T>(defaultValue ?? this.defaultValue, this.options, info); - } - prettyName(value: T): string { - return this.nameDict[value]; - } -} -export namespace Choice { - export type Values<T extends Choice<any, any>> = T extends Choice<infer R, any> ? R : any; -} diff --git a/src/extensions/meshes/examples.ts b/src/extensions/meshes/examples.ts index 64c2476ce95a19c4a98c0e89aa17a7d6f2e7b208..16956efd8bf9e24dd3d9e9af3ec790df0e3931bd 100644 --- a/src/extensions/meshes/examples.ts +++ b/src/extensions/meshes/examples.ts @@ -6,31 +6,28 @@ /** Testing examples for using mesh-extension.ts. */ -import { ParseMeshlistTransformer, MeshShapeTransformer, MeshlistData } from './mesh-extension'; -import * as MeshUtils from './mesh-utils'; -import { BACKGROUND_OPACITY, FOREROUND_OPACITY, InitMeshStreaming } from './mesh-streaming/transformers'; -import { MeshServerInfo } from './mesh-streaming/server-info'; -import { PluginUIContext } from '../../mol-plugin-ui/context'; -import { PluginContext } from '../../mol-plugin/context'; -import { StateObjectRef, StateObjectSelector } from '../../mol-state'; -import { Color } from '../../mol-util/color'; -import { Download } from '../../mol-plugin-state/transforms/data'; -import { StateTransforms } from '../../mol-plugin-state/transforms'; -import { Box3D } from '../../mol-math/geometry'; -import { ShapeRepresentation3D } from '../../mol-plugin-state/transforms/representation'; -import { ParamDefinition } from '../../mol-util/param-definition'; -import { PluginStateObject } from '../../mol-plugin-state/objects'; -import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params'; +import { CIF } from '../../mol-io/reader/cif'; import { Volume } from '../../mol-model/volume'; +import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params'; import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params'; +import { PluginStateObject } from '../../mol-plugin-state/objects'; +import { StateTransforms } from '../../mol-plugin-state/transforms'; +import { PluginContext } from '../../mol-plugin/context'; +import { StateObjectSelector } from '../../mol-state'; import { Asset } from '../../mol-util/assets'; -import { CIF } from '../../mol-io/reader/cif'; +import { Color } from '../../mol-util/color'; +import { ParamDefinition } from '../../mol-util/param-definition'; + +import { createMeshFromUrl } from './mesh-extension'; +import { MeshServerInfo } from './mesh-streaming/server-info'; +import { InitMeshStreaming } from './mesh-streaming/transformers'; +import * as MeshUtils from './mesh-utils'; export const DB_URL = '/db'; // local -export async function runMeshExtensionExamples(plugin: PluginUIContext, db_url: string = DB_URL) { +export async function runMeshExtensionExamples(plugin: PluginContext, db_url: string = DB_URL) { console.time('TIME MESH EXAMPLES'); // await runIsosurfaceExample(plugin, db_url); // await runMolsurfaceExample(plugin); @@ -47,7 +44,7 @@ export async function runMeshExtensionExamples(plugin: PluginUIContext, db_url: } /** Example for downloading multiple separate segments, each containing 1 mesh. */ -export async function runMeshExample(plugin: PluginUIContext, segments: 'fg' | 'all', db_url: string = DB_URL) { +export async function runMeshExample(plugin: PluginContext, segments: 'fg' | 'all', db_url: string = DB_URL) { const detail = 2; const segmentIds = (segments === 'all') ? [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17] // segment-16 has no detail-2 @@ -59,7 +56,7 @@ export async function runMeshExample(plugin: PluginUIContext, segments: 'fg' | ' } /** Example for downloading multiple separate segments, each containing 1 mesh. */ -export async function runMeshExample2(plugin: PluginUIContext, segments: 'one' | 'few' | 'fg' | 'all') { +export async function runMeshExample2(plugin: PluginContext, segments: 'one' | 'few' | 'fg' | 'all') { const detail = 1; const segmentIds = (segments === 'one') ? [15] : (segments === 'few') ? [1, 4, 7, 10, 16] @@ -72,48 +69,13 @@ export async function runMeshExample2(plugin: PluginUIContext, segments: 'one' | } /** Example for downloading a single segment containing multiple meshes. */ -export async function runMultimeshExample(plugin: PluginUIContext, segments: 'fg' | 'all', detailChoice: 'best' | 'worst', db_url: string = DB_URL) { +export async function runMultimeshExample(plugin: PluginContext, segments: 'fg' | 'all', detailChoice: 'best' | 'worst', db_url: string = DB_URL) { const urlDetail = (detailChoice === 'best') ? '2' : 'worst'; const numDetail = (detailChoice === 'best') ? 2 : 1000; await createMeshFromUrl(plugin, `${db_url}/empiar-10070-multimesh-rounded/segments-${segments}/detail-${urlDetail}`, 0, numDetail, false, undefined); } -/** Download data and create state tree hierarchy down to visual representation. */ -export async function createMeshFromUrl(plugin: PluginContext, meshDataUrl: string, segmentId: number, detail: number, - collapseTree: boolean, color?: Color, parent?: StateObjectSelector | StateObjectRef, transparentIfBboxAbove?: number, - name?: string, ownerId?: string) { - - const update = parent ? plugin.build().to(parent) : plugin.build().toRoot(); - const rawDataNodeRef = update.apply(Download, - { url: meshDataUrl, isBinary: true, label: `Downloaded Data ${segmentId}` }, - { state: { isCollapsed: collapseTree } } - ).ref; - const parsedDataNode = await update.to(rawDataNodeRef) - .apply(StateTransforms.Data.ParseCif) - .apply(ParseMeshlistTransformer, - { label: undefined, segmentId: segmentId, segmentName: name ?? `Segment ${segmentId}`, detail: detail, ownerId: ownerId }, - {} - ) - .commit(); - - let transparent = false; - if (transparentIfBboxAbove !== undefined && parsedDataNode.data) { - const bbox = MeshlistData.bbox(parsedDataNode.data) || Box3D.zero(); - transparent = Box3D.volume(bbox) > transparentIfBboxAbove; - } - - await plugin.build().to(parsedDataNode) - .apply(MeshShapeTransformer, { color: color },) - .apply(ShapeRepresentation3D, - { alpha: transparent ? BACKGROUND_OPACITY : FOREROUND_OPACITY }, - { tags: ['mesh-segment-visual', `segment-${segmentId}`] } - ) - .commit(); - - return rawDataNodeRef; -} - -export async function runMeshStreamingExample(plugin: PluginUIContext, source: MeshServerInfo.MeshSource = 'empiar', entryId: string = 'empiar-10070', serverUrl?: string, parent?: StateObjectSelector) { +export async function runMeshStreamingExample(plugin: PluginContext, source: MeshServerInfo.MeshSource = 'empiar', entryId: string = 'empiar-10070', serverUrl?: string, parent?: StateObjectSelector) { const params = ParamDefinition.getDefaultValues(MeshServerInfo.Params); if (serverUrl) params.serverUrl = serverUrl; params.source = source; @@ -122,7 +84,7 @@ export async function runMeshStreamingExample(plugin: PluginUIContext, source: M } /** Example for downloading a protein structure and visualizing molecular surface. */ -export async function runMolsurfaceExample(plugin: PluginUIContext) { +export async function runMolsurfaceExample(plugin: PluginContext) { const entryId = 'pdb-7etq'; // Node "https://www.ebi.ac.uk/pdbe/entry-files/download/7etq.bcif" ("transformer": "ms-plugin.download") -> var data @@ -151,7 +113,7 @@ export async function runMolsurfaceExample(plugin: PluginUIContext) { } /** Example for downloading an EMDB density data and visualizing isosurface. */ -export async function runIsosurfaceExample(plugin: PluginUIContext, db_url: string = DB_URL) { +export async function runIsosurfaceExample(plugin: PluginContext, db_url: string = DB_URL) { const entryId = 'emd-1832'; const isoLevel = 2.73; @@ -203,7 +165,7 @@ export async function runIsosurfaceExample(plugin: PluginUIContext, db_url: stri } -export async function runCifMeshExample(plugin: PluginUIContext, api: string = 'http://localhost:9000/v2', +export async function runCifMeshExample(plugin: PluginContext, api: string = 'http://localhost:9000/v2', source: MeshServerInfo.MeshSource = 'empiar', entryId: string = 'empiar-10070', segmentId: number = 1, detail: number = 10, ) { @@ -211,8 +173,8 @@ export async function runCifMeshExample(plugin: PluginUIContext, api: string = ' getMeshFromBcif(plugin, url); } -async function getMeshFromBcif(plugin: PluginUIContext, url: string) { - const urlAsset = Asset.getUrlAsset(plugin.managers.asset, url); // QUESTION how is urlAsset better than normal `fetch` +async function getMeshFromBcif(plugin: PluginContext, url: string) { + const urlAsset = Asset.getUrlAsset(plugin.managers.asset, url); const asset = await plugin.runTask(plugin.managers.asset.resolve(urlAsset, 'binary')); const parsed = await plugin.runTask(CIF.parseBinary(asset.data)); if (parsed.isError) { diff --git a/src/extensions/meshes/mesh-extension.ts b/src/extensions/meshes/mesh-extension.ts index be312e602275e677b7e212a35e5cc405d9b25c27..4ff1206572e84637f3bac790807383cb7329591c 100644 --- a/src/extensions/meshes/mesh-extension.ts +++ b/src/extensions/meshes/mesh-extension.ts @@ -15,14 +15,20 @@ import { Vec3 } from '../../mol-math/linear-algebra'; import { Shape } from '../../mol-model/shape'; import { ShapeProvider } from '../../mol-model/shape/provider'; import { PluginStateObject } from '../../mol-plugin-state/objects'; -import { StateTransformer } from '../../mol-state'; +import { StateTransforms } from '../../mol-plugin-state/transforms'; +import { Download } from '../../mol-plugin-state/transforms/data'; +import { ShapeRepresentation3D } from '../../mol-plugin-state/transforms/representation'; +import { PluginContext } from '../../mol-plugin/context'; +import { StateObjectRef, StateObjectSelector, StateTransformer } from '../../mol-state'; import { Task } from '../../mol-task'; import { Color } from '../../mol-util/color'; -import { Material } from '../../mol-util/material'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; import * as MeshUtils from './mesh-utils'; +export const BACKGROUND_OPACITY = 0.2; +export const FOREROUND_OPACITY = 1; + export const VolsegTransform: StateTransformer.Builder.Root = StateTransformer.builderFactory('volseg'); @@ -106,19 +112,17 @@ export namespace MeshlistData { } - // // // // // // // // // // // // // // // // // // // // // // // // // Raw Data -> Parsed data export class MeshlistStateObject extends PluginStateObject.Create<MeshlistData>({ name: 'Parsed Meshlist', typeClass: 'Object' }) { } -// QUESTION: is typeClass just for color, or does do something? export const ParseMeshlistTransformer = VolsegTransform({ name: 'meshlist-from-string', from: PluginStateObject.Format.Cif, to: MeshlistStateObject, params: { - label: PD.Text(MeshlistStateObject.type.name, { isHidden: true }), // QUESTION: Is this the right way to pass a value to apply() without exposing it in GUI? + label: PD.Text(MeshlistStateObject.type.name, { isHidden: true }), segmentId: PD.Numeric(1, {}, { isHidden: true }), segmentName: PD.Text('Segment'), detail: PD.Numeric(1, {}, { isHidden: true }), @@ -148,47 +152,32 @@ namespace MeshShapeProvider { return { label: 'Mesh', data: meshlist, - params: meshParamDef, // TODO how to pass the real params correctly? + params: meshShapeProviderParams, geometryUtils: Mesh.Utils, getShape: (ctx, data: MeshlistData) => MeshlistData.getShape(data, theColor), }; } } -/** Params for MeshShapeTransformer */ -const meshShapeParamDef = { - color: PD.Value<Color | undefined>(undefined), // undefined means random color -}; - -const meshParamDef: Mesh.Params = { - // These are basically original MS.Mesh.Params: - // BaseGeometry.Params - alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity', isEssential: true, description: 'How opaque/transparent the representation is rendered.' }), +const meshShapeProviderParams: Mesh.Params = { + ...Mesh.Params, quality: PD.Select<VisualQuality>('custom', VisualQualityOptions, { isEssential: true, description: 'Visual/rendering quality of the representation.' }), // use 'custom' when wanting to apply doubleSided - material: Material.getParam(), - clip: Mesh.Params.clip, // PD.Group(MS.Clip.Params), - instanceGranularity: PD.Boolean(false, { description: 'Use instance granularity for marker, transparency, clipping, overpaint, substance data to save memory.' }), - // Mesh.Params - doubleSided: PD.Boolean(true, BaseGeometry.CustomQualityParamInfo), // default: false (set true, to show at least something in weird cases) - flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory), - flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory), // default: false (set true to see the real mesh vertices and triangles) - ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory), - xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory), // this is like better opacity (angle-dependent), nice - transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque']), BaseGeometry.ShadingCategory), - bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory), - bumpAmplitude: PD.Numeric(1, { min: 0, max: 5, step: 0.1 }, BaseGeometry.ShadingCategory), - // TODO when I change values here, it has effect, but not if I change them in GUI + doubleSided: PD.Boolean(true, BaseGeometry.CustomQualityParamInfo), + // set `flatShaded`: true to see the real mesh vertices and triangles + transparentBackfaces: PD.Select('on', PD.arrayToOptions(['off', 'on', 'opaque']), BaseGeometry.ShadingCategory), // 'on' means: show backfaces with correct opacity, even when opacity < 1 (requires doubleSided) ÂŻ\_(ă„)_/ÂŻ }; + export const MeshShapeTransformer = VolsegTransform({ name: 'shape-from-meshlist', display: { name: 'Shape from Meshlist', description: 'Create Shape from Meshlist data' }, from: MeshlistStateObject, to: PluginStateObject.Shape.Provider, - params: meshShapeParamDef + params: { + color: PD.Value<Color | undefined>(undefined), // undefined means random color + }, })({ apply({ a, params }) { - // you can look for example at ShapeFromPly in mol-plugin-state/tansforms/model.ts as an example const shapeProvider = MeshShapeProvider.fromMeshlistData(a.data, params.color); return new PluginStateObject.Shape.Provider(shapeProvider, { label: PluginStateObject.Shape.Provider.type.name, description: a.description }); } @@ -196,32 +185,39 @@ export const MeshShapeTransformer = VolsegTransform({ // // // // // // // // // // // // // // // // // // // // // // // // -// Shape -> Repr - -// type MeshRepr = MS.PluginStateObject.Representation3DData<MS.ShapeRepresentation<MS.ShapeProvider<any,any,any>, MS.Mesh, MS.Mesh.Params>, any>; - -// export const CustomMeshReprTransformer = VolsegTransform({ -// name: 'custom-repr', -// from: MS.PluginStateObject.Shape.Provider, // later we can change this -// to: MS.PluginStateObject.Shape.Representation3D, -// })({ -// apply({ a }, globalCtx) { -// const repr: MeshRepr = createRepr(a.data); // TODO implement createRepr -// // have a look at MS.StateTransforms.Representation.ShapeRepresentation3D if you want to try implementing yourself -// return new MS.PluginStateObject.Shape.Representation3D(repr) -// }, -// }) - -// export async function createMeshRepr(plugin: MS.PluginContext, data: any) { -// await plugin.build() -// .toRoot() -// .apply(CreateMyShapeTransformer, { data }) -// .apply(MS.StateTransforms.Representation.ShapeRepresentation3D) // this should work -// // or .apply(CustomMeshRepr) -// .commit(); -// } - -// export function createRepr(reprData: MS.ShapeProvider<any,any,any>): MeshRepr { -// throw new Error('NotImplemented'); -// return {} as MeshRepr; -// } + + +/** Download data and create state tree hierarchy down to visual representation. */ +export async function createMeshFromUrl(plugin: PluginContext, meshDataUrl: string, segmentId: number, detail: number, + collapseTree: boolean, color?: Color, parent?: StateObjectSelector | StateObjectRef, transparentIfBboxAbove?: number, + name?: string, ownerId?: string) { + + const update = parent ? plugin.build().to(parent) : plugin.build().toRoot(); + const rawDataNodeRef = update.apply(Download, + { url: meshDataUrl, isBinary: true, label: `Downloaded Data ${segmentId}` }, + { state: { isCollapsed: collapseTree } } + ).ref; + const parsedDataNode = await update.to(rawDataNodeRef) + .apply(StateTransforms.Data.ParseCif) + .apply(ParseMeshlistTransformer, + { label: undefined, segmentId: segmentId, segmentName: name ?? `Segment ${segmentId}`, detail: detail, ownerId: ownerId }, + {} + ) + .commit(); + + let transparent = false; + if (transparentIfBboxAbove !== undefined && parsedDataNode.data) { + const bbox = MeshlistData.bbox(parsedDataNode.data) || Box3D.zero(); + transparent = Box3D.volume(bbox) > transparentIfBboxAbove; + } + + await plugin.build().to(parsedDataNode) + .apply(MeshShapeTransformer, { color: color },) + .apply(ShapeRepresentation3D, + { alpha: transparent ? BACKGROUND_OPACITY : FOREROUND_OPACITY }, + { tags: ['mesh-segment-visual', `segment-${segmentId}`] } + ) + .commit(); + + return rawDataNodeRef; +} diff --git a/src/extensions/meshes/mesh-streaming/behavior.ts b/src/extensions/meshes/mesh-streaming/behavior.ts index a52f0ece1ae5675487d332352fe10edede70d3ae..f24784a80a2bfb997e5727cc22aa576c0ed89e0f 100644 --- a/src/extensions/meshes/mesh-streaming/behavior.ts +++ b/src/extensions/meshes/mesh-streaming/behavior.ts @@ -19,9 +19,10 @@ import { Color } from '../../../mol-util/color'; import { ColorNames } from '../../../mol-util/color/names'; import { ParamDefinition as PD } from '../../../mol-util/param-definition'; -import { Choice } from '../choice'; +import { Choice } from '../../volumes-and-segmentations/helpers'; +import { MetadataWrapper } from '../../volumes-and-segmentations/volseg-api/utils'; + import { MeshlistData } from '../mesh-extension'; -import { Metadata } from '../metadata'; import { MeshServerInfo } from './server-info'; @@ -33,8 +34,6 @@ const MAX_DETAIL = 10; const DEFAULT_DETAIL = 7; // TODO decide a reasonable default /** Segments whose bounding box volume is above this value (relative to the overall bounding box) are considered as background segments */ export const BACKGROUND_SEGMENT_VOLUME_THRESHOLD = 0.5; -// const DEBUG_IGNORED_SEGMENTS = new Set([13, 15]); // TODO remove -const DEBUG_IGNORED_SEGMENTS = new Set(); // TODO remove export class MeshStreaming extends PluginStateObject.CreateBehavior<MeshStreaming.Behavior>({ name: 'Mesh Streaming' }) { } @@ -116,7 +115,7 @@ export namespace MeshStreaming { private id: string; private ref: string = ''; public parentData: MeshServerInfo.Data; - private metadata?: Metadata; + private metadata?: MetadataWrapper; public visuals?: { [tag: string]: VisualInfo }; public backgroundSegments: { [segmentId: number]: boolean } = {}; private focusObservable = this.plugin.behaviors.interaction.click.pipe( // QUESTION is this OK way to get focused segment? @@ -165,7 +164,8 @@ export namespace MeshStreaming { if (!this.metadata) { const response = await fetch(this.getMetadataUrl()); - this.metadata = await response.json(); + const rawMetadata = await response.json(); + this.metadata = new MetadataWrapper(rawMetadata); } if (!this.visuals) { @@ -209,13 +209,10 @@ export namespace MeshStreaming { } private initVisualInfos() { - const namesAndColors = Metadata.namesAndColorsBySegment(this.metadata!); - const visuals: { [tag: string]: VisualInfo } = {}; - for (const segid of Metadata.meshSegments(this.metadata!)) { - if (DEBUG_IGNORED_SEGMENTS.has(segid)) continue; - const name = namesAndColors[segid]?.name ?? DEFAULT_SEGMENT_NAME; - const color = namesAndColors[segid]?.color ?? DEFAULT_SEGMENT_COLOR; + for (const segid of this.metadata!.meshSegmentIds) { + const name = this.metadata?.getSegment(segid)?.biological_annotation.name ?? DEFAULT_SEGMENT_NAME; + const color = this.metadata?.getSegmentColor(segid) ?? DEFAULT_SEGMENT_COLOR; for (const detailType of VisualInfo.DetailTypes) { const visual: VisualInfo = { tag: VisualInfo.tagFor(segid, detailType), @@ -254,7 +251,7 @@ export namespace MeshStreaming { const visual = this.visuals[tag]; const preferredDetail = (visual.detailType === 'high') ? highDetail : lowDetail; if (preferredDetail !== undefined) { - visual.detail = Metadata.getSufficientDetail(this.metadata!, visual.segmentId, preferredDetail); + visual.detail = this.metadata!.getSufficientMeshDetail(visual.segmentId, preferredDetail); } } } diff --git a/src/extensions/meshes/mesh-streaming/server-info.ts b/src/extensions/meshes/mesh-streaming/server-info.ts index b85315a1c5618b4b9c2129b7ba52692442f54177..400dd5d51b777c8ce1786d324e2345c1004d420b 100644 --- a/src/extensions/meshes/mesh-streaming/server-info.ts +++ b/src/extensions/meshes/mesh-streaming/server-info.ts @@ -7,7 +7,7 @@ import { PluginStateObject } from '../../../mol-plugin-state/objects'; import { ParamDefinition as PD } from '../../../mol-util/param-definition'; -import { Choice } from '../choice'; +import { Choice } from '../../volumes-and-segmentations/helpers'; export const DEFAULT_MESH_SERVER = 'http://localhost:9000/v2'; diff --git a/src/extensions/meshes/mesh-streaming/transformers.ts b/src/extensions/meshes/mesh-streaming/transformers.ts index 6f64eb463d9a744d18b87dd32d3a59c1affc769b..34c43374c69cfeb689d0bfe9a89bf59960eb3b0f 100644 --- a/src/extensions/meshes/mesh-streaming/transformers.ts +++ b/src/extensions/meshes/mesh-streaming/transformers.ts @@ -13,15 +13,11 @@ import { Task } from '../../../mol-task'; import { shallowEqualObjects } from '../../../mol-util'; import { ParamDefinition as PD } from '../../../mol-util/param-definition'; -import { VolsegTransform, MeshlistData } from '../mesh-extension'; +import { BACKGROUND_OPACITY, FOREROUND_OPACITY, MeshlistData, VolsegTransform } from '../mesh-extension'; import { MeshStreaming, NO_SEGMENT } from './behavior'; import { MeshServerInfo } from './server-info'; -export const BACKGROUND_OPACITY = 0.2; -export const FOREROUND_OPACITY = 1; - - // // // // // // // // // // // // // // // // // // // // // // // // export const MeshServerTransformer = VolsegTransform({ diff --git a/src/extensions/meshes/metadata.ts b/src/extensions/meshes/metadata.ts deleted file mode 100644 index ba43707d7719c8e0519bc18fcde86ee165adef32..0000000000000000000000000000000000000000 --- a/src/extensions/meshes/metadata.ts +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Adam Midlik <midlik@gmail.com> - */ - - -import { Color } from '../../mol-util/color'; - - -// TODO unify with Metadata in Volseg - -export interface Metadata { - grid: { - general: { - details: string, - }, - volumes: Volumes, - segmentation_lattices: SegmentationLattices, - segmentation_meshes: SegmentationMeshes, - }, - annotation: Annotation, -} - -export interface Volumes { - volume_downsamplings: number[], - voxel_size: { [downsampling: number]: Vector3 }, - origin: Vector3, - grid_dimensions: Vector3, - sampled_grid_dimensions: { [downsampling: number]: Vector3 }, - mean: { [downsampling: number]: number }, - std: { [downsampling: number]: number }, - min: { [downsampling: number]: number }, - max: { [downsampling: number]: number }, - volume_force_dtype: string, -} - -export interface SegmentationLattices { - segmentation_lattice_ids: number[], - segmentation_downsamplings: { [lattice: number]: number[] }, -} - -export interface Annotation { - name: string, - details: string, - segment_list: Segment[], -} - -export interface Segment { - id: number, - colour: number[], - biological_annotation: BiologicalAnnotation, -} - -export interface BiologicalAnnotation { - name: string, - external_references: { id: number, resource: string, accession: string, label: string, description: string }[] -} - -export interface SegmentationMeshes { - mesh_component_numbers: { - segment_ids?: { - [segId: number]: { - detail_lvls: { - [detail: number]: { - mesh_ids: { - [meshId: number]: { - num_triangles: number, - num_vertices: number - } - } - } - } - } - } - } - detail_lvl_to_fraction: { - [lvl: number]: number - } -} - -type Vector3 = [number, number, number]; - - - -export namespace Metadata { - export function meshSegments(metadata: Metadata): number[] { - const segmentIds = metadata.grid.segmentation_meshes.mesh_component_numbers.segment_ids; - if (segmentIds === undefined) return []; - return Object.keys(segmentIds).map(s => parseInt(s)); - } - export function meshSegmentDetails(metadata: Metadata, segmentId: number): number[] { - const segmentIds = metadata.grid.segmentation_meshes.mesh_component_numbers.segment_ids; - if (segmentIds === undefined) return []; - const details = segmentIds[segmentId].detail_lvls; - return Object.keys(details).map(s => parseInt(s)); - } - /** Get the worst available detail level that is not worse than preferredDetail. - * If preferredDetail is null, get the worst detail level overall. - * (worse = greater number) */ - export function getSufficientDetail(metadata: Metadata, segmentId: number, preferredDetail: number | null) { - let availDetails = meshSegmentDetails(metadata, segmentId); - if (preferredDetail !== null) { - availDetails = availDetails.filter(det => det <= preferredDetail); - } - return Math.max(...availDetails); - } - export function annotationsBySegment(metadata: Metadata): { [id: number]: Segment } { - const result: { [id: number]: Segment } = {}; - for (const segment of metadata.annotation.segment_list) { - if (segment.id in result) { - throw new Error(`Duplicate segment annotation for segment ${segment.id}`); - } - result[segment.id] = segment; - } - return result; - } - export function dropSegments(metadata: Metadata, segments: number[]): void { - if (metadata.grid.segmentation_meshes.mesh_component_numbers.segment_ids === undefined) return; - const dropSet = new Set(segments); - metadata.annotation.segment_list = metadata.annotation.segment_list.filter(seg => !dropSet.has(seg.id)); - for (const seg of segments) { - delete metadata.grid.segmentation_meshes.mesh_component_numbers.segment_ids[seg]; - } - } - export function namesAndColorsBySegment(metadata: Metadata) { - const result: { [id: number]: { name: string, color: Color } } = {}; - for (const segment of metadata.annotation.segment_list) { - if (segment.id in result) throw new Error(`Duplicate segment annotation for segment ${segment.id}`); - result[segment.id] = { name: segment.biological_annotation.name, color: Color.fromNormalizedArray(segment.colour, 0) }; - } - return result; - - } -} \ No newline at end of file diff --git a/src/extensions/volumes-and-segmentations/entry-meshes.ts b/src/extensions/volumes-and-segmentations/entry-meshes.ts index 15a75d66c956ac45af5931b13ebe82125d7e6596..bf106ea7abd3425c3900a49fb0294a1ca72ff61f 100644 --- a/src/extensions/volumes-and-segmentations/entry-meshes.ts +++ b/src/extensions/volumes-and-segmentations/entry-meshes.ts @@ -12,8 +12,8 @@ import { PluginCommands } from '../../mol-plugin/commands'; import { Color } from '../../mol-util/color'; import { ColorNames } from '../../mol-util/color/names'; -import { createMeshFromUrl } from '../meshes/examples'; import { BACKGROUND_SEGMENT_VOLUME_THRESHOLD } from '../meshes/mesh-streaming/behavior'; +import { createMeshFromUrl } from '../meshes/mesh-extension'; import { Segment } from './volseg-api/data'; import { VolsegEntryData } from './entry-root'; diff --git a/src/extensions/volumes-and-segmentations/volseg-api/utils.ts b/src/extensions/volumes-and-segmentations/volseg-api/utils.ts index f3576bc71cbcd95c8275799b42c5e76518f42099..d138bf5abe7e3922927a0f8fc7d8621fb64f3361 100644 --- a/src/extensions/volumes-and-segmentations/volseg-api/utils.ts +++ b/src/extensions/volumes-and-segmentations/volseg-api/utils.ts @@ -4,6 +4,7 @@ * @author Adam Midlik <midlik@gmail.com> */ +import { Color } from '../../../mol-util/color'; import { Metadata, Segment } from './data'; @@ -33,6 +34,11 @@ export class MetadataWrapper { return this.segmentMap[segmentId]; } + getSegmentColor(segmentId: number): Color | undefined { + const colorArray = this.getSegment(segmentId)?.colour; + return colorArray ? Color.fromNormalizedArray(colorArray, 0) : undefined; + } + /** Get the list of detail levels available for the given mesh segment. */ getMeshDetailLevels(segmentId: number): number[] { const segmentIds = this.raw.grid.segmentation_meshes.mesh_component_numbers.segment_ids; @@ -65,4 +71,4 @@ export class MetadataWrapper { return vx * vy * vz * gx * gy * gz; } -} \ No newline at end of file +}