diff --git a/data/rcsb-graphql/codegen.js b/data/rcsb-graphql/codegen.js new file mode 100644 index 0000000000000000000000000000000000000000..e66716d8ecab5d1b66e8e993d91d91465e950e8b --- /dev/null +++ b/data/rcsb-graphql/codegen.js @@ -0,0 +1,18 @@ +const { generate } = require('graphql-code-generator') +const path = require('path') + +const basePath = path.join(__dirname, '..', '..', 'src', 'servers', 'model', 'properties', 'rcsb', 'graphql') + +generate({ + args: [ + path.join(basePath, 'symmetry.gql.ts') + ], + schema: 'http://rest-experimental.rcsb.org/graphql', + template: 'typescript', + out: path.join(basePath), + skipSchema: true, + overwrite: true, + config: path.join(__dirname, 'codegen.json') +}, true).then( + console.log('done') +).catch(e => console.error(e)) \ No newline at end of file diff --git a/data/rcsb-graphql/codegen.json b/data/rcsb-graphql/codegen.json new file mode 100644 index 0000000000000000000000000000000000000000..a005e9369d5807dfdd581297c9556aa8a08ccbba --- /dev/null +++ b/data/rcsb-graphql/codegen.json @@ -0,0 +1,8 @@ +{ + "flattenTypes": false, + "generatorConfig": { + "printTime": true, + "immutableTypes": true, + "resolvers": false + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d150b46ca731750dc9d2ed16995cbadd4e52fed2..7c984dd0b938e852ed5cd265f4bf59b79e191c9b 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index dead84e32b58210e47db72fb3e84d85d2db0a32c..0627484b076a23d20d22ae187269ec66c30f35a9 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,9 @@ }, "scripts": { "lint": "tslint src/**/*.ts", - "build": "cpx \"src/**/*.{vert,frag,glsl,scss,woff,woff2,ttf,otf,eot,svg,html}\" build/node_modules/ && tsc", + "build": "cpx \"src/**/*.{vert,frag,glsl,scss,woff,woff2,ttf,otf,eot,svg,html,gql}\" build/node_modules/ && tsc", "watch": "tsc -watch", - "watch-extra": "cpx \"src/**/*.{vert,frag,glsl,scss,woff,woff2,ttf,otf,eot,svg,html}\" build/node_modules/ --watch", + "watch-extra": "cpx \"src/**/*.{vert,frag,glsl,scss,woff,woff2,ttf,otf,eot,svg,html,gql}\" build/node_modules/ --watch", "watch-all-win": "start cmd /K npm run watch & start cmd /K npm run watch-extra & start cmd /K npm run watch-viewer & start http-server -p 1338", "test": "jest", "build-viewer": "webpack build/node_modules/apps/viewer/index.js --mode development -o build/viewer/index.js", @@ -84,6 +84,9 @@ "file-loader": "^1.1.11", "glslify-import": "^3.1.0", "glslify-loader": "^1.0.2", + "graphql-code-generator": "^0.10.5", + "graphql-codegen-typescript-template": "^0.10.5", + "graphql-tag": "^2.9.2", "jest": "^23.4.2", "jest-raw-loader": "^1.0.1", "mini-css-extract-plugin": "^0.4.1", @@ -104,6 +107,8 @@ "argparse": "^1.0.10", "compression": "^1.7.3", "express": "^4.16.3", + "graphql": "^0.13.2", + "graphql-request": "^1.8.0", "immutable": "^4.0.0-rc.9", "node-fetch": "^2.2.0", "react": "^16.4.2", diff --git a/src/servers/model/properties.ts b/src/servers/model/properties.ts index b3486a48d75c4f5dffb6e436637fa2367393a447..eafa41955940a0efc940e872b67bf8ab2fb1ac58 100644 --- a/src/servers/model/properties.ts +++ b/src/servers/model/properties.ts @@ -2,15 +2,18 @@ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { Model } from 'mol-model/structure'; import { StructureQualityReport } from './properties/structure-quality-report'; +import { SymmetryAnnotation } from './properties/rcsb/symmetry'; export function attachModelProperties(model: Model): Promise<any>[] { // return a list of promises that start attaching the props in parallel // (if there are downloads etc.) return [ - StructureQualityReport.attachFromPDBeApi(model) + StructureQualityReport.attachFromPDBeApi(model), + SymmetryAnnotation.attachFromRCSB(model) ]; } \ No newline at end of file diff --git a/src/servers/model/properties/rcsb/graphql/symmetry.gql.ts b/src/servers/model/properties/rcsb/graphql/symmetry.gql.ts new file mode 100644 index 0000000000000000000000000000000000000000..0a01a456f7dca9eb8caf5a4cad9ae42eddcf4197 --- /dev/null +++ b/src/servers/model/properties/rcsb/graphql/symmetry.gql.ts @@ -0,0 +1,28 @@ + // workaround so the query gets found by the codegen +function gql (strs: TemplateStringsArray) { return strs.raw.join('') } + +export default +gql`query RcsbSymmetry($pdbId: String) { + assemblies(pdbId: $pdbId) { + assembly_id + rcsb_annotation_symmetry { + source + symmetry_features { + type + clusters { + avg_rmsd + members + } + stoichiometry { + description + value + } + symmetry_axes { + order + start + end + } + } + } + } +}` \ No newline at end of file diff --git a/src/servers/model/properties/rcsb/graphql/types.ts b/src/servers/model/properties/rcsb/graphql/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..5283fa12db8a479aabcdac07adcbc6740534bfe2 --- /dev/null +++ b/src/servers/model/properties/rcsb/graphql/types.ts @@ -0,0 +1,197 @@ +/* tslint:disable */ +/** Generated in 2018-08-03T15:19:31-07:00 */ + +export enum PdbxLeavingAtomFlag { + N = "N", + Y = "Y" +} + +export enum PdbxStereoConfig { + N = "N", + R = "R", + S = "S" +} + +export enum ExperimentalSupport { + ASSAY_FOR_OLIGOMERIZATION = "ASSAY_FOR_OLIGOMERIZATION", + CROSS_LINKING = "CROSS_LINKING", + EQUILIBRIUM_CENTRIFUGATION = "EQUILIBRIUM_CENTRIFUGATION", + FLUORESCENCE_RESONANCE_ENERGY_TRANSFER = "FLUORESCENCE_RESONANCE_ENERGY_TRANSFER", + GEL_FILTRATION = "GEL_FILTRATION", + HOMOLOGY = "HOMOLOGY", + IMMUNOPRECIPITATION = "IMMUNOPRECIPITATION", + ISOTHERMAL_TITRATION_CALORIMETRY = "ISOTHERMAL_TITRATION_CALORIMETRY", + LIGHT_SCATTERING = "LIGHT_SCATTERING", + MASS_SPECTROMETRY = "MASS_SPECTROMETRY", + MICROSCOPY = "MICROSCOPY", + NATIVE_GEL_ELECTROPHORESIS = "NATIVE_GEL_ELECTROPHORESIS", + NONE = "NONE", + SAXS = "SAXS", + SCANNING_TRANSMISSION_ELECTRON_MICROSCOPY = "SCANNING_TRANSMISSION_ELECTRON_MICROSCOPY", + SURFACE_PLASMON_RESONANCE = "SURFACE_PLASMON_RESONANCE" +} + +export enum Type { + GLOBAL = "GLOBAL", + LOCAL = "LOCAL", + PSEUDO = "PSEUDO" +} + +export enum UnpublishedFlag { + N = "N", + Y = "Y" +} + +export enum PdbxDiffrnProtocol { + LAUE = "LAUE", + MAD = "MAD", + SINGLE_WAVELENGTH = "SINGLE_WAVELENGTH" +} + +export enum PdbxMonochromaticOrLaueML { + L = "L", + M = "M" +} + +export enum PdbxScatteringType { + ELECTRON = "ELECTRON", + NEUTRON = "NEUTRON", + X_RAY = "X_RAY" +} + +export enum Source { + ELECTRON_MICROSCOPE = "ELECTRON_MICROSCOPE", + FREE_ELECTRON_LASER = "FREE_ELECTRON_LASER", + LIQUID_ANODE = "LIQUID_ANODE", + NUCLEAR_REACTOR = "NUCLEAR_REACTOR", + ROTATING_ANODE = "ROTATING_ANODE", + SEALED_TUBE = "SEALED_TUBE", + SPALLATION_SOURCE = "SPALLATION_SOURCE", + SYNCHROTRON = "SYNCHROTRON" +} + +export enum RefSpace { + REAL = "REAL", + RECIPROCAL = "RECIPROCAL" +} + +export enum SymmetryType { + HELICAL = "HELICAL", + POINT = "POINT", + _2_D_CRYSTAL = "_2_D_CRYSTAL", + _3_D_CRYSTAL = "_3_D_CRYSTAL" +} + +export enum AggregationState { + CELL = "CELL", + FILAMENT = "FILAMENT", + HELICAL_ARRAY = "HELICAL_ARRAY", + PARTICLE = "PARTICLE", + TISSUE = "TISSUE", + _2_D_ARRAY = "_2_D_ARRAY", + _3_D_ARRAY = "_3_D_ARRAY" +} + +export enum ReconstructionMethod { + CRYSTALLOGRAPHY = "CRYSTALLOGRAPHY", + HELICAL = "HELICAL", + SINGLE_PARTICLE = "SINGLE_PARTICLE", + SUBTOMOGRAM_AVERAGING = "SUBTOMOGRAM_AVERAGING", + TOMOGRAPHY = "TOMOGRAPHY" +} + +export enum EmbeddingApplied { + NO = "NO", + YES = "YES" +} + +export enum StainingApplied { + NO = "NO", + YES = "YES" +} + +export enum VitrificationApplied { + NO = "NO", + YES = "YES" +} + +export enum SrcMethod { + MAN = "MAN", + NAT = "NAT", + SYN = "SYN" +} + +export enum RcsbType { + DNA = "DNA", + HYBRID = "HYBRID", + OTHER = "OTHER", + POLYPEPTIDE = "POLYPEPTIDE", + RNA = "RNA" +} + +export enum Level { + _100 = "_100", + _30 = "_30", + _40 = "_40", + _50 = "_50", + _60 = "_60", + _70 = "_70", + _80 = "_80", + _90 = "_90", + _95 = "_95" +} + +export enum PdbFormatCompatible { + N = "N", + Y = "Y" +} + +export namespace RcsbSymmetry { + export type Variables = { + readonly pdbId?: string | null; + }; + + export type Query = { + readonly __typename?: "Query"; + readonly assemblies?: ReadonlyArray<Assemblies | null> | null; + }; + + export type Assemblies = { + readonly __typename?: "CoreAssembly"; + readonly assembly_id?: number | null; + readonly rcsb_annotation_symmetry?: RcsbAnnotationSymmetry | null; + }; + + export type RcsbAnnotationSymmetry = { + readonly __typename?: "RcsbAnnotationSymmetry"; + readonly source?: string | null; + readonly symmetry_features?: ReadonlyArray<SymmetryFeatures | null> | null; + }; + + export type SymmetryFeatures = { + readonly __typename?: "SymmetryFeature"; + readonly type?: Type | null; + readonly clusters?: ReadonlyArray<Clusters | null> | null; + readonly stoichiometry?: Stoichiometry | null; + readonly symmetry_axes?: ReadonlyArray<SymmetryAxes | null> | null; + }; + + export type Clusters = { + readonly __typename?: "Cluster"; + readonly avg_rmsd?: number | null; + readonly members?: ReadonlyArray<string | null> | null; + }; + + export type Stoichiometry = { + readonly __typename?: "Stoichiometry"; + readonly description?: string | null; + readonly value?: ReadonlyArray<string | null> | null; + }; + + export type SymmetryAxes = { + readonly __typename?: "SymmetryAxis"; + readonly order?: number | null; + readonly start?: ReadonlyArray<number | null> | null; + readonly end?: ReadonlyArray<number | null> | null; + }; +} diff --git a/src/servers/model/properties/rcsb/symmetry.ts b/src/servers/model/properties/rcsb/symmetry.ts new file mode 100644 index 0000000000000000000000000000000000000000..e34934c8d5e374ea45c1dfc8ed26ca8e75f872b1 --- /dev/null +++ b/src/servers/model/properties/rcsb/symmetry.ts @@ -0,0 +1,150 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { GraphQLClient } from 'graphql-request' + +import { RcsbSymmetry } from './graphql/types'; +import query from './graphql/symmetry.gql'; + +import { Model, ModelPropertyDescriptor } from 'mol-model/structure'; +import { CifWriter } from 'mol-io/writer/cif'; +import { Database as _Database, Column, Table } from 'mol-data/db' +import { Category } from 'mol-io/writer/cif/encoder'; +import { Tensor } from 'mol-math/linear-algebra'; +import { CifExportContext } from 'mol-model/structure/export/mmcif'; + +const { str, int, float, Aliased, Vector, List } = Column.Schema; + +function getInstance(name: keyof SymmetryAnnotation.Schema): (ctx: CifExportContext) => CifWriter.Category.Instance<any, any> { + return function(ctx: CifExportContext) { + const db = SymmetryAnnotation.get(ctx.model); + return db ? Category.ofTable(db[name]) : CifWriter.Category.Empty; + } +} + +function getCategory(name: keyof SymmetryAnnotation.Schema) { + return { name, instance: getInstance(name) } +} + +function createDatabase(assemblies: ReadonlyArray<RcsbSymmetry.Assemblies>): SymmetryAnnotation.Database { + const Schema = SymmetryAnnotation.Schema + + const featureRows: Table.Row<typeof Schema.symmetry_annotation_feature>[] = [] + const clusterRows: Table.Row<typeof Schema.symmetry_annotation_cluster>[] = [] + const axisRows: Table.Row<typeof Schema.symmetry_annotation_axis>[] = [] + + let id = 0 + for (let i = 0, il = assemblies.length; i < il; ++i) { + const a = assemblies[i] + const assembly_id = (a.assembly_id!).toString() + const source = a.rcsb_annotation_symmetry!.source! + const symmetry_features = a.rcsb_annotation_symmetry!.symmetry_features! + for (let j = 0, jl = symmetry_features.length; j < jl; ++j) { + const f = symmetry_features[j]! + featureRows.push({ + id, + assembly_id, + source, + type: f.type!, + stoichiometry_value: (f.stoichiometry!.value!) as string[], + stoichiometry_description: f.stoichiometry!.description! + }) + + const clusters = f.clusters + if (clusters) { + for (let k = 0, kl = clusters.length; k < kl; ++k) { + const c = clusters[k]! + clusterRows.push({ + feature_id: id, + avg_rmsd: c.avg_rmsd!, + members: c.members as string[] + }) + } + } + + const axes = f.symmetry_axes + if (axes) { + for (let k = 0, kl = axes.length; k < kl; ++k) { + const a = axes[k]! + axisRows.push({ + feature_id: id, + order: a.order!, + start: a.start as Tensor.Data, + end: a.end as Tensor.Data + }) + } + } + + id += 1 + } + } + + return _Database.ofTables('symmetry_annotation', Schema, { + symmetry_annotation_feature: Table.ofRows(Schema.symmetry_annotation_feature, featureRows), + symmetry_annotation_cluster: Table.ofRows(Schema.symmetry_annotation_cluster, clusterRows), + symmetry_annotation_axis: Table.ofRows(Schema.symmetry_annotation_axis, axisRows) + }) +} + +const _Descriptor: ModelPropertyDescriptor = { + isStatic: true, + name: 'symmetry_annotation', + cifExport: { + categories: [ + getCategory('symmetry_annotation_feature'), + getCategory('symmetry_annotation_cluster'), + getCategory('symmetry_annotation_axis') + ] + } +} + +const client = new GraphQLClient('http://rest-experimental.rcsb.org/graphql') + +export namespace SymmetryAnnotation { + export const Schema = { + symmetry_annotation_feature: { + id: int, + assembly_id: str, + source: str, + type: Aliased<'GLOBAL' | 'LOCAL' | 'PSEUDO'>(str), + stoichiometry_value: List(',', x => x), + stoichiometry_description: str + }, + symmetry_annotation_cluster: { + feature_id: int, + avg_rmsd: float, + members: List(',', x => x) + }, + symmetry_annotation_axis: { + feature_id: int, + order: int, + start: Vector(3), + end: Vector(3) + } + } + export type Schema = typeof Schema + export interface Database extends _Database<Schema> {} + + export const Descriptor = _Descriptor; + + export async function attachFromRCSB(model: Model) { + if (model.customProperties.has(Descriptor)) return true; + + const variables: RcsbSymmetry.Variables = { pdbId: model.label.toLowerCase() }; + const result = await client.request<RcsbSymmetry.Query>(query, variables); + if (!result || !result.assemblies) return false; + + const db: Database = createDatabase(result.assemblies as ReadonlyArray<RcsbSymmetry.Assemblies>) + model.customProperties.add(Descriptor); + model._staticPropertyData.__SymmetryAnnotation__ = db; + + return true; + } + + export function get(model: Model): Database | undefined { + return model._staticPropertyData.__SymmetryAnnotation__; + } +} \ No newline at end of file diff --git a/src/servers/model/test.ts b/src/servers/model/test.ts index 882c18b6afc6095f813f4f12c2e5e48bd07b0d3a..14bafb1bac02f982ded8770439dc5e5532bcf5c8 100644 --- a/src/servers/model/test.ts +++ b/src/servers/model/test.ts @@ -1,5 +1,6 @@ import { resolveJob } from './server/query'; import * as fs from 'fs' +import * as path from 'path' import { StructureCache } from './server/structure-wrapper'; import { createJob } from './server/jobs'; @@ -33,13 +34,25 @@ function wrapFile(fn: string) { return w; } +const basePath = path.join(__dirname, '..', '..', '..', '..') +const examplesPath = path.join(basePath, 'examples') +const outPath = path.join(basePath, 'build', 'test') +if (!fs.existsSync(outPath)) fs.mkdirSync(outPath); + async function run() { try { - const request = createJob('_local_', 'e:/test/quick/1cbs_updated.cif', 'residueInteraction', { label_comp_id: 'REA' }); + // const request = createJob('_local_', 'e:/test/quick/1cbs_updated.cif', 'residueInteraction', { label_comp_id: 'REA' }); + // const encoder = await resolveJob(request); + // const writer = wrapFile('e:/test/mol-star/1cbs_full.cif'); + // const testFile = '1crn.cif' + const testFile = '1grm_updated.cif' + const request = createJob('_local_', path.join(examplesPath, testFile), 'full', {}); const encoder = await resolveJob(request); - const writer = wrapFile('e:/test/mol-star/1cbs_full.cif'); + const writer = wrapFile(path.join(outPath, testFile)); encoder.writeTo(writer); writer.end(); + } catch (e) { + console.error(e) } finally { StructureCache.expireAll(); }