Skip to content
Snippets Groups Projects
Commit f9343a80 authored by Alexander Rose's avatar Alexander Rose
Browse files

wip, load volume, volume gui

parent 6d3bae4e
No related branches found
No related tags found
No related merge requests found
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
......@@ -5,21 +5,22 @@
*/
import Viewer from 'mol-view/viewer';
import { getCifFromUrl, getModelsFromMmcif, getCifFromFile, getCcp4FromUrl } from './util';
import { getCifFromUrl, getModelsFromMmcif, getCifFromFile, getCcp4FromUrl, getVolumeFromCcp4, getCcp4FromFile } 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 IsosurfaceVisual from 'mol-geo/representation/volume/isosurface';
import { VolumeView } from './volume-view';
import { Ccp4File } from 'mol-io/reader/ccp4/schema';
export class App {
viewer: Viewer
container: HTMLDivElement | null = null;
canvas: HTMLCanvasElement | null = null;
structureView: StructureView | null = null;
volumeView: VolumeView | null = null;
pdbIdLoaded: BehaviorSubject<StructureView | null> = new BehaviorSubject<StructureView | null>(null)
structureLoaded: BehaviorSubject<StructureView | null> = new BehaviorSubject<StructureView | null>(null)
volumeLoaded: BehaviorSubject<VolumeView | null> = new BehaviorSubject<VolumeView | null>(null)
initViewer(_canvas: HTMLCanvasElement, _container: HTMLDivElement) {
this.canvas = _canvas
......@@ -63,7 +64,7 @@ export class App {
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)
this.structureLoaded.next(this.structureView)
}
async loadPdbIdOrMmcifUrl(idOrUrl: string, options?: { assemblyId?: string, binary?: boolean }) {
......@@ -82,17 +83,21 @@ export class App {
//
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(IsosurfaceVisual)
await volRepr.createOrUpdate({
isoValue: 1
}, volume).run()
this.viewer.add(volRepr)
console.log('volRepr', volRepr)
this.viewer.requestDraw(true)
async loadCcp4(ccp4: Ccp4File) {
const volume = await this.runTask(getVolumeFromCcp4(ccp4), 'Get Volume')
this.volumeView = await this.runTask(VolumeView(this, this.viewer, volume), 'Init volume view')
this.volumeLoaded.next(this.volumeView)
}
async loadCcp4File(file: File) {
if (this.volumeView) this.volumeView.destroy();
const ccp4 = await this.runTask(getCcp4FromFile(file), 'Load CCP4 from file')
this.loadCcp4(ccp4)
}
async loadCcp4Url(url: string) {
if (this.volumeView) this.volumeView.destroy();
const ccp4 = await this.runTask(getCcp4FromUrl(url), 'Load CCP4 from URL')
this.loadCcp4(ccp4)
}
}
\ No newline at end of file
......@@ -10,6 +10,8 @@ import { App } from '../app';
import { Viewport } from './viewport';
import { StructureViewComponent } from './structure-view';
import { Examples } from '../examples';
import { VolumeViewComponent } from './volume-view';
import { VolumeView } from '../volume-view';
export interface AppProps {
app: App
......@@ -17,25 +19,28 @@ export interface AppProps {
export interface AppState {
structureView: StructureView | null,
volumeView: VolumeView | null,
binary: boolean
}
export class AppComponent extends React.Component<AppProps, AppState> {
state = {
structureView: this.props.app.structureView,
volumeView: this.props.app.volumeView,
binary: false
}
componentDidMount() {
this.props.app.pdbIdLoaded.subscribe((structureView) => {
this.setState({
structureView: this.props.app.structureView
})
this.props.app.structureLoaded.subscribe((structureView) => {
this.setState({ structureView: this.props.app.structureView })
})
this.props.app.volumeLoaded.subscribe((volumeView) => {
this.setState({ volumeView: this.props.app.volumeView })
})
}
render() {
const { structureView } = this.state
const { structureView, volumeView } = this.state
return <div style={{width: '100%', height: '100%'}}>
<div style={{left: '0px', right: '350px', height: '100%', position: 'absolute'}}>
......@@ -69,6 +74,16 @@ export class AppComponent extends React.Component<AppProps, AppState> {
}}
/>
</div>
<div>
<span>Load CCP4/MRC file </span>
<input
accept='*.ccp4,*.mrc, *.map'
type='file'
onChange={e => {
if (e.target.files) this.props.app.loadCcp4File(e.target.files[0])
}}
/>
</div>
<div>
<span>Load example </span>
<select
......@@ -87,6 +102,10 @@ export class AppComponent extends React.Component<AppProps, AppState> {
<div style={{marginBottom: '10px'}}>
{structureView ? <StructureViewComponent structureView={structureView} /> : ''}
</div>
<hr/>
<div style={{marginBottom: '10px'}}>
{volumeView ? <VolumeViewComponent volumeView={volumeView} /> : ''}
</div>
</div>
</div>;
}
......
......@@ -10,17 +10,6 @@ import { StructureRepresentation } from 'mol-geo/representation/structure';
import { RepresentationComponent } from './representation';
import { Representation } from 'mol-geo/representation';
// export function FileInput (props: {
// accept: string
// onChange: (v: FileList | null) => void,
// }) {
// return <input
// accept={props.accept || '*.*'}
// type='file'
// onChange={e => props.onChange.call(null, e.target.files)}
// />
// }
export interface StructureViewComponentProps {
structureView: StructureView
}
......
/**
* 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 { RepresentationComponent } from './representation';
import { Representation } from 'mol-geo/representation';
import { VolumeView } from '../volume-view';
import { VolumeRepresentation } from 'mol-geo/representation/volume';
export interface VolumeViewComponentProps {
volumeView: VolumeView
}
export interface VolumeViewComponentState {
volumeView: VolumeView
label: string
active: { [k: string]: boolean }
volumeRepresentations: { [k: string]: VolumeRepresentation<any> }
}
export class VolumeViewComponent extends React.Component<VolumeViewComponentProps, VolumeViewComponentState> {
state = this.stateFromVolumeView(this.props.volumeView)
private stateFromVolumeView(vv: VolumeView) {
return {
volumeView: vv,
label: vv.label,
active: vv.active,
volumeRepresentations: vv.volumeRepresentations
}
}
componentWillMount() {
this.setState(this.stateFromVolumeView(this.props.volumeView))
}
componentDidMount() {
const vv = this.props.volumeView
this.props.volumeView.updated.subscribe(() => this.setState({
volumeRepresentations: vv.volumeRepresentations
}))
}
componentWillReceiveProps(nextProps: VolumeViewComponentProps) {
if (nextProps.volumeView !== this.props.volumeView) {
this.setState(this.stateFromVolumeView(nextProps.volumeView))
nextProps.volumeView.updated.subscribe(() => this.setState({
volumeRepresentations: nextProps.volumeView.volumeRepresentations
}))
}
}
// async update(state: Partial<VolumeViewComponentState>) {
// const vv = this.state.volumeView
// this.setState(this.stateFromVolumeView(vv))
// }
render() {
const { volumeView, label, active, volumeRepresentations } = this.state
return <div>
<div>
<h2>{label}</h2>
</div>
<div>
<div>
<h4>Active</h4>
{ Object.keys(active).map((k, i) => {
return <div key={i}>
<input
type='checkbox'
checked={active[k]}
onChange={(e) => {
volumeView.setVolumeRepresentation(k, e.target.checked)
}}
/> {k}
</div>
} ) }
</div>
<div>
<h3>Volume Representations</h3>
{ Object.keys(volumeRepresentations).map((k, i) => {
if (active[k]) {
return <div key={i}>
<RepresentationComponent
repr={volumeRepresentations[k] as Representation<any>}
viewer={volumeView.viewer}
app={volumeView.app}
/>
</div>
} else {
return ''
}
} ) }
</div>
</div>
</div>;
}
}
\ No newline at end of file
......@@ -4,11 +4,13 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { readUrl, readFile, readUrlAsBuffer } from 'mol-util/read';
import { readUrl, readFile, readUrlAsBuffer, readFileAsBuffer } from 'mol-util/read';
import CIF, { CifBlock } from 'mol-io/reader/cif'
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 { Ccp4File } from 'mol-io/reader/ccp4/schema';
import { volumeFromCcp4 } from 'mol-model/volume/formats/ccp4';
// import { parse as parseObj } from 'mol-io/reader/obj/parser'
// export async function getObjFromUrl(url: string) {
......@@ -53,9 +55,17 @@ export async function getCcp4FromUrl(url: string) {
return getCcp4FromData(await readUrlAsBuffer(url))
}
export async function getCcp4FromFile(file: File) {
return getCcp4FromData(await readFileAsBuffer(file))
}
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
}
export async function getVolumeFromCcp4(ccp4: Ccp4File) {
return await volumeFromCcp4(ccp4).run()
}
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import Viewer from 'mol-view/viewer';
import { BehaviorSubject } from 'rxjs';
import { App } from './app';
import { Progress } from 'mol-task';
import { VolumeData } from 'mol-model/volume';
import { VolumeRepresentation } from 'mol-geo/representation/volume';
import IsosurfaceVisual from 'mol-geo/representation/volume/isosurface';
import { Vec3 } from 'mol-math/linear-algebra';
export interface VolumeView {
readonly app: App
readonly viewer: Viewer
readonly label: string
readonly volume: VolumeData
readonly active: { [k: string]: boolean }
readonly volumeRepresentations: { [k: string]: VolumeRepresentation<any> }
readonly updated: BehaviorSubject<null>
setVolumeRepresentation(name: string, value: boolean): void
destroy: () => void
}
interface StructureViewProps {
assemblyId?: string
symmetryFeatureId?: number
}
export async function VolumeView(app: App, viewer: Viewer, volume: VolumeData, props: StructureViewProps = {}): Promise<VolumeView> {
const active: { [k: string]: boolean } = {
isosurface: true,
volume: false,
}
const volumeRepresentations: { [k: string]: VolumeRepresentation<any> } = {
isosurface: VolumeRepresentation(IsosurfaceVisual),
}
const updated: BehaviorSubject<null> = new BehaviorSubject<null>(null)
let label: string = 'Volume'
async function setVolumeRepresentation(k: string, value: boolean) {
active[k] = value
await createVolumeRepr()
}
async function createVolumeRepr() {
for (const k in volumeRepresentations) {
if (active[k]) {
await app.runTask(volumeRepresentations[k].createOrUpdate({}, volume).run(
progress => console.log(Progress.format(progress))
), 'Create/update representation')
viewer.add(volumeRepresentations[k])
} else {
viewer.remove(volumeRepresentations[k])
}
}
// const center = Vec3.clone(volume.cell.size)
// Vec3.scale(center, center, 0.5)
// viewer.center(center)
updated.next(null)
viewer.requestDraw(true)
console.log('stats', viewer.stats)
}
await createVolumeRepr()
return {
app,
viewer,
get label() { return label },
volume,
active,
volumeRepresentations,
setVolumeRepresentation,
updated,
destroy: () => {
for (const k in volumeRepresentations) {
viewer.remove(volumeRepresentations[k])
volumeRepresentations[k].destroy()
}
viewer.requestDraw(true)
}
}
}
\ No newline at end of file
......@@ -5,7 +5,6 @@
*/
import { Task } from 'mol-task'
import { RenderObject } from 'mol-gl/render-object';
import { RepresentationProps, Representation, Visual } from '..';
import { VolumeData } from 'mol-model/volume';
import { PickingId } from '../../geometry/picking';
......@@ -13,32 +12,41 @@ import { Loci, EmptyLoci } from 'mol-model/loci';
import { MarkerAction } from '../../geometry/marker-data';
import { Geometry } from '../../geometry/geometry';
import { paramDefaultValues } from 'mol-view/parameter';
import { IsosurfaceParams } from './isosurface';
export interface VolumeVisual<P extends RepresentationProps = {}> extends Visual<VolumeData, P> { }
export interface VolumeRepresentation<P extends RepresentationProps = {}> extends Representation<VolumeData, P> { }
export const VolumeParams = {
...Geometry.Params
...Geometry.Params,
...IsosurfaceParams
}
export const DefaultVolumeProps = paramDefaultValues(VolumeParams)
export type VolumeProps = typeof DefaultVolumeProps
export function VolumeRepresentation<P extends VolumeProps>(visualCtor: (volumeData: VolumeData) => VolumeVisual<P>): VolumeRepresentation<P> {
const renderObjects: RenderObject[] = []
let _volumeData: VolumeData
let visual: VolumeVisual<any>
let _props: P
let busy = false
function createOrUpdate(props: Partial<P> = {}, volumeData?: VolumeData) {
_props = Object.assign({}, DefaultVolumeProps, _props, props)
return Task.create('VolumeRepresentation.create', async ctx => {
if (volumeData) {
_volumeData = volumeData
const visual = visualCtor(_volumeData)
await visual.createOrUpdate(ctx, props, _volumeData)
if (visual.renderObject) renderObjects.push(visual.renderObject)
// TODO queue it somehow
if (busy) return
if (!visual && !volumeData) {
throw new Error('volumeData missing')
} else if (volumeData && !visual) {
busy = true
visual = visualCtor(volumeData)
await visual.createOrUpdate(ctx, props, volumeData)
busy = false
} else {
throw new Error('missing volumeData')
busy = true
await visual.createOrUpdate(ctx, props, volumeData)
busy = false
}
});
}
......@@ -46,7 +54,9 @@ export function VolumeRepresentation<P extends VolumeProps>(visualCtor: (volumeD
return {
label: 'Volume mesh',
params: VolumeParams,
get renderObjects () { return renderObjects },
get renderObjects() {
return visual && visual.renderObject ? [ visual.renderObject ] : []
},
get props () { return _props },
createOrUpdate,
getLoci(pickingId: PickingId) {
......
......@@ -17,58 +17,91 @@ import { Loci, EmptyLoci } from 'mol-model/loci';
import { LocationIterator } from '../../util/location-iterator';
import { NullLocation } from 'mol-model/location';
import { createIdentityTransform } from '../../geometry/transform-data';
import { createRenderableState } from '../../geometry/geometry';
import { createRenderableState, updateRenderableState } from '../../geometry/geometry';
import { paramDefaultValues, NumberParam } from 'mol-view/parameter';
import { ValueCell } from 'mol-util';
export function computeVolumeSurface(volume: VolumeData, isoValue: VolumeIsoValue) {
export function computeVolumeSurface(volume: VolumeData, isoValue: VolumeIsoValue, mesh?: Mesh) {
return Task.create<Mesh>('Volume Surface', async ctx => {
ctx.update({ message: 'Marching cubes...' });
const mesh = await computeMarchingCubesMesh({
const surface = await computeMarchingCubesMesh({
isoLevel: VolumeIsoValue.toAbsolute(isoValue).absoluteValue,
scalarField: volume.data
}).runAsChild(ctx);
}, mesh).runAsChild(ctx);
const transform = VolumeData.getGridToCartesianTransform(volume);
ctx.update({ message: 'Transforming mesh...' });
Mesh.transformImmediate(mesh, transform);
Mesh.transformImmediate(surface, transform);
Mesh.computeNormalsImmediate(surface)
return mesh;
return surface;
});
}
export const IsosurfaceParams = {
...Mesh.Params,
isoValue: NumberParam('Iso Value', '', 2, -5, 5, 0.01),
isoValue: NumberParam('Iso Value', '', 2, -15, 15, 0.01),
}
export const DefaultIsosurfaceProps = paramDefaultValues(IsosurfaceParams)
export type IsosurfaceProps = typeof DefaultIsosurfaceProps
export default function IsosurfaceVisual(): VolumeVisual<IsosurfaceProps> {
let renderObject: MeshRenderObject
let currentProps = DefaultIsosurfaceProps
let renderObject: MeshRenderObject
let currentVolume: VolumeData
let mesh: Mesh
async function create(ctx: RuntimeContext, volume: VolumeData, props: Partial<IsosurfaceProps> = {}) {
currentProps = { ...DefaultIsosurfaceProps, ...props }
mesh = await computeVolumeSurface(volume, VolumeIsoValue.relative(volume.dataStats, currentProps.isoValue)).runAsChild(ctx)
const locationIt = LocationIterator(1, 1, () => NullLocation)
const transform = createIdentityTransform()
const values = await Mesh.createValues(ctx, mesh, transform, locationIt, currentProps)
const state = createRenderableState(currentProps)
renderObject = createMeshRenderObject(values, state)
}
async function update(ctx: RuntimeContext, props: Partial<IsosurfaceProps> = {}) {
const newProps = { ...currentProps, ...props }
let createMesh = false
if (newProps.isoValue !== currentProps.isoValue) createMesh = true
if (createMesh) {
mesh = await computeVolumeSurface(currentVolume, VolumeIsoValue.relative(currentVolume.dataStats, currentProps.isoValue), mesh).runAsChild(ctx)
ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
}
Mesh.updateValues(renderObject.values, newProps)
updateRenderableState(renderObject.state, newProps)
currentProps = newProps
return true
}
return {
get renderObject () { return renderObject },
async createOrUpdate(ctx: RuntimeContext, props: Partial<IsosurfaceProps> = {}, volume?: VolumeData) {
currentProps = { ...DefaultIsosurfaceProps, ...props }
console.log('MOINMOIN')
if (!volume) return
const mesh = await computeVolumeSurface(volume, VolumeIsoValue.relative(volume.dataStats, currentProps.isoValue)).runAsChild(ctx)
if (!props.flatShaded) {
Mesh.computeNormalsImmediate(mesh)
if (!volume && !currentVolume) {
throw new Error('missing volume')
} else if (volume && (!currentVolume || !renderObject)) {
currentVolume = volume
await create(ctx, volume, props)
} else if (volume && volume !== currentVolume) {
currentVolume = volume
await create(ctx, volume, props)
} else {
await update(ctx, props)
}
const locationIt = LocationIterator(1, 1, () => NullLocation)
const transform = createIdentityTransform()
const values = await Mesh.createValues(ctx, mesh, transform, locationIt, currentProps)
const state = createRenderableState(currentProps)
currentProps = { ...DefaultIsosurfaceProps, ...props }
renderObject = createMeshRenderObject(values, state)
console.log('renderObject', renderObject)
},
getLoci(pickingId: PickingId) {
// TODO
......
......@@ -176,6 +176,72 @@ namespace Mat4 {
return Mat4.copy(Mat4.zero(), a);
}
/**
* Returns the translation vector component of a transformation matrix.
*/
export function getTranslation(out: Vec3, mat: Mat4) {
out[0] = mat[12];
out[1] = mat[13];
out[2] = mat[14];
return out;
}
/**
* Returns the scaling factor component of a transformation matrix.
*/
export function getScaling(out: Vec3, mat: Mat4) {
let m11 = mat[0];
let m12 = mat[1];
let m13 = mat[2];
let m21 = mat[4];
let m22 = mat[5];
let m23 = mat[6];
let m31 = mat[8];
let m32 = mat[9];
let m33 = mat[10];
out[0] = Math.sqrt(m11 * m11 + m12 * m12 + m13 * m13);
out[1] = Math.sqrt(m21 * m21 + m22 * m22 + m23 * m23);
out[2] = Math.sqrt(m31 * m31 + m32 * m32 + m33 * m33);
return out;
}
/**
* Returns a quaternion representing the rotational component of a transformation matrix.
*/
export function getRotation(out: Quat, mat: Mat4) {
// Algorithm taken from http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
let trace = mat[0] + mat[5] + mat[10];
let S = 0;
if (trace > 0) {
S = Math.sqrt(trace + 1.0) * 2;
out[3] = 0.25 * S;
out[0] = (mat[6] - mat[9]) / S;
out[1] = (mat[8] - mat[2]) / S;
out[2] = (mat[1] - mat[4]) / S;
} else if ((mat[0] > mat[5]) && (mat[0] > mat[10])) {
S = Math.sqrt(1.0 + mat[0] - mat[5] - mat[10]) * 2;
out[3] = (mat[6] - mat[9]) / S;
out[0] = 0.25 * S;
out[1] = (mat[1] + mat[4]) / S;
out[2] = (mat[8] + mat[2]) / S;
} else if (mat[5] > mat[10]) {
S = Math.sqrt(1.0 + mat[5] - mat[0] - mat[10]) * 2;
out[3] = (mat[8] - mat[2]) / S;
out[0] = (mat[1] + mat[4]) / S;
out[1] = 0.25 * S;
out[2] = (mat[6] + mat[9]) / S;
} else {
S = Math.sqrt(1.0 + mat[10] - mat[0] - mat[5]) * 2;
out[3] = (mat[1] - mat[4]) / S;
out[0] = (mat[8] + mat[2]) / S;
out[1] = (mat[6] + mat[9]) / S;
out[2] = 0.25 * S;
}
return out;
}
export function transpose(out: Mat4, a: Mat4) {
// If we are transposing ourselves we can skip a few steps but have to cache some values
if (out === a) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment