From 8802f518d7a16ad6296e52840678329e4177125a Mon Sep 17 00:00:00 2001
From: Alexander Rose <alex.rose@rcsb.org>
Date: Fri, 3 Aug 2018 18:26:57 -0700
Subject: [PATCH] wip, symmetry annotation, graphql schema codegen

---
 data/rcsb-graphql/codegen.js                  |  18 ++
 data/rcsb-graphql/codegen.json                |   8 +
 package-lock.json                             | Bin 399947 -> 418525 bytes
 package.json                                  |   9 +-
 src/servers/model/properties.ts               |   5 +-
 .../properties/rcsb/graphql/symmetry.gql.ts   |  28 +++
 .../model/properties/rcsb/graphql/types.ts    | 197 ++++++++++++++++++
 src/servers/model/properties/rcsb/symmetry.ts | 150 +++++++++++++
 src/servers/model/test.ts                     |  17 +-
 9 files changed, 427 insertions(+), 5 deletions(-)
 create mode 100644 data/rcsb-graphql/codegen.js
 create mode 100644 data/rcsb-graphql/codegen.json
 create mode 100644 src/servers/model/properties/rcsb/graphql/symmetry.gql.ts
 create mode 100644 src/servers/model/properties/rcsb/graphql/types.ts
 create mode 100644 src/servers/model/properties/rcsb/symmetry.ts

diff --git a/data/rcsb-graphql/codegen.js b/data/rcsb-graphql/codegen.js
new file mode 100644
index 000000000..e66716d8e
--- /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 000000000..a005e9369
--- /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
GIT binary patch
delta 8462
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
F|3A{#_E-P_

delta 317
zcmV-D0mA;>#Tm=A7_cz}v!({X3A3*gdIXa=5D%BXA_N$d4Qw)#?-B=-I1mi8t{M3R
zlkh|ilOQn$vzH%IOtbfHD|NF><ueMh^SBtdeGCCI0=K~r0mclsy&VCY8Ml2p0i_VP
z<Vyhy7`NVJ0n`Y$wr&AaK)1-R0cDc6c2xp98MoeV0v9ir`4$EdmyZ1c4VN&+1T~kf
zXaW|ujiCbQ2$zei0>`&)tpdasw<N{_`*ODrI|IBdll0vZm;NFF7?*%f12ebpXafWe
zw>5YJ&0V()*8~3rw?W<mumQLD-~%BYmx|H?3%4)-0~xTFHHifkx0bL3dlk1E#{{1s
zw~*)rUo*EE9R;N`x9Cv?a7(vHp9Lljw>+%{LYkMB$paFXT{8p>m!NL}1eec012VUi
PN(RFf2MQn{eF}XF<)C~?

diff --git a/package.json b/package.json
index dead84e32..0627484b0 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 b3486a48d..eafa41955 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 000000000..0a01a456f
--- /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 000000000..5283fa12d
--- /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 000000000..e34934c8d
--- /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 882c18b6a..14bafb1ba 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();
     }
-- 
GitLab