diff --git a/src/apps/canvas/app.ts b/src/apps/canvas/app.ts index 3615bbc0e4dcdbd0bb497f524821deebdecfe0f3..ea0c7f2ff39c97c84fe68886d5bdb684417c5673 100644 --- a/src/apps/canvas/app.ts +++ b/src/apps/canvas/app.ts @@ -5,10 +5,14 @@ */ import Viewer from 'mol-view/viewer'; -import { getCifFromUrl, getModelsFromMmcif, getCifFromFile } from './util'; +import { getCifFromUrl, getModelsFromMmcif, getCifFromFile, getCcp4FromUrl } from './util'; import { StructureView } from './structure-view'; import { BehaviorSubject } from 'rxjs'; import { CifBlock } from 'mol-io/reader/cif'; +import { volumeFromCcp4 } from 'mol-model/volume/formats/ccp4'; +import { VolumeRepresentation } from 'mol-geo/representation/volume'; +import SurfaceVisual from 'mol-geo/representation/volume/surface'; +import { VolumeIsoValue } from 'mol-model/volume'; export class App { viewer: Viewer @@ -55,23 +59,41 @@ export class App { return result } - async loadCif(cif: CifBlock, assemblyId?: string) { + // + + async loadMmcif(cif: CifBlock, assemblyId?: string) { const models = await this.runTask(getModelsFromMmcif(cif), 'Build models') this.structureView = await this.runTask(StructureView(this, this.viewer, models, { assemblyId }), 'Init structure view') this.pdbIdLoaded.next(this.structureView) } - async loadPdbIdOrUrl(idOrUrl: string, options?: { assemblyId?: string, binary?: boolean }) { + async loadPdbIdOrMmcifUrl(idOrUrl: string, options?: { assemblyId?: string, binary?: boolean }) { if (this.structureView) this.structureView.destroy(); const url = idOrUrl.length <= 4 ? `https://files.rcsb.org/download/${idOrUrl}.cif` : idOrUrl; const cif = await this.runTask(getCifFromUrl(url, options ? !!options.binary : false), 'Load mmCIF from URL') - this.loadCif(cif, options ? options.assemblyId : void 0) + this.loadMmcif(cif, options ? options.assemblyId : void 0) } - async loadCifFile(file: File) { + async loadMmcifFile(file: File) { if (this.structureView) this.structureView.destroy(); const binary = /\.bcif$/.test(file.name); const cif = await this.runTask(getCifFromFile(file, binary), 'Load mmCIF from file') - this.loadCif(cif) + this.loadMmcif(cif) + } + + // + + async loadCcp4File() { + const url = 'http://localhost:8091/ngl/data/betaGal.mrc' + const ccp4 = await getCcp4FromUrl(url) + console.log(ccp4) + const volume = await volumeFromCcp4(ccp4).run() + const volRepr = VolumeRepresentation(SurfaceVisual) + await volRepr.createOrUpdate({ + isoValue: VolumeIsoValue.relative(volume.dataStats, 1) + }, volume).run() + this.viewer.add(volRepr) + console.log('volRepr', volRepr) + this.viewer.requestDraw(true) } } \ No newline at end of file diff --git a/src/apps/canvas/component/app.tsx b/src/apps/canvas/component/app.tsx index bbfcd4b1ca11ac6ec8784907fbd3f44bbdd0bca5..bc15e244bee3d02e4e7d226965cbb1d3513a274f 100644 --- a/src/apps/canvas/component/app.tsx +++ b/src/apps/canvas/component/app.tsx @@ -53,7 +53,7 @@ export class AppComponent extends React.Component<AppProps, AppState> { if (e.keyCode === 13) { const value = e.currentTarget.value.trim() if (value) { - this.props.app.loadPdbIdOrUrl(value, { binary: this.state.binary }) + this.props.app.loadPdbIdOrMmcifUrl(value, { binary: this.state.binary }) } } }} @@ -65,7 +65,7 @@ export class AppComponent extends React.Component<AppProps, AppState> { accept='*.cif' type='file' onChange={e => { - if (e.target.files) this.props.app.loadCifFile(e.target.files[0]) + if (e.target.files) this.props.app.loadMmcifFile(e.target.files[0]) }} /> </div> @@ -74,7 +74,7 @@ export class AppComponent extends React.Component<AppProps, AppState> { <select style={{width: '200px'}} onChange={e => { - this.props.app.loadPdbIdOrUrl(e.target.value) + this.props.app.loadPdbIdOrMmcifUrl(e.target.value) }} > <option value=''></option> diff --git a/src/apps/canvas/component/volume-representation.tsx b/src/apps/canvas/component/volume-representation.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b22b95667622e931c0cbf5047c554fbcbe23ef0b --- /dev/null +++ b/src/apps/canvas/component/volume-representation.tsx @@ -0,0 +1,245 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as React from 'react' +import Viewer from 'mol-view/viewer'; +import { ColorThemeProps, ColorThemeName, ColorThemeNames, ColorTheme } from 'mol-view/theme/color'; +import { Color } from 'mol-util/color'; +import { Progress } from 'mol-task'; +import { VisualQuality, VisualQualityNames } from 'mol-geo/geometry/geometry'; +import { SizeThemeProps } from 'mol-view/theme/size'; +import { App } from '../app'; +import { VolumeRepresentation, VolumeProps } from 'mol-geo/representation/volume'; + +export interface VolumeRepresentationComponentProps { + app: App + viewer: Viewer + representation: VolumeRepresentation<VolumeProps> +} + +export interface VolumeRepresentationComponentState { + label: string + visible: boolean + alpha: number + quality: VisualQuality + colorTheme: ColorThemeProps + depthMask: boolean + + flatShaded?: boolean + resolutionFactor?: number + radiusOffset?: number + smoothness?: number + pointSizeAttenuation?: boolean + pointFilledCircle?: boolean + pointEdgeBleach?: number + + visuals?: { [k: string]: boolean } +} + +export class VolumeRepresentationComponent extends React.Component<VolumeRepresentationComponentProps, VolumeRepresentationComponentState> { + state = this.stateFromRepresentation(this.props.representation) + + private stateFromRepresentation(repr: VolumeRepresentation<VolumeProps>) { + return { + label: repr.label, + visible: repr.props.visible, + alpha: repr.props.alpha, + quality: repr.props.quality, + colorTheme: repr.props.colorTheme, + depthMask: repr.props.depthMask, + + flatShaded: (repr.props as any).flatShaded, + resolutionFactor: (repr.props as any).resolutionFactor, + radiusOffset: (repr.props as any).radiusOffset, + smoothness: (repr.props as any).smoothness, + pointSizeAttenuation: (repr.props as any).pointSizeAttenuation, + pointFilledCircle: (repr.props as any).pointFilledCircle, + pointEdgeBleach: (repr.props as any).pointEdgeBleach, + + visuals: (repr.props as any).visuals, + } + } + + componentWillMount() { + this.setState(this.stateFromRepresentation(this.props.representation)) + } + + async update(state: Partial<VolumeRepresentationComponentState>) { + const repr = this.props.representation + const props: Partial<VolumeProps> = {} + + if (state.visible !== undefined) props.visible = state.visible + if (state.quality !== undefined) props.quality = state.quality + if (state.alpha !== undefined) props.alpha = state.alpha + if (state.colorTheme !== undefined) props.colorTheme = state.colorTheme + if (state.depthMask !== undefined) props.depthMask = state.depthMask + + if (state.flatShaded !== undefined) (props as any).flatShaded = state.flatShaded + if (state.resolutionFactor !== undefined) (props as any).resolutionFactor = state.resolutionFactor + if (state.radiusOffset !== undefined) (props as any).radiusOffset = state.radiusOffset + if (state.smoothness !== undefined) (props as any).smoothness = state.smoothness + if (state.pointSizeAttenuation !== undefined) (props as any).pointSizeAttenuation = state.pointSizeAttenuation + if (state.pointFilledCircle !== undefined) (props as any).pointFilledCircle = state.pointFilledCircle + if (state.pointEdgeBleach !== undefined) (props as any).pointEdgeBleach = state.pointEdgeBleach + + if (state.visuals !== undefined) (props as any).visuals = state.visuals + + await this.props.app.runTask(repr.createOrUpdate(props).run( + progress => console.log(Progress.format(progress)) + ), 'Create/update representation') + this.props.viewer.add(repr) + this.props.viewer.draw(true) + + this.setState(this.stateFromRepresentation(repr)) + } + + render() { + const { label, visible, quality, alpha, colorTheme, depthMask } = this.state + const ct = ColorTheme(colorTheme) + + return <div> + <div> + <h4>{label}</h4> + </div> + <div> + <div> + <span>Visible </span> + <button onClick={(e) => this.update({ visible: !visible }) }> + {visible ? 'Hide' : 'Show'} + </button> + </div> + { this.state.visuals !== undefined ? <div> + <span>Visuals: </span> + { Object.keys(this.state.visuals).map(k => { + return <span key={k}>{k} <input + type='checkbox' + checked={this.state.visuals[k]} + onChange={e => { + this.update({ visuals: { ...this.state.visuals, [k]: !!e.target.checked } }) + }} + ></input> </span> + }) } + </div> : '' } + <div> + <span>Depth Mask </span> + <button onClick={(e) => this.update({ depthMask: !depthMask }) }> + {depthMask ? 'Deactivate' : 'Activate'} + </button> + </div> + { this.state.flatShaded !== undefined ? <div> + <span>Flat Shaded </span> + <button onClick={(e) => this.update({ flatShaded: !this.state.flatShaded }) }> + {this.state.flatShaded ? 'Deactivate' : 'Activate'} + </button> + </div> : '' } + <div> + <span>Quality </span> + <select value={quality} onChange={e => this.update({ quality: e.target.value as VisualQuality }) }> + {VisualQualityNames.map(name => <option key={name} value={name}>{name}</option>)} + </select> + </div> + <div> + <span>Opacity </span> + <input type='range' + defaultValue={alpha.toString()} + min='0' + max='1' + step='0.05' + onInput={e => this.update({ alpha: parseFloat(e.currentTarget.value) })} + > + </input> + </div> + { this.state.resolutionFactor !== undefined ? <div> + <span>Resolution Factor </span> + <input type='range' + defaultValue={this.state.resolutionFactor.toString()} + min='4' + max='9' + step='1' + onChange={(e) => this.update({ resolutionFactor: parseInt(e.currentTarget.value) })} + > + </input> + </div> : '' } + { this.state.smoothness !== undefined ? <div> + <span>Smoothness </span> + <input type='range' + defaultValue={this.state.smoothness.toString()} + min='1' + max='3' + step='0.1' + onChange={e => this.update({ smoothness: parseFloat(e.currentTarget.value) })} + > + </input> + </div> : '' } + { this.state.radiusOffset !== undefined ? <div> + <span>Radius Offset </span> + <input type='range' + defaultValue={this.state.radiusOffset.toString()} + min='0' + max='4' + step='0.1' + onChange={e => this.update({ radiusOffset: parseFloat(e.currentTarget.value) })} + > + </input> + </div> : '' } + { this.state.pointSizeAttenuation !== undefined ? <div> + <span>Size Attenuation </span> + <button onClick={e => this.update({ pointSizeAttenuation: !this.state.pointSizeAttenuation }) }> + {this.state.pointSizeAttenuation ? 'Deactivate' : 'Activate'} + </button> + </div> : '' } + { this.state.pointFilledCircle !== undefined ? <div> + <span>Filled Circle </span> + <button onClick={e => this.update({ pointFilledCircle: !this.state.pointFilledCircle }) }> + {this.state.pointFilledCircle ? 'Deactivate' : 'Activate'} + </button> + </div> : '' } + { this.state.pointEdgeBleach !== undefined ? <div> + <span>Edge Bleach </span> + <input type='range' + defaultValue={this.state.pointEdgeBleach.toString()} + min='0' + max='1' + step='0.05' + onInput={e => this.update({ pointEdgeBleach: parseFloat(e.currentTarget.value) })} + > + </input> + </div> : '' } + <div> + <span>Color Theme </span> + <select value={colorTheme.name} onChange={e => this.update({ colorTheme: { name: e.target.value as ColorThemeName } }) }> + {ColorThemeNames.map(name => <option key={name} value={name}>{name}</option>)} + </select> + {ct.description ? <div><i>{ct.description}</i></div> : ''} + { + ct.legend && ct.legend.kind === 'scale-legend' + ? <div + style={{ + width: '100%', + height: '30px', + background: `linear-gradient(to right, ${ct.legend.colors.map(c => Color.toStyle(c)).join(', ')})` + }} + > + <span style={{float: 'left', padding: '6px', color: 'white', fontWeight: 'bold', backgroundColor: 'rgba(0, 0, 0, 0.2)'}}>{ct.legend.minLabel}</span> + <span style={{float: 'right', padding: '6px', color: 'white', fontWeight: 'bold', backgroundColor: 'rgba(0, 0, 0, 0.2)'}}>{ct.legend.maxLabel}</span> + </div> + : ct.legend && ct.legend.kind === 'table-legend' + ? <div> + {ct.legend.table.map((value, i) => { + const [name, color] = value + return <div key={i} style={{minWidth: '60px', marginRight: '5px', display: 'inline-block'}}> + <div style={{width: '30px', height: '20px', backgroundColor: Color.toStyle(color), display: 'inline-block'}}></div> + {name} + </div> + })} + </div> + : '' + } + </div> + </div> + </div>; + } +} \ No newline at end of file diff --git a/src/apps/canvas/examples.ts b/src/apps/canvas/examples.ts index 8eb1c4dca394e35f692f63223b28db0ce0b31943..d998f9b6bc254817cb08592a6833307a7e7f7fe2 100644 --- a/src/apps/canvas/examples.ts +++ b/src/apps/canvas/examples.ts @@ -43,10 +43,18 @@ export const Examples: Example[] = [ id: '4v5a', description: 'ribosome' }, + { + id: '6h7w', + description: 'retromer assembled on membrane' + }, { id: '3j3q', description: '...' }, + { + id: '5gob', + description: 'D-aminoacids' + }, { id: '2np2', description: 'dna' diff --git a/src/apps/canvas/index.ts b/src/apps/canvas/index.ts index 05ec4d3732a6e6f1dd3fec80a97770580008d7b7..76e32dee292221aa544ec2a4a4d912696399b5a8 100644 --- a/src/apps/canvas/index.ts +++ b/src/apps/canvas/index.ts @@ -21,4 +21,6 @@ ReactDOM.render(React.createElement(AppComponent, { app }), elm); const assemblyId = urlQueryParameter('assembly') const pdbId = urlQueryParameter('pdb') -if (pdbId) app.loadPdbIdOrUrl(pdbId, { assemblyId }) \ No newline at end of file +if (pdbId) app.loadPdbIdOrMmcifUrl(pdbId, { assemblyId }) + +app.loadCcp4File() \ No newline at end of file diff --git a/src/apps/canvas/util.ts b/src/apps/canvas/util.ts index 577c3a3da2c6588b3ddff7a6fc630747bde55d6a..226785421fafbb0e93aa98f1a58cccd0e41534a8 100644 --- a/src/apps/canvas/util.ts +++ b/src/apps/canvas/util.ts @@ -4,9 +4,11 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ +import { readUrl, readFile, readUrlAsBuffer } from 'mol-util/read'; import CIF, { CifBlock } from 'mol-io/reader/cif' -import { readUrlAs, readFileAs } from 'mol-util/read'; import { Model, Format, StructureSymmetry, Structure } from 'mol-model/structure'; +import CCP4 from 'mol-io/reader/ccp4/parser' +import { FileHandle } from 'mol-io/common/file-handle'; // import { parse as parseObj } from 'mol-io/reader/obj/parser' // export async function getObjFromUrl(url: string) { @@ -25,11 +27,11 @@ export async function getCifFromData(data: string | Uint8Array) { } export async function getCifFromUrl(url: string, binary = false) { - return getCifFromData(await readUrlAs(url, binary)) + return getCifFromData(await readUrl(url, binary)) } export async function getCifFromFile(file: File, binary = false) { - return getCifFromData(await readFileAs(file, binary)) + return getCifFromData(await readFile(file, binary)) } export async function getModelsFromMmcif(cif: CifBlock) { @@ -43,4 +45,17 @@ export async function getStructureFromModel(model: Model, assembly: string) { } else if (assemblies.find(a => a.id === assembly)) { return await StructureSymmetry.buildAssembly(Structure.ofModel(model), assembly).run() } +} + +// + +export async function getCcp4FromUrl(url: string) { + return getCcp4FromData(await readUrlAsBuffer(url)) +} + +export async function getCcp4FromData(data: Uint8Array) { + const file = FileHandle.fromBuffer(data) + const parsed = await CCP4(file).run() + if (parsed.isError) throw parsed + return parsed.result } \ No newline at end of file diff --git a/src/mol-geo/geometry/geometry.ts b/src/mol-geo/geometry/geometry.ts index ceab1fe7c8474eb912f74ad286792f995118ab8d..e8b1bfcf602b4e7e56cb980edcf9c77d2e1be9a8 100644 --- a/src/mol-geo/geometry/geometry.ts +++ b/src/mol-geo/geometry/geometry.ts @@ -10,11 +10,31 @@ import { RenderableState } from 'mol-gl/renderable'; import { ValueCell } from 'mol-util'; import { BaseValues } from 'mol-gl/renderable/schema'; import { Color } from 'mol-util/color'; -import { ColorThemeProps } from 'mol-view/theme/color'; +import { ColorThemeOptions, ColorThemeName } from 'mol-view/theme/color'; import { LocationIterator } from '../util/location-iterator'; import { ColorType } from './color-data'; import { SizeType } from './size-data'; import { Lines } from './lines/lines'; +import { paramDefaultValues, RangeParam, CheckboxParam, SelectParam, ColorParam } from 'mol-view/parameter' + +// + +export const VisualQualityInfo = { + 'custom': {}, + 'auto': {}, + 'highest': {}, + 'higher': {}, + 'high': {}, + 'medium': {}, + 'low': {}, + 'lower': {}, + 'lowest': {}, +} +export type VisualQuality = keyof typeof VisualQualityInfo +export const VisualQualityNames = Object.keys(VisualQualityInfo) +export const VisualQualityOptions = VisualQualityNames.map(n => [n, n] as [VisualQuality, string]) + +// export type GeometryKindType = { 'mesh': Mesh, 'points': Points, 'lines': Lines } export type GeometryKind = keyof GeometryKindType @@ -31,14 +51,16 @@ export namespace Geometry { // - export const DefaultProps = { - alpha: 1, - visible: true, - depthMask: true, - useFog: false, - quality: 'auto' as VisualQuality, - colorTheme: { name: 'uniform', value: Color(0x22EE11) } as ColorThemeProps, + export const Params = { + alpha: RangeParam('Opacity', '', 1, 0, 1, 0.01), + visible: CheckboxParam('Visible', '', true), + depthMask: CheckboxParam('Depth Mask', '', true), + useFog: CheckboxParam('Use Fog', '', false), + quality: SelectParam<VisualQuality>('Quality', '', 'auto', VisualQualityOptions), + colorTheme: SelectParam<ColorThemeName>('Color Theme', '', 'uniform', ColorThemeOptions), + colorValue: ColorParam('Color Value', '', Color(0xCCCCCC)), } + export const DefaultProps = paramDefaultValues(Params) export type Props = typeof DefaultProps export type Counts = { drawCount: number, groupCount: number, instanceCount: number } @@ -78,20 +100,4 @@ export function getGranularity(locationIt: LocationIterator, granularity: ColorT // Always use 'group' granularity for 'complex' location iterators, // i.e. for which an instance may include multiple units return granularity === 'instance' && locationIt.isComplex ? 'group' : granularity -} - -// - -export const VisualQualityInfo = { - 'custom': {}, - 'auto': {}, - 'highest': {}, - 'higher': {}, - 'high': {}, - 'medium': {}, - 'low': {}, - 'lower': {}, - 'lowest': {}, -} -export type VisualQuality = keyof typeof VisualQualityInfo -export const VisualQualityNames = Object.keys(VisualQualityInfo) \ No newline at end of file +} \ No newline at end of file diff --git a/src/mol-geo/geometry/lines/lines.ts b/src/mol-geo/geometry/lines/lines.ts index 739dd878043486ac3c0bb4375a1b6ac2281d5907..b3b630de9df2528fbb1dc06bcfbbba8167e9d41c 100644 --- a/src/mol-geo/geometry/lines/lines.ts +++ b/src/mol-geo/geometry/lines/lines.ts @@ -14,10 +14,11 @@ import { createMarkers } from '../marker-data'; import { createSizes } from '../size-data'; import { TransformData } from '../transform-data'; import { LocationIterator } from '../../util/location-iterator'; -import { SizeThemeProps } from 'mol-view/theme/size'; +import { SizeThemeName, SizeThemeOptions } from 'mol-view/theme/size'; import { LinesValues } from 'mol-gl/renderable/lines'; import { Mesh } from '../mesh/mesh'; import { LinesBuilder } from './lines-builder'; +import { CheckboxParam, SelectParam, NumberParam, paramDefaultValues } from 'mol-view/parameter'; /** Wide line */ export interface Lines { @@ -90,17 +91,19 @@ export namespace Lines { // - export const DefaultProps = { - ...Geometry.DefaultProps, - lineSizeAttenuation: false, - sizeTheme: { name: 'uniform', value: 1 } as SizeThemeProps, + export const Params = { + ...Geometry.Params, + lineSizeAttenuation: CheckboxParam('Line Size Attenuation', '', false), + sizeTheme: SelectParam<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions), + sizeValue: NumberParam('Size Value', '', 1, 0, 0.1, 20), } + export const DefaultProps = paramDefaultValues(Params) export type Props = typeof DefaultProps export async function createValues(ctx: RuntimeContext, lines: Lines, transform: TransformData, locationIt: LocationIterator, props: Props): Promise<LinesValues> { const { instanceCount, groupCount } = locationIt - const color = await createColors(ctx, locationIt, props.colorTheme) - const size = await createSizes(ctx, locationIt, props.sizeTheme) + const color = await createColors(ctx, locationIt, { name: props.colorTheme, value: props.colorValue }) + const size = await createSizes(ctx, locationIt, { name: props.sizeTheme, value: props.sizeValue }) const marker = createMarkers(instanceCount * groupCount) const counts = { drawCount: lines.lineCount * 2 * 3, groupCount, instanceCount } diff --git a/src/mol-geo/geometry/mesh/mesh.ts b/src/mol-geo/geometry/mesh/mesh.ts index 675647f565f7e5813a77e111832ea46ec26e0119..b7a8c7c1910c81c833f3f0e6f55286ea9c8a8c49 100644 --- a/src/mol-geo/geometry/mesh/mesh.ts +++ b/src/mol-geo/geometry/mesh/mesh.ts @@ -16,6 +16,7 @@ import { TransformData } from '../transform-data'; import { LocationIterator } from '../../util/location-iterator'; import { createColors } from '../color-data'; import { ChunkedArray } from 'mol-data/util'; +import { CheckboxParam, paramDefaultValues } from 'mol-view/parameter'; export interface Mesh { readonly kind: 'mesh', @@ -314,17 +315,18 @@ export namespace Mesh { // - export const DefaultProps = { - ...Geometry.DefaultProps, - doubleSided: false, - flipSided: false, - flatShaded: false, + export const Params = { + ...Geometry.Params, + doubleSided: CheckboxParam('Double Sided', '', false), + flipSided: CheckboxParam('Flip Sided', '', false), + flatShaded: CheckboxParam('Flat Shaded', '', false), } + export const DefaultProps = paramDefaultValues(Params) export type Props = typeof DefaultProps export async function createValues(ctx: RuntimeContext, mesh: Mesh, transform: TransformData, locationIt: LocationIterator, props: Props): Promise<MeshValues> { const { instanceCount, groupCount } = locationIt - const color = await createColors(ctx, locationIt, props.colorTheme) + const color = await createColors(ctx, locationIt, { name: props.colorTheme, value: props.colorValue }) const marker = createMarkers(instanceCount * groupCount) const counts = { drawCount: mesh.triangleCount * 3, groupCount, instanceCount } diff --git a/src/mol-geo/geometry/points/points.ts b/src/mol-geo/geometry/points/points.ts index e3bad473a4d1632a0fbfdb2e7ffcc2270b7f931f..cd805c2eb3bef9199397d60aac38bf3a13af9ea6 100644 --- a/src/mol-geo/geometry/points/points.ts +++ b/src/mol-geo/geometry/points/points.ts @@ -15,7 +15,8 @@ import { createMarkers } from '../marker-data'; import { createSizes } from '../size-data'; import { TransformData } from '../transform-data'; import { LocationIterator } from '../../util/location-iterator'; -import { SizeThemeProps } from 'mol-view/theme/size'; +import { SizeThemeProps, SizeThemeName, SizeThemeOptions } from 'mol-view/theme/size'; +import { CheckboxParam, NumberParam, SelectParam, paramDefaultValues } from 'mol-view/parameter'; /** Point cloud */ export interface Points { @@ -52,19 +53,21 @@ export namespace Points { // - export const DefaultProps = { - ...Geometry.DefaultProps, - pointSizeAttenuation: false, - pointFilledCircle: false, - pointEdgeBleach: 0.2, - sizeTheme: { name: 'uniform', value: 1 } as SizeThemeProps, + export const Params = { + ...Geometry.Params, + pointSizeAttenuation: CheckboxParam('Point Size Attenuation', '', false), + pointFilledCircle: CheckboxParam('Point Filled Circle', '', false), + pointEdgeBleach: NumberParam('Point Edge Bleach', '', 0.2, 0, 0.05, 1), + sizeTheme: SelectParam<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions), + sizeValue: NumberParam('Size Value', '', 1, 0, 0.1, 20), } + export const DefaultProps = paramDefaultValues(Params) export type Props = typeof DefaultProps export async function createValues(ctx: RuntimeContext, points: Points, transform: TransformData, locationIt: LocationIterator, props: Props): Promise<PointsValues> { const { instanceCount, groupCount } = locationIt - const color = await createColors(ctx, locationIt, props.colorTheme) - const size = await createSizes(ctx, locationIt, props.sizeTheme) + const color = await createColors(ctx, locationIt, { name: props.colorTheme, value: props.colorValue }) + const size = await createSizes(ctx, locationIt, { name: props.sizeTheme, value: props.sizeValue }) const marker = createMarkers(instanceCount * groupCount) const counts = { drawCount: points.pointCount, groupCount, instanceCount } diff --git a/src/mol-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts index e8b1dfc969ce762c2f386cf9151df8bc2692c837..b177db9fda68fcac7507d6c8eafaf3dc307d2402 100644 --- a/src/mol-geo/representation/structure/index.ts +++ b/src/mol-geo/representation/structure/index.ts @@ -6,27 +6,30 @@ */ import { Structure } from 'mol-model/structure'; -import { ColorThemeProps } from 'mol-view/theme/color'; -import { SizeThemeProps } from 'mol-view/theme/size'; +import { ColorThemeProps, ColorThemeName, ColorThemeOptions } from 'mol-view/theme/color'; +import { SizeThemeProps, SizeThemeName, SizeThemeOptions } from 'mol-view/theme/size'; import { Representation, RepresentationProps } from '..'; import { Geometry } from '../../geometry/geometry'; import { Mesh } from '../../geometry/mesh/mesh'; import { Points } from '../../geometry/points/points'; import { Lines } from '../../geometry/lines/lines'; +import { SelectParam, paramDefaultValues } from 'mol-view/parameter'; export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { } -export const DefaultStructureProps = { - ...Geometry.DefaultProps, - colorTheme: { name: 'unit-index' } as ColorThemeProps, - sizeTheme: { name: 'physical' } as SizeThemeProps, +export const StructureParams = { + ...Geometry.Params, + colorTheme: SelectParam<ColorThemeName>('Color Theme', '', 'unit-index', ColorThemeOptions), + sizeTheme: SelectParam<SizeThemeName>('Size Theme', '', 'physical', SizeThemeOptions), } +export const DefaultStructureProps = paramDefaultValues(StructureParams) export type StructureProps = typeof DefaultStructureProps -export const DefaultStructureMeshProps = { - ...Mesh.DefaultProps, - ...DefaultStructureProps, +export const StructureMeshParams = { + ...Mesh.Params, + ...StructureParams, } +export const DefaultStructureMeshProps = paramDefaultValues(StructureMeshParams) export type StructureMeshProps = typeof DefaultStructureMeshProps export const DefaultStructurePointsProps = { diff --git a/src/mol-geo/representation/structure/units-visual.ts b/src/mol-geo/representation/structure/units-visual.ts index bc669e87cf7ed1e659c794771c6762a835c06caf..df24f1ae5703964ef5735163dc1617ab1a9b4218 100644 --- a/src/mol-geo/representation/structure/units-visual.ts +++ b/src/mol-geo/representation/structure/units-visual.ts @@ -8,7 +8,7 @@ import { Unit, Structure } from 'mol-model/structure'; import { RepresentationProps, Visual } from '../'; -import { DefaultStructureMeshProps, VisualUpdateState, DefaultStructurePointsProps, DefaultStructureLinesProps } from '.'; +import { DefaultStructureMeshProps, VisualUpdateState, DefaultStructurePointsProps, DefaultStructureLinesProps, StructureMeshParams } from '.'; import { RuntimeContext } from 'mol-task'; import { PickingId } from '../../geometry/picking'; import { LocationIterator } from '../../util/location-iterator'; @@ -24,6 +24,16 @@ import { updateRenderableState } from '../../geometry/geometry'; import { createColors } from '../../geometry/color-data'; import { createSizes } from '../../geometry/size-data'; import { Lines } from '../../geometry/lines/lines'; +import { MultiSelectParam, paramDefaultValues } from 'mol-view/parameter'; + +export const UnitKindInfo = { + 'atomic': {}, + 'spheres': {}, + 'gaussians': {}, +} +export type UnitKind = keyof typeof UnitKindInfo +export const UnitKindNames = Object.keys(UnitKindInfo) +export const UnitKindOptions = UnitKindNames.map(n => [n, n] as [UnitKind, string]) export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup } @@ -36,12 +46,22 @@ function sameGroupConformation(groupA: Unit.SymmetryGroup, groupB: Unit.Symmetry ) } +function includesUnitKind(unitKinds: UnitKind[], unit: Unit) { + for (let i = 0, il = unitKinds.length; i < il; ++i) { + if (Unit.isAtomic(unit) && unitKinds[i] === 'atomic') return true + if (Unit.isSpheres(unit) && unitKinds[i] === 'spheres') return true + if (Unit.isGaussians(unit) && unitKinds[i] === 'gaussians') return true + } + return false +} + // mesh -export const DefaultUnitsMeshProps = { - ...DefaultStructureMeshProps, - unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[] +export const UnitsMeshParams = { + ...StructureMeshParams, + unitKinds: MultiSelectParam<UnitKind>('Unit Kind', '', [ 'atomic', 'spheres' ], UnitKindOptions), } +export const DefaultUnitsMeshProps = paramDefaultValues(UnitsMeshParams) export type UnitsMeshProps = typeof DefaultUnitsMeshProps export interface UnitsMeshVisualBuilder<P extends UnitsMeshProps> { @@ -72,7 +92,7 @@ export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisu const unit = group.units[0] currentConformationId = Unit.conformationId(unit) - mesh = currentProps.unitKinds.includes(unit.kind) + mesh = includesUnitKind(currentProps.unitKinds, unit) ? await createMesh(ctx, unit, currentStructure, currentProps, mesh) : Mesh.createEmpty(mesh) @@ -223,7 +243,7 @@ export function UnitsPointsVisual<P extends UnitsPointsProps>(builder: UnitsPoin const unit = group.units[0] currentConformationId = Unit.conformationId(unit) - points = currentProps.unitKinds.includes(unit.kind) + points = includesUnitKind(currentProps.unitKinds, unit) ? await createPoints(ctx, unit, currentStructure, currentProps, points) : Points.createEmpty(points) @@ -378,7 +398,7 @@ export function UnitsLinesVisual<P extends UnitsLinesProps>(builder: UnitsLinesV const unit = group.units[0] currentConformationId = Unit.conformationId(unit) - lines = currentProps.unitKinds.includes(unit.kind) + lines = includesUnitKind(currentProps.unitKinds, unit) ? await createLines(ctx, unit, currentStructure, currentProps, lines) : Lines.createEmpty(lines) diff --git a/src/mol-geo/representation/structure/visual/element-sphere.ts b/src/mol-geo/representation/structure/visual/element-sphere.ts index 62a59004bb7393a0220bfc8d46c1b5e2e213242f..80176be24a5a38d8fe1b7ef4d5b7746348074e4c 100644 --- a/src/mol-geo/representation/structure/visual/element-sphere.ts +++ b/src/mol-geo/representation/structure/visual/element-sphere.ts @@ -9,6 +9,9 @@ import { UnitsVisual, VisualUpdateState } from '..'; import { createElementSphereMesh, markElement, getElementLoci, StructureElementIterator } from './util/element'; import { UnitsMeshVisual, DefaultUnitsMeshProps } from '../units-visual'; +export const ElementSphereParams = { + UnitsMe +} export const DefaultElementSphereProps = { ...DefaultUnitsMeshProps, detail: 0 diff --git a/src/mol-geo/representation/volume/surface.ts b/src/mol-geo/representation/volume/surface.ts index 1f19bff8be49a5948ce3e3683029d874a40b3e74..b6c6b1fce28d6954edd483103926678a5d58a0f8 100644 --- a/src/mol-geo/representation/volume/surface.ts +++ b/src/mol-geo/representation/volume/surface.ts @@ -51,6 +51,7 @@ export default function SurfaceVisual(): VolumeVisual<SurfaceProps> { async createOrUpdate(ctx: RuntimeContext, props: Partial<SurfaceProps> = {}, volume?: VolumeData) { currentProps = { ...DefaultSurfaceProps, ...props } + console.log('MOINMOIN') if (!volume) return const mesh = await computeVolumeSurface(volume, currentProps.isoValue).runAsChild(ctx) @@ -65,6 +66,7 @@ export default function SurfaceVisual(): VolumeVisual<SurfaceProps> { const state = createRenderableState(currentProps) renderObject = createMeshRenderObject(values, state) + console.log('renderObject', renderObject) }, getLoci(pickingId: PickingId) { // TODO diff --git a/src/mol-io/common/binary-cif/decoder.ts b/src/mol-io/common/binary-cif/decoder.ts index e9720e52fd6d36570a4eb59162124f62c9f165e5..21845329952d64bad0fff4c2f8a72e65a4401fac 100644 --- a/src/mol-io/common/binary-cif/decoder.ts +++ b/src/mol-io/common/binary-cif/decoder.ts @@ -6,6 +6,7 @@ */ import { Encoding, EncodedData } from './encoding' +import { IsNativeEndianLittle, flipByteOrder } from '../binary'; /** * Fixed point, delta, RLE, integer packing adopted from https://github.com/rcsb/mmtf-javascript/ @@ -64,32 +65,10 @@ function getFloatArray(type: Encoding.FloatDataType, size: number) { } } -/* http://stackoverflow.com/questions/7869752/javascript-typed-arrays-and-endianness */ -const isLittleEndian = (function () { - const arrayBuffer = new ArrayBuffer(2); - const uint8Array = new Uint8Array(arrayBuffer); - const uint16array = new Uint16Array(arrayBuffer); - uint8Array[0] = 0xAA; - uint8Array[1] = 0xBB; - if (uint16array[0] === 0xBBAA) return true; - return false; -})(); - function int8(data: Uint8Array) { return new Int8Array(data.buffer, data.byteOffset); } -function flipByteOrder(data: Uint8Array, bytes: number) { - let buffer = new ArrayBuffer(data.length); - let ret = new Uint8Array(buffer); - for (let i = 0, n = data.length; i < n; i += bytes) { - for (let j = 0; j < bytes; j++) { - ret[i + bytes - j - 1] = data[i + j]; - } - } - return buffer; -} - function view<T>(data: Uint8Array, byteSize: number, c: new (buffer: ArrayBuffer) => T) { - if (isLittleEndian) return new c(data.buffer); + if (IsNativeEndianLittle) return new c(data.buffer); return new c(flipByteOrder(data, byteSize)); } diff --git a/src/mol-io/common/binary.ts b/src/mol-io/common/binary.ts new file mode 100644 index 0000000000000000000000000000000000000000..30601b04723ccd192830e769b8c5cf4569b3febb --- /dev/null +++ b/src/mol-io/common/binary.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author David Sehnal <david.sehnal@gmail.com> + */ + +export const IsNativeEndianLittle = new Uint16Array(new Uint8Array([0x12, 0x34]).buffer)[0] === 0x3412; + +export function flipByteOrder(data: Uint8Array, bytes: number) { + let buffer = new ArrayBuffer(data.length); + let ret = new Uint8Array(buffer); + for (let i = 0, n = data.length; i < n; i += bytes) { + for (let j = 0; j < bytes; j++) { + ret[i + bytes - j - 1] = data[i + j]; + } + } + return buffer; +} \ No newline at end of file diff --git a/src/mol-io/common/file-handle.ts b/src/mol-io/common/file-handle.ts new file mode 100644 index 0000000000000000000000000000000000000000..b3ae0fd64d1adb4f74c4f93482f2af7a75f8469a --- /dev/null +++ b/src/mol-io/common/file-handle.ts @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { defaults } from 'mol-util'; + +export interface FileHandle { + /** The number of bytes in the file */ + length: number + /** + * @param position The offset from the beginning of the file from which data should be read. + * @param sizeOrBuffer The buffer the data will be written to. If a number a buffer of that size will be created. + * @param size The number of bytes to read. + * @param byteOffset The offset in the buffer at which to start writing. + */ + readBuffer(position: number, sizeOrBuffer: Uint8Array | number, size?: number, byteOffset?: number): Promise<{ bytesRead: number, buffer: Uint8Array }> +} + +export namespace FileHandle { + export function fromBuffer(buffer: Uint8Array): FileHandle { + return { + length: buffer.length, + readBuffer: (position: number, sizeOrBuffer: Uint8Array | number, size?: number, byteOffset?: number) => { + if (typeof sizeOrBuffer === 'number') { + const start = position + const end = Math.min(buffer.length, start + (defaults(size, sizeOrBuffer))) + return Promise.resolve({ + bytesRead: end - start, + buffer: buffer.subarray(start, end), + }) + } else { + if (size === void 0) { + return Promise.reject('readBuffer: Specify size.'); + } + const start = position + const end = Math.min(buffer.length, start + defaults(size, sizeOrBuffer.length)) + sizeOrBuffer.set(buffer.subarray(start, end), byteOffset) + return Promise.resolve({ + bytesRead: end - start, + buffer: sizeOrBuffer, + }) + } + } + } + } +} \ No newline at end of file diff --git a/src/mol-io/common/msgpack/decode.ts b/src/mol-io/common/msgpack/decode.ts index 5d465550eab4b6e0a6eb1ccfbb6e811f9d72afdd..9d6e1aa85b11443612730d43383879e063e3c4af 100644 --- a/src/mol-io/common/msgpack/decode.ts +++ b/src/mol-io/common/msgpack/decode.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info. * * Adapted from https://github.com/rcsb/mmtf-javascript * @author Alexander Rose <alexander.rose@weirdbyte.de> @@ -25,13 +25,11 @@ interface State { /** * decode all key-value pairs of a map into an object - * @param {Integer} length - number of key-value pairs - * @return {Object} decoded map */ function map(state: State, length: number) { - let value: any = {}; + const value: { [k: string]: any } = {}; for (let i = 0; i < length; i++) { - let key = parse(state); + const key = parse(state); value[key] = parse(state); } return value; @@ -39,8 +37,6 @@ function map(state: State, length: number) { /** * decode binary array - * @param {Integer} length - number of elements in the array - * @return {Uint8Array} decoded array */ function bin(state: State, length: number) { // This approach to binary parsing wastes a bit of memory to trade for speed compared to: @@ -51,8 +47,8 @@ function bin(state: State, length: number) { // in the background, which causes the element access to be several times slower // than creating the new byte array. - let value = new Uint8Array(length); - let o = state.offset; + const value = new Uint8Array(length); + const o = state.offset; for (let i = 0; i < length; i++) value[i] = state.buffer[i + o]; state.offset += length; return value; @@ -60,22 +56,18 @@ function bin(state: State, length: number) { /** * decode string - * @param {Integer} length - number string characters - * @return {String} decoded string */ function str(state: State, length: number) { - let value = utf8Read(state.buffer, state.offset, length); + const value = utf8Read(state.buffer, state.offset, length); state.offset += length; return value; } /** - * decode array - * @param {Integer} length - number of array elements - * @return {Array} decoded array - */ + * decode array + */ function array(state: State, length: number) { - let value: any[] = new Array(length); + const value: any[] = new Array(length); for (let i = 0; i < length; i++) { value[i] = parse(state); } @@ -83,11 +75,10 @@ function array(state: State, length: number) { } /** - * recursively parse the MessagePack data - * @return {Object|Array|String|Number|Boolean|null} decoded MessagePack data + * recursively parse the MessagePack data and return decoded MessagePack data */ function parse(state: State) { - let type = state.buffer[state.offset]; + const type = state.buffer[state.offset]; let value: any, length: number; // Positive FixInt if ((type & 0x80) === 0x00) { diff --git a/src/mol-io/common/msgpack/encode.ts b/src/mol-io/common/msgpack/encode.ts index fb79a65496c56c1dc40e9667b805092b84d1770e..35231843a4eeb62d41f8a58d8242bc2eb42afd22 100644 --- a/src/mol-io/common/msgpack/encode.ts +++ b/src/mol-io/common/msgpack/encode.ts @@ -17,7 +17,7 @@ export default function encode(value: any) { } function encodedSize(value: any) { - let type = typeof value; + const type = typeof value; // Raw Bytes if (type === 'string') { diff --git a/src/mol-io/reader/_spec/ccp4.spec.ts b/src/mol-io/reader/_spec/ccp4.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..0b3a23859da692e417edb8c2975bdcc881ef5813 --- /dev/null +++ b/src/mol-io/reader/_spec/ccp4.spec.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import CCP4 from '../ccp4/parser' +import { FileHandle } from '../../common/file-handle'; + +const ccp4Buffer = new Uint8Array([]) + +describe('ccp4 reader', () => { + it('basic', async () => { + const file = FileHandle.fromBuffer(ccp4Buffer) + const parsed = await CCP4(file).run(); + + if (parsed.isError) { + console.log(parsed) + return; + } + // const ccp4File = parsed.result; + // const { header, values } = ccp4File; + + // TODO + }); +}); diff --git a/src/mol-io/reader/ccp4/parser.ts b/src/mol-io/reader/ccp4/parser.ts new file mode 100644 index 0000000000000000000000000000000000000000..488ca23311375f5ac452e7a2a233b74fec102891 --- /dev/null +++ b/src/mol-io/reader/ccp4/parser.ts @@ -0,0 +1,119 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Task, RuntimeContext } from 'mol-task'; +import * as Schema from './schema' +import Result from '../result' +import { FileHandle } from '../../common/file-handle'; +import { flipByteOrder } from '../../common/binary'; + +async function parseInternal(file: FileHandle, ctx: RuntimeContext): Promise<Result<Schema.Ccp4File>> { + await ctx.update({ message: 'Parsing CCP4 file...' }); + + const { buffer } = await file.readBuffer(0, file.length) + const bin = buffer.buffer + + const intView = new Int32Array(bin, 0, 56) + const floatView = new Float32Array(bin, 0, 56) + const dv = new DataView(bin) + + // 53 MAP Character string 'MAP ' to identify file type + const MAP = String.fromCharCode( + dv.getUint8(52 * 4), dv.getUint8(52 * 4 + 1), + dv.getUint8(52 * 4 + 2), dv.getUint8(52 * 4 + 3) + ) + if (MAP !== 'MAP ') { + return Result.error('ccp4 format error, missing "MAP " string'); + } + + // 54 MACHST Machine stamp indicating machine type which wrote file + // 17 and 17 for big-endian or 68 and 65 for little-endian + const MACHST = [ dv.getUint8(53 * 4), dv.getUint8(53 * 4 + 1) ] + + if (MACHST[ 0 ] === 17 && MACHST[ 1 ] === 17) { + flipByteOrder(buffer, buffer.length) + } + + const header: Schema.Ccp4Header = { + NC: intView[0], + NR: intView[1], + NS: intView[2], + + MODE: intView[3], + + NCSTART: intView[4], + NRSTART: intView[5], + NSSTART: intView[6], + + NX: intView[7], + NY: intView[8], + NZ: intView[9], + + xLength: floatView[10], + yLength: floatView[11], + zLength: floatView[12], + + alpha: floatView[13], + beta: floatView[14], + gamma: floatView[15], + + MAPC: intView[16], + MAPR: intView[17], + MAPS: intView[18], + + AMIN: floatView[19], + AMAX: floatView[20], + AMEAN: floatView[21], + + ISPG: intView[22], + + NSYMBT: intView[23], + + LSKFLG: intView[24], + + SKWMAT: [], // TODO bytes 26-34 + SKWTRN: [], // TODO bytes 35-37 + + // bytes 50-52 origin in X,Y,Z used for transforms + originX: floatView[49], + originY: floatView[50], + originZ: floatView[51], + + MAP, // bytes 53 MAP + MACHST, // bytes 54 MACHST + + ARMS: floatView[54], + + // TODO bytes 56 NLABL + // TODO bytes 57-256 LABEL + } + + let values + if (header.MODE === 2) { + values = new Float32Array( + bin, 256 * 4 + header.NSYMBT, + header.NX * header.NY * header.NZ + ) + } else if (header.MODE === 0) { + values = new Float32Array(new Int8Array( + bin, 256 * 4 + header.NSYMBT, + header.NX * header.NY * header.NZ + )) + } else { + return Result.error(`ccp4 mode '${header.MODE}' unsupported`); + } + + const result: Schema.Ccp4File = { header, values }; + return Result.success(result); +} + +export function parse(file: FileHandle) { + return Task.create<Result<Schema.Ccp4File>>('Parse CCP4', async ctx => { + return await parseInternal(file, ctx); + }); +} + +export default parse; \ No newline at end of file diff --git a/src/mol-io/reader/ccp4/schema.ts b/src/mol-io/reader/ccp4/schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..12b23631614eff9db792686a495c68e4a83e14f0 --- /dev/null +++ b/src/mol-io/reader/ccp4/schema.ts @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author David Sehnal <david.sehnal@gmail.com> + */ + +export interface Ccp4Header { + /** columns (fastest changing) */ + NC: number + /** rows */ + NR: number + /** sections (slowest changing) */ + NS: number + /** + * 0 image : signed 8-bit bytes range -128 to 127 + * 1 image : 16-bit halfwords + * 2 image : 32-bit reals + * 3 transform : complex 16-bit integers + * 4 transform : complex 32-bit reals + * 6 image : unsigned 16-bit range 0 to 65535 + * 16 image: unsigned char * 3 (for rgb data, non-standard) + * + * Note: Mode 2 is the normal mode used in the CCP4 programs. + * This parser only supports modes 0 and 2 + */ + MODE: number + /** first column */ + NCSTART: number + /** first row */ + NRSTART: number + /** first section */ + NSSTART: number + /** intervals along x */ + NX: number + /** intervals along y */ + NY: number + /** intervals along z */ + NZ: number + /** x axis cell length (Angstroms in CCP4) */ + xLength: number + /** y axis cell length (Angstroms in CCP4) */ + yLength: number + /** z axis cell length (Angstroms in CCP4) */ + zLength: number + /** alpha cell angle (Degrees) */ + alpha: number + /** beta cell angle (Degrees) */ + beta: number + /** gamma cell angle (Degrees) */ + gamma: number + /** axis corresponds for columns (1,2,3 for X,Y,Z) */ + MAPC: number + /** axis corresponds for rows (1,2,3 for X,Y,Z) */ + MAPR: number + /** axis corresponds for sections (1,2,3 for X,Y,Z) */ + MAPS: number + /** minimum density value */ + AMIN: number + /** maximum density value */ + AMAX: number + /** mean/average density value */ + AMEAN: number + /** space group number */ + ISPG: number + /** number of bytes used for storing symmetry operators */ + NSYMBT: number + /** flag for skew transformation, =0 none, =1 if foll */ + LSKFLG: number + /** Skew matrix S (in order S11, S12, S13, S21 etc) if LSKFLG .ne. 0 + * + * May be used in CCP4 but not in MRC + */ + SKWMAT: number[] + /** + * Skew translation t if LSKFLG != 0 + * Skew transformation is from standard orthogonal + * coordinate frame (as used for atoms) to orthogonal + * map frame, as Xo(map) = S * (Xo(atoms) - t) + * + * May be used in CCP4 but not in MRC + */ + SKWTRN: number[] + /** x axis origin transformation (not used in CCP4) */ + originX: number + /** y axis origin transformation (not used in CCP4) */ + originY: number + /** z axis origin transformation (not used in CCP4) */ + originZ: number + /** Character string 'MAP ' to identify file type */ + MAP: string + /** + * Machine stamp indicating machine type which wrote file, + * 17 and 17 for big-endian or 68 and 65 for little-endian. + */ + MACHST: number[] + /** rms deviation of map from mean density */ + ARMS: number +} + +/** + * MRC + * http://ami.scripps.edu/software/mrctools/mrc_specification.php + * http://www2.mrc-lmb.cam.ac.uk/research/locally-developed-software/image-processing-software/#image + * http://bio3d.colorado.edu/imod/doc/mrc_format.txt + * + * CCP4 (MAP) + * http://www.ccp4.ac.uk/html/maplib.html + * + * MRC format does not use the skew transformation header records (words 25-37) + * CCP4 format does not use the ORIGIN header records (words 50-52) + */ +export interface Ccp4File { + header: Ccp4Header + values: Float32Array | Int8Array +} \ No newline at end of file diff --git a/src/mol-io/reader/gro/parser.ts b/src/mol-io/reader/gro/parser.ts index afd469111309c7d3ef6626844ddcd431e520a0ff..6183a9a5fee6e887e889b4bd6efd4cec5b3de42b 100644 --- a/src/mol-io/reader/gro/parser.ts +++ b/src/mol-io/reader/gro/parser.ts @@ -140,7 +140,7 @@ function handleBoxVectors(state: State) { async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<Schema.GroFile>> { const tokenizer = Tokenizer(data); - ctx.update({ message: 'Parsing...', current: 0, max: data.length }); + await ctx.update({ message: 'Parsing...', current: 0, max: data.length }); const structures: Schema.GroStructure[] = []; while (tokenizer.position < data.length) { const state = State(tokenizer, ctx); diff --git a/src/mol-model/volume/formats/ccp4.ts b/src/mol-model/volume/formats/ccp4.ts new file mode 100644 index 0000000000000000000000000000000000000000..bed2de43b102abfeeb99e260d4e2c3497a16dab2 --- /dev/null +++ b/src/mol-model/volume/formats/ccp4.ts @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { VolumeData } from '../data' +import { Task } from 'mol-task'; +import { SpacegroupCell, Box3D } from 'mol-math/geometry'; +import { Tensor, Vec3 } from 'mol-math/linear-algebra'; +import { Ccp4File } from 'mol-io/reader/ccp4/schema'; +import { degToRad } from 'mol-math/misc'; + +// TODO implement voxelSize parameter + +// TODO support for rescaling of values +// based on uglymol (https://github.com/uglymol/uglymol) by Marcin Wojdyr (wojdyr) +// if the file was converted by mapmode2to0 - scale the data +// if (intView[ 39 ] === -128 && intView[ 40 ] === 127) { +// // scaling f(x)=b1*x+b0 such that f(-128)=min and f(127)=max +// const b1 = (header.DMAX - header.DMIN) / 255.0 +// const b0 = 0.5 * (header.DMIN + header.DMAX + b1) +// for (let j = 0, jl = data.length; j < jl; ++j) { +// data[ j ] = b1 * data[ j ] + b0 +// } +// } + +function volumeFromCcp4(source: Ccp4File): Task<VolumeData> { + return Task.create<VolumeData>('Parse Volume Data', async ctx => { + const { header, values } = source; + + const cell = SpacegroupCell.create( + header.ISPG, + Vec3.create(header.xLength, header.yLength, header.zLength), + Vec3.create(degToRad(header.alpha), degToRad(header.beta), degToRad(header.gamma) + )) + + const origin = Vec3.create(header.originX, header.originY, header.originZ) + const dimensions = Vec3.create(header.NX, header.NY, header.NZ) + const axisOrder = Vec3.create(header.MAPC - 1, header.MAPR - 1, header.MAPS - 1) + + const space = Tensor.Space(dimensions, axisOrder, header.MODE === 0 ? Int8Array : Float32Array); + const data = Tensor.create(space, Tensor.Data1(values)); + + // TODO Calculate stats? When to trust header data? + + return { + cell, + fractionalBox: Box3D.create(origin, Vec3.add(Vec3.zero(), origin, dimensions)), + data, + dataStats: { + min: header.AMIN, + max: header.AMAX, + mean: header.AMEAN, + sigma: header.ARMS + } + }; + }); +} + +export { volumeFromCcp4 } \ No newline at end of file diff --git a/src/mol-util/read.ts b/src/mol-util/read.ts index 29cc2acf06a6e0a36295a114be29d28b56495b59..7cf70c65d316abc6b8ef1b257d20645cd09fa3d0 100644 --- a/src/mol-util/read.ts +++ b/src/mol-util/read.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -export const readFileAs = (file: File, isBinary = false) => { +export function readFile (file: File, isBinary = false) { const fileReader = new FileReader() return new Promise<string | Uint8Array>((resolve, reject) => { fileReader.onerror = () => { @@ -23,22 +23,22 @@ export const readFileAs = (file: File, isBinary = false) => { } export function readFileAsText(file: File) { - return readFileAs(file, false) as Promise<string> + return readFile(file, false) as Promise<string> } export function readFileAsBuffer(file: File) { - return readFileAs(file, true) as Promise<Uint8Array> + return readFile(file, true) as Promise<Uint8Array> } -export async function readUrlAs(url: string, isBinary: boolean) { +export async function readUrl(url: string, isBinary: boolean) { const response = await fetch(url); return isBinary ? new Uint8Array(await response.arrayBuffer()) : await response.text(); } export function readUrlAsText(url: string) { - return readUrlAs(url, false) as Promise<string> + return readUrl(url, false) as Promise<string> } export function readUrlAsBuffer(url: string) { - return readUrlAs(url, true) as Promise<Uint8Array> + return readUrl(url, true) as Promise<Uint8Array> } \ No newline at end of file diff --git a/src/mol-view/parameter.ts b/src/mol-view/parameter.ts new file mode 100644 index 0000000000000000000000000000000000000000..93c95f6490cfe5d9faf205fd279f6d442d74c044 --- /dev/null +++ b/src/mol-view/parameter.ts @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Color } from 'mol-util/color'; + +export interface BaseParam<T> { + label: string + description: string + defaultValue: T +} + +export interface SelectParam<T extends string> extends BaseParam<T> { + type: 'select' + /** array of (value, label) tupels */ + options: [T, string][] +} +export function SelectParam<T extends string>(label: string, description: string, defaultValue: T, options: [T, string][]): SelectParam<T> { + return { type: 'select', label, description, defaultValue, options } +} + +export interface MultiSelectParam<E extends string, T = E[]> extends BaseParam<T> { + type: 'multi-select' + /** array of (value, label) tupels */ + options: [E, string][] +} +export function MultiSelectParam<E extends string, T = E[]>(label: string, description: string, defaultValue: T, options: [E, string][]): MultiSelectParam<E, T> { + return { type: 'multi-select', label, description, defaultValue, options } +} + +export interface CheckboxParam extends BaseParam<boolean> { + type: 'checkbox' +} +export function CheckboxParam(label: string, description: string, defaultValue: boolean): CheckboxParam { + return { type: 'checkbox', label, description, defaultValue } +} + +export interface RangeParam extends BaseParam<number> { + type: 'range' + min: number + max: number + /** if an `integer` parse value with parseInt, otherwise use parseFloat */ + step: number +} +export function RangeParam(label: string, description: string, defaultValue: number, min: number, max: number, step: number): RangeParam { + return { type: 'range', label, description, defaultValue, min, max, step } +} + +export interface TextParam extends BaseParam<string> { + type: 'text' +} +export function TextParam(label: string, description: string, defaultValue: string): TextParam { + return { type: 'text', label, description, defaultValue } +} + +export interface ColorParam extends BaseParam<Color> { + type: 'color' +} +export function ColorParam(label: string, description: string, defaultValue: Color): ColorParam { + return { type: 'color', label, description, defaultValue } +} + +export interface NumberParam extends BaseParam<number> { + type: 'number' + min: number + max: number + /** if an `integer` parse value with parseInt, otherwise use parseFloat */ + step: number +} +export function NumberParam(label: string, description: string, defaultValue: number, min: number, max: number, step: number): NumberParam { + return { type: 'number', label, description, defaultValue, min, max, step } +} + +export type Param = SelectParam<any> | MultiSelectParam<any> | CheckboxParam | RangeParam | TextParam | ColorParam | NumberParam + +export function paramDefaultValues<T extends { [k: string]: Param }>(params: T) { + const d: { [k: string]: any } = {} + Object.keys(params).forEach(k => d[k] = params[k].defaultValue) + return d as { [k in keyof T]: T[k]['defaultValue'] } +} \ No newline at end of file diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts index 271b2f9d703695a6b7717fe17d632e4de7445fd0..00e35a60c76ef21b706d0b30fd135fdcc03ce0eb 100644 --- a/src/mol-view/stage.ts +++ b/src/mol-view/stage.ts @@ -20,32 +20,36 @@ import { CarbohydrateProps } from 'mol-geo/representation/structure/representati const spacefillProps: Partial<SpacefillProps> = { doubleSided: true, - colorTheme: { name: 'chain-id' }, - sizeTheme: { name: 'physical', factor: 1.0 }, + colorTheme: 'chain-id', + sizeTheme: 'physical', + sizeValue: 1.0, quality: 'auto', useFog: false } const ballAndStickProps: Partial<BallAndStickProps> = { doubleSided: true, - colorTheme: { name: 'chain-id' }, - sizeTheme: { name: 'uniform', value: 0.15 }, + colorTheme: 'chain-id', + sizeTheme: 'uniform', + sizeValue: 0.15, quality: 'auto', useFog: false } const distanceRestraintProps: Partial<DistanceRestraintProps> = { doubleSided: true, - colorTheme: { name: 'cross-link' }, - sizeTheme: { name: 'uniform', value: 0.6 }, + colorTheme: 'cross-link', + sizeTheme: 'uniform', + sizeValue: 0.6, quality: 'auto', useFog: false } const backboneProps: Partial<BackboneProps> = { doubleSided: true, - colorTheme: { name: 'chain-id' }, - sizeTheme: { name: 'uniform', value: 0.3 }, + colorTheme: 'chain-id', + sizeTheme: 'uniform', + sizeValue: 0.3, quality: 'auto', useFog: false, alpha: 0.5 @@ -53,8 +57,9 @@ const backboneProps: Partial<BackboneProps> = { const cartoonProps: Partial<CartoonProps> = { doubleSided: true, - colorTheme: { name: 'chain-id' }, - sizeTheme: { name: 'uniform', value: 0.13, factor: 1 }, + colorTheme: 'chain-id', + sizeTheme: 'uniform', + sizeValue: 0.13, aspectRatio: 8, quality: 'auto', useFog: false @@ -62,8 +67,9 @@ const cartoonProps: Partial<CartoonProps> = { const carbohydrateProps: Partial<CarbohydrateProps> = { doubleSided: true, - colorTheme: { name: 'carbohydrate-symbol' }, - sizeTheme: { name: 'uniform', value: 1, factor: 1 }, + colorTheme: 'carbohydrate-symbol', + sizeTheme: 'uniform', + sizeValue: 1, quality: 'highest', useFog: false } diff --git a/src/mol-view/state/entity.ts b/src/mol-view/state/entity.ts index 60ba57af03ea7f6453f3370defee5010704808f5..7c16728cbc1fb217b81bf7fdec259bd7c4c94bdf 100644 --- a/src/mol-view/state/entity.ts +++ b/src/mol-view/state/entity.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { readFileAs, readUrlAs } from 'mol-util/read' +import { readFile, readUrl } from 'mol-util/read' import { idFactory } from 'mol-util/id-factory' import { StateContext } from './context'; import { getFileInfo } from 'mol-util/file-info'; @@ -54,7 +54,7 @@ export namespace UrlEntity { const { name, ext: type, compressed, binary } = getFileInfo(url) return StateEntity.create(ctx, 'url', { url, name, type, - getData: () => readUrlAs(url, isBinary || !!compressed || binary) + getData: () => readUrl(url, isBinary || !!compressed || binary) }) } } @@ -71,7 +71,7 @@ export namespace FileEntity { const { name, ext: type, compressed, binary } = getFileInfo(file) return StateEntity.create(ctx, 'file', { name, type, - getData: () => readFileAs(file, isBinary || !!compressed || binary) + getData: () => readFile(file, isBinary || !!compressed || binary) }) } } diff --git a/src/mol-view/theme/color.ts b/src/mol-view/theme/color.ts index a9ca94485f94d2d8b80b3fae6920662af8c40cba..001b497b62913a702ed8e78df347a929006d922f 100644 --- a/src/mol-view/theme/color.ts +++ b/src/mol-view/theme/color.ts @@ -95,4 +95,5 @@ export const ColorThemeInfo = { 'uniform': {}, } export type ColorThemeName = keyof typeof ColorThemeInfo -export const ColorThemeNames = Object.keys(ColorThemeInfo) \ No newline at end of file +export const ColorThemeNames = Object.keys(ColorThemeInfo) +export const ColorThemeOptions = ColorThemeNames.map(n => [n, n] as [ColorThemeName, string]) \ No newline at end of file diff --git a/src/mol-view/theme/size.ts b/src/mol-view/theme/size.ts index 337b4ede2853ec62050efcfc54aa80097377af5f..65428f9d0ce22ffd84f4865a529cded363f4e083 100644 --- a/src/mol-view/theme/size.ts +++ b/src/mol-view/theme/size.ts @@ -34,4 +34,5 @@ export const SizeThemeInfo = { 'uniform': {} } export type SizeThemeName = keyof typeof SizeThemeInfo -export const SizeThemeNames = Object.keys(SizeThemeInfo) \ No newline at end of file +export const SizeThemeNames = Object.keys(SizeThemeInfo) +export const SizeThemeOptions = SizeThemeNames.map(n => [n, n] as [SizeThemeName, string]) \ No newline at end of file