diff --git a/src/mol-io/reader/cif/schema/density.ts b/src/apps/structure-info/density.ts similarity index 100% rename from src/mol-io/reader/cif/schema/density.ts rename to src/apps/structure-info/density.ts diff --git a/src/apps/structure-info/helpers.ts b/src/apps/structure-info/helpers.ts new file mode 100644 index 0000000000000000000000000000000000000000..45fb1c0322837fbf02d7ced63000c1e9f2fc3f2d --- /dev/null +++ b/src/apps/structure-info/helpers.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import * as util from 'util' +import * as fs from 'fs' +import fetch from 'node-fetch' +require('util.promisify').shim(); + +import CIF from 'mol-io/reader/cif' +import { Run, Progress } from 'mol-task' + +const readFileAsync = util.promisify(fs.readFile); + +async function readFile(path: string) { + if (path.match(/\.bcif$/)) { + const input = await readFileAsync(path) + const data = new Uint8Array(input.byteLength); + for (let i = 0; i < input.byteLength; i++) data[i] = input[i]; + return data; + } else { + return readFileAsync(path, 'utf8'); + } +} + +async function parseCif(data: string|Uint8Array) { + const comp = CIF.parse(data); + const parsed = await Run(comp, p => console.log(Progress.format(p)), 250); + if (parsed.isError) throw parsed; + return parsed +} + +export async function openCif(path: string) { + const data = await readFile(path); + return parseCif(data); +} + +export async function downloadCif(url: string, isBinary: boolean) { + const data = await fetch(url); + return parseCif(isBinary ? new Uint8Array(await data.arrayBuffer()) : await data.text()); +} \ No newline at end of file diff --git a/src/apps/structure-info/index.ts b/src/apps/structure-info/index.ts index 3fb2e6b313855163791c86e86ecb9906d66ce3ca..93b84cb096bb82196e6658cfd56d2bd9d12f3c59 100644 --- a/src/apps/structure-info/index.ts +++ b/src/apps/structure-info/index.ts @@ -2,54 +2,32 @@ * 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> */ import * as argparse from 'argparse' -import * as util from 'util' -import * as fs from 'fs' -import fetch from 'node-fetch' require('util.promisify').shim(); // import { Table } from 'mol-data/db' import CIF from 'mol-io/reader/cif' import { Model, Structure, Element, ElementSet, Unit, ElementGroup, Queries } from 'mol-model/structure' -import { Run, Progress } from 'mol-task' +// import { Run, Progress } from 'mol-task' import { OrderedSet } from 'mol-data/int'; import { Table } from 'mol-data/db'; import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif'; import CoarseGrained from 'mol-model/structure/model/properties/coarse-grained'; +import { openCif, downloadCif } from './helpers'; -const readFileAsync = util.promisify(fs.readFile); -async function readFile(path: string) { - if (path.match(/\.bcif$/)) { - const input = await readFileAsync(path) - const data = new Uint8Array(input.byteLength); - for (let i = 0; i < input.byteLength; i++) data[i] = input[i]; - return data; - } else { - return readFileAsync(path, 'utf8'); - } -} - -async function readCif(path: string) { - const data = await readFile(path); - const parsed = await parseCif(data); - return CIF.schema.mmCIF(parsed.result.blocks[0]) -} - -async function parseCif(data: string|Uint8Array) { - const comp = CIF.parse(data); - const parsed = await Run(comp, p => console.log(Progress.format(p)), 250); - if (parsed.isError) throw parsed; - return parsed +async function downloadFromPdb(pdb: string) { + // `https://files.rcsb.org/download/${pdb}.cif` + const parsed = await downloadCif(`http://www.ebi.ac.uk/pdbe/static/entry/${pdb}_updated.cif`, false); + return CIF.schema.mmCIF(parsed.result.blocks[0]); } -async function getPdb(pdb: string) { - // const data = await fetch(`https://files.rcsb.org/download/${pdb}.cif`) - const data = await fetch(`http://www.ebi.ac.uk/pdbe/static/entry/${pdb}_updated.cif`); - const parsed = await parseCif(await data.text()) - return CIF.schema.mmCIF(parsed.result.blocks[0]) +async function readPdbFile(path: string) { + const parsed = await openCif(path); + return CIF.schema.mmCIF(parsed.result.blocks[0]); } export function atomLabel(model: Model, aI: number) { @@ -154,12 +132,12 @@ async function run(mmcif: mmCIF_Database) { } async function runDL(pdb: string) { - const mmcif = await getPdb(pdb) + const mmcif = await downloadFromPdb(pdb) run(mmcif); } async function runFile(filename: string) { - const mmcif = await readCif(filename); + const mmcif = await readPdbFile(filename); run(mmcif); } diff --git a/src/mol-data/db/column.ts b/src/mol-data/db/column.ts index b50672afbb7cb0673c8ea8850db4976e18a74d13..1b0f46ce483df2f61039f0ae9d8612e927cdc130 100644 --- a/src/mol-data/db/column.ts +++ b/src/mol-data/db/column.ts @@ -34,7 +34,7 @@ namespace Column { export type Float = { '@type': 'float', T: number } & Base<'float'> export type Coordinate = { '@type': 'coord', T: number } & Base<'float'> - export type Tensor = { '@type': 'tensor', T: Tensors.Data, space: Tensors.Space } & Base<'tensor'> + export type Tensor = { '@type': 'tensor', T: Tensors.Data, space: Tensors.Space, baseType: Int | Float } & Base<'tensor'> export type Aliased<T> = { '@type': 'aliased', T: T } & Base<'str' | 'int'> export type List<T extends number|string> = { '@type': 'list', T: T[], separator: string, itemParse: (x: string) => T } & Base<'list'> @@ -46,9 +46,9 @@ namespace Column { export function Str(defaultValue = ''): Str { return { '@type': 'str', T: defaultValue, valueType: 'str' } }; export function Int(defaultValue = 0): Int { return { '@type': 'int', T: defaultValue, valueType: 'int' } }; export function Float(defaultValue = 0): Float { return { '@type': 'float', T: defaultValue, valueType: 'float' } }; - export function Tensor(space: Tensors.Space): Tensor { return { '@type': 'tensor', T: space.create(), space, valueType: 'tensor' }; } - export function Vector(dim: number): Tensor { return Tensor(Tensors.Vector(dim)); } - export function Matrix(rows: number, cols: number): Tensor { return Tensor(Tensors.ColumnMajorMatrix(rows, cols)); } + export function Tensor(space: Tensors.Space, baseType: Int | Float = float): Tensor { return { '@type': 'tensor', T: space.create(), space, valueType: 'tensor', baseType }; } + export function Vector(dim: number, baseType: Int | Float = float): Tensor { return Tensor(Tensors.Vector(dim), baseType); } + export function Matrix(rows: number, cols: number, baseType: Int | Float = float): Tensor { return Tensor(Tensors.ColumnMajorMatrix(rows, cols), baseType); } export function Aliased<T>(t: Str | Int, defaultValue?: T): Aliased<T> { if (typeof defaultValue !== 'undefined') return { ...t, T: defaultValue } as any as Aliased<T>; diff --git a/src/mol-io/reader/cif/schema/density-server.ts b/src/mol-io/reader/cif/schema/density-server.ts new file mode 100644 index 0000000000000000000000000000000000000000..646f74a6b93fadb0fe4e0d0e892cdb5bc28a214f --- /dev/null +++ b/src/mol-io/reader/cif/schema/density-server.ts @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Database, Column } from 'mol-data/db' + +import Schema = Column.Schema + +const str = Schema.str; +const int = Schema.int; +const float = Schema.float; + +const Aliased = Schema.Aliased; +const Vector = Schema.Vector; + +export const DensityServer_Header_Schema = { + density_server_result: { + 'server_version': str, + 'datetime_utc': str, + 'guid': str, + 'is_empty': Aliased<'no' | 'n' | 'yes' | 'y'>(str), + 'has_error': Aliased<'no' | 'n' | 'yes' | 'y'>(str), + 'error': str, + 'query_source_id': str, + 'query_type': Aliased<'box' | 'cell'>(str), + 'query_box_type': Aliased<'cartesian' | 'fractional'>(str), + 'query_box_a': Vector(3), + 'query_box_b': Vector(3) + } +} + +export const DensityServer_Data_Schema = { + volume_data_3d_info: { + 'name': str, + // zero indexed axis order of the data + 'axis_order': Vector(3, int), + // Origin in fractional coords + 'origin': Vector(3), + // Dimension in fractional coords + 'dimensions': Vector(3), + 'sample_rate': int, + // number of samples along each axis + 'sample_count': Vector(3, int), + 'spacegroup_number': int, + 'spacegroup_cell_size': Vector(3), + // angles in degrees + 'spacegroup_cell_angles': Vector(3), + 'mean_source': float, + 'mean_sampled': float, + 'sigma_source': float, + 'sigma_sampled': float, + 'min_source': float, + 'min_sampled': float, + 'max_source': float, + 'max_sampled': float + }, + volume_data_3d: { + values: float + } +} + +export type DensityServer_Header_Schema = typeof DensityServer_Header_Schema; +export interface DensityServer_Header_Database extends Database<DensityServer_Header_Schema> {} + +export type DensityServer_Data_Schema = typeof DensityServer_Data_Schema; +export interface DensityServer_Data_Database extends Database<DensityServer_Data_Schema> {} \ No newline at end of file diff --git a/src/mol-math/geometry/primitives/box3d.ts b/src/mol-math/geometry/primitives/box3d.ts index 90b6b241c113c05083456013b2ed3d7b6eb5fbcc..efceedb4f763741e1ff6ba3b7476f58a287ac18d 100644 --- a/src/mol-math/geometry/primitives/box3d.ts +++ b/src/mol-math/geometry/primitives/box3d.ts @@ -11,6 +11,8 @@ import { OrderedSet } from 'mol-data/int'; interface Box3D { min: Vec3, max: Vec3 } namespace Box3D { + export function create(min: Vec3, max: Vec3): Box3D { return { min, max }; } + export function computeBounding(data: PositionData): Box3D { const min = [Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE]; const max = [-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE]; @@ -28,6 +30,10 @@ namespace Box3D { return { min: Vec3.create(min[0], min[1], min[2]), max: Vec3.create(max[0], max[1], max[2]) } } + export function size(box: Box3D) { + return Vec3.sub(Vec3.zero(), box.max, box.min); + } + export function expand(box: Box3D, delta: Vec3): Box3D { return { min: Vec3.sub(Vec3.zero(), box.min, delta), diff --git a/src/mol-math/geometry/spacegroup/construction.ts b/src/mol-math/geometry/spacegroup/construction.ts index 807eb02ad96ad1c9df499b95459bdb7206866330..626782b73af8f1392da9dd353ed24f1d7397a4f3 100644 --- a/src/mol-math/geometry/spacegroup/construction.ts +++ b/src/mol-math/geometry/spacegroup/construction.ts @@ -9,23 +9,34 @@ import { SpacegroupName, TransformData, GroupData, SpacegroupNumbers, Spacegroup import { SymmetryOperator } from '../../geometry'; interface SpacegroupCell { + readonly number: number, readonly size: Vec3, readonly anglesInRadians: Vec3, /** Transfrom cartesian -> fractional coordinates within the cell */ readonly toFractional: Mat4, /** Transfrom fractional coordinates within the cell -> cartesian */ - readonly fromFractional: Mat4 + readonly fromFractional: Mat4, + readonly fractionalBasis: Vec3[] } interface Spacegroup { - readonly number: number, readonly name: string, readonly cell: SpacegroupCell, readonly operators: ReadonlyArray<Mat4> } namespace SpacegroupCell { - export function create(size: Vec3, anglesInRadians: Vec3): SpacegroupCell { + // Create a 'P 1' with cellsize [1, 1, 1] + export function zero() { + return create(0, Vec3.create(1, 1, 1), Vec3.create(Math.PI / 2, Math.PI / 2, Math.PI / 2)); + } + + // True if 'P 1' with cellsize [1, 1, 1] + export function isZero(cell: SpacegroupCell) { + return cell.size[0] === 1 && cell.size[1] === 1 && cell.size[1] === 1; + } + + export function create(nameOrNumber: number | SpacegroupName, size: Vec3, anglesInRadians: Vec3): SpacegroupCell { const alpha = anglesInRadians[0]; const beta = anglesInRadians[1]; const gamma = anglesInRadians[2]; @@ -48,7 +59,14 @@ namespace SpacegroupCell { ]); const toFractional = Mat4.invert(Mat4.zero(), fromFractional)!; - return { size, anglesInRadians, toFractional, fromFractional }; + const fractionalBasis = [ + Vec3.transformMat4(Vec3.zero(), Vec3.create(1, 0, 0), toFractional), + Vec3.transformMat4(Vec3.zero(), Vec3.create(0, 1, 0), toFractional), + Vec3.transformMat4(Vec3.zero(), Vec3.create(0, 0, 1), toFractional) + ]; + + const num = typeof nameOrNumber === 'number' ? nameOrNumber : SpacegroupNumbers[nameOrNumber]; + return { number: num, size, anglesInRadians, toFractional, fromFractional, fractionalBasis }; } } @@ -63,7 +81,7 @@ namespace Spacegroup { const operators = GroupData[num].map(i => getOperatorMatrix(OperatorData[i])); - return { number: num, name, cell, operators }; + return { name, cell, operators }; } const _tempVec = Vec3.zero(), _tempMat = Mat4.zero(); diff --git a/src/mol-math/linear-algebra/3d/mat4.ts b/src/mol-math/linear-algebra/3d/mat4.ts index 4cb713b77ef6a535822e628f8d3e96ef3ded3c9a..930eaf8518e425ff451b638830264510a8c5d97b 100644 --- a/src/mol-math/linear-algebra/3d/mat4.ts +++ b/src/mol-math/linear-algebra/3d/mat4.ts @@ -79,6 +79,11 @@ namespace Mat4 { return mat; } + export function setZero(mat: Mat4): Mat4 { + for (let i = 0; i < 16; i++) mat[i] = 0; + return mat; + } + export function ofRows(rows: number[][]): Mat4 { const out = zero(); for (let i = 0; i < 4; i++) { @@ -741,6 +746,18 @@ namespace Mat4 { return out; } + + /** + * Perm is 0-indexed permutation + */ + export function fromPermutation(out: Mat4, perm: number[]) { + setZero(out); + for (let i = 0; i < 4; i++) { + const p = perm[i]; + setValue(out, i, p, 1); + } + return out; + } } export default Mat4 \ No newline at end of file diff --git a/src/mol-math/linear-algebra/_spec/mat4.spec.ts b/src/mol-math/linear-algebra/_spec/mat4.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..4b6668e8d63eafe34f08378ca552856b4a733284 --- /dev/null +++ b/src/mol-math/linear-algebra/_spec/mat4.spec.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Mat4, Vec3 } from '../3d' + +describe('Mat4', () => { + it('permutation', () => { + expect(Mat4.areEqual(Mat4.fromPermutation(Mat4.zero(), [0, 1, 2, 3]), Mat4.identity(), 1e-6)).toBe(true); + + expect(Mat4.areEqual(Mat4.fromPermutation(Mat4.zero(), [1, 0, 2, 3]), Mat4.ofRows([ + [0, 1, 0, 0], + [1, 0, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], + ]), 1e-6)).toBe(true); + + const perm = Mat4.fromPermutation(Mat4.zero(), [1, 2, 0, 3]); + + expect(Vec3.transformMat4(Vec3.zero(), Vec3.create(1, 2, 3), perm)).toEqual(Vec3.create(2, 3, 1)); + }); +}); \ No newline at end of file diff --git a/src/mol-math/linear-algebra/tensor.ts b/src/mol-math/linear-algebra/tensor.ts index 5a2231baf00ec1013259a3a30b21884a065003b6..976e410e1b0fb96055c435cabd873cb868791be9 100644 --- a/src/mol-math/linear-algebra/tensor.ts +++ b/src/mol-math/linear-algebra/tensor.ts @@ -49,6 +49,8 @@ export namespace Tensor { return { rank: dimensions.length, dimensions, axisOrderSlowToFast, create: creator(layout), get, set }; } + export function Data1(values: ArrayLike<number>): Data { return values as Data; } + export function Vector(d: number, ctor?: ArrayCtor) { return Space([d], [0], ctor); } export function ColumnMajorMatrix(rows: number, cols: number, ctor?: ArrayCtor) { return Space([rows, cols], [1, 0], ctor); } export function RowMajorMatrix(rows: number, cols: number, ctor?: ArrayCtor) { return Space([rows, cols], [0, 1], ctor); } diff --git a/src/mol-model/sequence/TODO b/src/mol-model/sequence/TODO index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a14553cb9d4b45b94a29e6b28b9c0478205867b3 100644 --- a/src/mol-model/sequence/TODO +++ b/src/mol-model/sequence/TODO @@ -0,0 +1,2 @@ +- Support for FASTA etc.. +- Mapping/properties for 'structure' \ No newline at end of file diff --git a/src/mol-model/volume/TODO b/src/mol-model/volume.ts similarity index 100% rename from src/mol-model/volume/TODO rename to src/mol-model/volume.ts diff --git a/src/mol-model/volume/_spec/data.spec.ts b/src/mol-model/volume/_spec/data.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..879b4fb842e26b1581ef57647aa48c69ba9ab9f5 --- /dev/null +++ b/src/mol-model/volume/_spec/data.spec.ts @@ -0,0 +1,6 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + diff --git a/src/mol-model/volume/data.ts b/src/mol-model/volume/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..628f4fe6b57b48c3b33833e09790c8671089c58c --- /dev/null +++ b/src/mol-model/volume/data.ts @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { SpacegroupCell, Box3D } from 'mol-math/geometry' +import { Tensor, Mat4, Vec3 } from 'mol-math/linear-algebra' + +interface VolumeData { + // The basic unit cell that contains the data. + readonly cell: SpacegroupCell, + readonly fractionalBox: Box3D, + readonly data: Tensor, + readonly dataStats: Readonly<{ + min: number, + max: number, + mean: number, + sigma: number + }> +} + +namespace VolumeData { + const _scale = Mat4.zero(), _translate = Mat4.zero(), _perm = Mat4.zero(); + export function getGridToCartesianTransform(volume: VolumeData) { + const { data: { space } } = volume; + const scale = Mat4.fromScaling(_scale, Vec3.div(Vec3.zero(), Box3D.size(volume.fractionalBox), Vec3.ofArray(space.dimensions))); + const translate = Mat4.fromTranslation(_translate, volume.fractionalBox.min); + const ret = Mat4.mul3(Mat4.zero(), volume.cell.fromFractional, translate, scale); + + const [x, y, z] = space.axisOrderSlowToFast; + if (x !== 0 || y !== 1 || z !== 2) { + Mat4.mul(ret, Mat4.fromPermutation(_perm, [x, y, z, 3]), ret); + } + return ret; + } +} + +export { VolumeData } \ No newline at end of file diff --git a/src/mol-model/volume/formats/density-server.ts b/src/mol-model/volume/formats/density-server.ts new file mode 100644 index 0000000000000000000000000000000000000000..c7972e690ef4ae5f40ced2651f8c49d5c6cea6fc --- /dev/null +++ b/src/mol-model/volume/formats/density-server.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { DensityServer_Data_Database } from 'mol-io/reader/cif/schema/density-server' +import { VolumeData } from '../data' +import { Task } from 'mol-task'; +import { SpacegroupCell, Box3D } from 'mol-math/geometry'; +import { Tensor, Vec3 } from 'mol-math/linear-algebra'; + +function parseDensityServerData(source: DensityServer_Data_Database): Task<VolumeData> { + return Task.create<VolumeData>('Parse Volume Data', async ctx => { + const { volume_data_3d_info: info, volume_data_3d: values } = source; + const cell = SpacegroupCell.create(info.spacegroup_number.value(0), Vec3.ofArray(info.spacegroup_cell_size.value(0)), Vec3.ofArray(info.spacegroup_cell_angles.value(0))); + + const tensorSpace = Tensor.Space(info.sample_count.value(0), info.axis_order.value(0), Float32Array); + const data = Tensor.create(tensorSpace, Tensor.Data1(values.values.toArray())); + + const origin = Vec3.ofArray(info.origin.value(0)), dimensions = Vec3.ofArray(info.dimensions.value(0)); + + return { + cell, + fractionalBox: Box3D.create(origin, Vec3.add(Vec3.zero(), origin, dimensions)), + data, + dataStats: { + min: info.min_sampled.value(0), + max: info.max_sampled.value(0), + mean: info.mean_sampled.value(0), + sigma: info.sigma_sampled.value(0) + } + }; + }); +} + +export { parseDensityServerData } \ No newline at end of file