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

Merge branch 'master' into cartoon-repr

parents 84716c3e 31e92e44
Branches
Tags
No related merge requests found
......@@ -9,11 +9,13 @@ import { StructureQuery } from './query/query'
export * from './query/context'
import * as generators from './query/queries/generators'
import * as modifiers from './query/queries/modifiers'
import * as combinators from './query/queries/combinators'
import pred from './query/predicates'
export const Queries = {
generators,
modifiers,
combinators,
pred
}
......
......@@ -6,8 +6,14 @@
import { StructureQuery } from '../query';
import { StructureSelection } from '../selection';
import { none } from './generators';
export function merge(queries: ArrayLike<StructureQuery>): StructureQuery {
if (queries.length === 0) {
return none;
} else if (queries.length === 1) {
return queries[0];
}
return ctx => {
const ret = StructureSelection.UniqueBuilder(ctx.inputStructure);
for (let i = 0; i < queries.length; i++) {
......
......@@ -11,6 +11,7 @@ import { Segmentation } from 'mol-data/int'
import { LinearGroupingBuilder } from '../utils/builders';
import { QueryPredicate, QueryFn, QueryContextView } from '../context';
export const none: StructureQuery = ctx => StructureSelection.Sequence(ctx.inputStructure, []);
export const all: StructureQuery = ctx => StructureSelection.Singletons(ctx.inputStructure, ctx.inputStructure);
export interface AtomsQueryParams {
......
......@@ -7,6 +7,10 @@
import { Model } from 'mol-model/structure';
import { StructureQualityReport } from './properties/structure-quality-report';
export async function attachModelProperties(model: Model) {
await StructureQualityReport.attachFromPDBeApi(model);
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)
];
}
\ No newline at end of file
......@@ -46,7 +46,6 @@ const _structure_quality_report_fields: CifField<ResidueIndex, ExportCtx>[] = [
CifField.int<ResidueIndex, ExportCtx>('auth_seq_id', (i, d) => P.residue.auth_seq_id(d.residues[i])),
CifField.str<ResidueIndex, ExportCtx>('auth_asym_id', (i, d) => P.chain.auth_asym_id(d.residues[i])),
CifField.str<ResidueIndex, ExportCtx>('issues', (i, d) => d.issues.get(d.residueIndex[d.residues[i].element])!.join(','))
];
......@@ -106,7 +105,7 @@ export namespace StructureQualityReport {
if (model.customProperties.has(Descriptor)) return true;
const id = model.label.toLowerCase();
const rawData = await fetch(`https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/${model.label.toLowerCase()}`);
const rawData = await fetch(`https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/${model.label.toLowerCase()}`, { timeout: 500 });
const json = await rawData.json();
const data = json[id];
if (!data) return false;
......
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { QueryPredicate, StructureElement, StructureProperties as Props, Queries } from 'mol-model/structure';
import { AtomsQueryParams } from 'mol-model/structure/query/queries/generators';
export function getAtomsTests(params: any): Partial<AtomsQueryParams>[] {
if (!params) return [{ }];
if (Array.isArray(params)) {
return params.map(p => atomsTest(p));
} else {
return [atomsTest(params)];
}
}
function atomsTest(params: any): Partial<AtomsQueryParams> {
return {
entityTest: entityTest(params),
chainTest: chainTest(params),
residueTest: residueTest(params),
atomTest: atomTest(params)
};
}
function entityTest(params: any): QueryPredicate | undefined {
if (!params || typeof params.entity_id === 'undefined') return void 0;
const p = Props.entity.id, id = '' + params.label_entity_id;
return ctx => p(ctx.element) === id;
}
function chainTest(params: any): QueryPredicate | undefined {
if (!params) return void 0;
if (typeof params.label_asym_id !== 'undefined') {
const p = Props.chain.label_asym_id, id = '' + params.label_asym_id;
return ctx => p(ctx.element) === id;
}
if (typeof params.auth_asym_id !== 'undefined') {
const p = Props.chain.auth_asym_id, id = '' + params.auth_asym_id;
return ctx => p(ctx.element) === id;
}
return void 0;
}
function residueTest(params: any): QueryPredicate | undefined {
if (!params) return void 0;
const props: StructureElement.Property<any>[] = [], values: any[] = [];
if (typeof params.label_seq_id !== 'undefined') {
props.push(Props.residue.label_seq_id);
values.push(+params.label_seq_id);
}
if (typeof params.auth_seq_id !== 'undefined') {
props.push(Props.residue.auth_seq_id);
values.push(+params.auth_seq_id);
}
if (typeof params.label_comp_id !== 'undefined') {
props.push(Props.residue.label_comp_id);
values.push(params.label_comp_id);
}
if (typeof params.auth_comp_id !== 'undefined') {
props.push(Props.residue.auth_comp_id);
values.push(params.auth_comp_id);
}
if (typeof params.pdbx_PDB_ins_code !== 'undefined') {
props.push(Props.residue.pdbx_PDB_ins_code);
values.push(params.pdbx_PDB_ins_code);
}
return andEqual(props, values);
}
function atomTest(params: any): QueryPredicate | undefined {
if (!params) return void 0;
const props: StructureElement.Property<any>[] = [], values: any[] = [];
if (typeof params.label_atom_id !== 'undefined') {
props.push(Props.atom.label_atom_id);
values.push(+params.label_atom_id);
}
if (typeof params.auth_atom_id !== 'undefined') {
props.push(Props.atom.auth_atom_id);
values.push(+params.auth_atom_id);
}
if (typeof params.type_symbol !== 'undefined') {
props.push(Props.atom.type_symbol);
values.push(+params.type_symbol);
}
return andEqual(props, values);
}
function andEqual(props: StructureElement.Property<any>[], values: any[]): QueryPredicate | undefined {
switch (props.length) {
case 0: return void 0;
case 1: return ctx => props[0](ctx.element) === values[0];
case 2: return ctx => props[0](ctx.element) === values[0] && props[1](ctx.element) === values[1];
case 3: return ctx => props[0](ctx.element) === values[0] && props[1](ctx.element) === values[1] && props[2](ctx.element) === values[2];
default: {
const len = props.length;
return ctx => {
for (let i = 0; i < len; i++) if (!props[i](ctx.element) !== values[i]) return false;
return true;
};
}
}
}
\ No newline at end of file
// TODO
\ No newline at end of file
......@@ -6,7 +6,6 @@
import * as express from 'express';
import Config from '../config';
import { QueryDefinition, QueryList } from './api';
import { ConsoleLogger } from 'mol-util/console-logger';
import { resolveJob } from './query';
import { JobManager } from './jobs';
......@@ -81,24 +80,35 @@ async function processNextJob() {
}
}
function mapQuery(app: express.Express, queryName: string, queryDefinition: QueryDefinition) {
app.get(makePath(':entryId/' + queryName), (req, res) => {
ConsoleLogger.log('Server', `Query '${req.params.entryId}/${queryName}'...`);
// function mapQuery(app: express.Express, queryName: string, queryDefinition: QueryDefinition) {
// app.get(makePath(':entryId/' + queryName), (req, res) => {
// ConsoleLogger.log('Server', `Query '${req.params.entryId}/${queryName}'...`);
if (JobManager.size >= Config.maxQueueLength) {
res.status(503).send('Too many queries, please try again later.');
res.end();
return;
}
// if (JobManager.size >= Config.maxQueueLength) {
// res.status(503).send('Too many queries, please try again later.');
// res.end();
// return;
// }
const jobId = JobManager.add('pdb', req.params.entryId, queryName, req.query);
// const jobId = JobManager.add('pdb', req.params.entryId, queryName, req.query);
// responseMap.set(jobId, res);
// if (JobManager.size === 1) processNextJob();
// });
// }
export function initWebApi(app: express.Express) {
app.get(makePath('query'), (req, res) => {
const query = /\?(.*)$/.exec(req.url)![1];
const args = JSON.parse(decodeURIComponent(query));
const name = args.name;
const entryId = args.id;
const params = args.params || { };
const jobId = JobManager.add('pdb', entryId, name, params);
responseMap.set(jobId, res);
if (JobManager.size === 1) processNextJob();
});
}
export function initWebApi(app: express.Express) {
for (const q of QueryList) {
mapQuery(app, q.name, q.definition);
}
// for (const q of QueryList) {
// mapQuery(app, q.name, q.definition);
// }
}
\ No newline at end of file
......@@ -4,9 +4,11 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { StructureQuery, Queries, Structure, StructureElement, StructureSymmetry, StructureProperties as Props, QueryPredicate } from 'mol-model/structure';
import { Queries, Structure, StructureQuery, StructureSymmetry } from 'mol-model/structure';
import { getAtomsTests } from '../query/atoms';
export enum QueryParamType {
JSON,
String,
Integer,
Float
......@@ -18,7 +20,7 @@ export interface QueryParamInfo {
description?: string,
required?: boolean,
defaultValue?: any,
exampleValue?: string,
exampleValues?: string[],
validation?: (v: any) => void
}
......@@ -32,124 +34,87 @@ export interface QueryDefinition {
structureTransform?: (params: any, s: Structure) => Promise<Structure>
}
const AtomSiteParameters = {
entity_id: <QueryParamInfo>{ name: 'entity_id', type: QueryParamType.String, description: 'Corresponds to the \'_entity.id\' or \'*.label_entity_id\' field, depending on the context.' },
// const AtomSiteParams = {
// entity_id: <QueryParamInfo>{ name: 'entity_id', type: QueryParamType.String, description: 'Corresponds to the \'_entity.id\' or \'*.label_entity_id\' field, depending on the context.' },
label_asym_id: <QueryParamInfo>{ name: 'label_asym_id', type: QueryParamType.String, description: 'Corresponds to the \'_atom_site.label_asym_id\' field.' },
auth_asym_id: <QueryParamInfo>{ name: 'auth_asym_id', type: QueryParamType.String, exampleValue: 'A', description: 'Corresponds to the \'_atom_site.auth_asym_id\' field.' },
// label_asym_id: <QueryParamInfo>{ name: 'label_asym_id', type: QueryParamType.String, description: 'Corresponds to the \'_atom_site.label_asym_id\' field.' },
// auth_asym_id: <QueryParamInfo>{ name: 'auth_asym_id', type: QueryParamType.String, exampleValue: 'A', description: 'Corresponds to the \'_atom_site.auth_asym_id\' field.' },
label_seq_id: <QueryParamInfo>{ name: 'label_seq_id', type: QueryParamType.Integer, description: 'Residue seq. number. Corresponds to the \'_atom_site.label_seq_id\' field.' },
auth_seq_id: <QueryParamInfo>{ name: 'auth_seq_id', type: QueryParamType.Integer, exampleValue: '200', description: 'Author residue seq. number. Corresponds to the \'_atom_site.auth_seq_id\' field.' },
label_comp_id: <QueryParamInfo>{ name: 'label_comp_id', type: QueryParamType.String, description: 'Residue name. Corresponds to the \'_atom_site.label_comp_id\' field.' },
auth_comp_id: <QueryParamInfo>{ name: 'auth_comp_id', type: QueryParamType.String, exampleValue: 'REA', description: 'Author residue name. Corresponds to the \'_atom_site.auth_comp_id\' field.' },
pdbx_PDB_ins_code: <QueryParamInfo>{ name: 'pdbx_PDB_ins_code', type: QueryParamType.String, description: 'Corresponds to the \'_atom_site.pdbx_PDB_ins_code\' field.' },
};
// function entityTest(params: any): Element.Predicate | undefined {
// if (typeof params.entity_id === 'undefined') return void 0;
// const p = Props.entity.id, id = '' + params.entityId;
// return Element.property(l => p(l) === id);
// }
function entityTest1_555(params: any): QueryPredicate | undefined {
if (typeof params.entity_id === 'undefined') return ctx => ctx.element.unit.conformation.operator.isIdentity;
const p = Props.entity.id, id = '' + params.entityId;
return ctx => ctx.element.unit.conformation.operator.isIdentity && p(ctx.element) === id;
}
// label_seq_id: <QueryParamInfo>{ name: 'label_seq_id', type: QueryParamType.Integer, description: 'Residue seq. number. Corresponds to the \'_atom_site.label_seq_id\' field.' },
// auth_seq_id: <QueryParamInfo>{ name: 'auth_seq_id', type: QueryParamType.Integer, exampleValue: '200', description: 'Author residue seq. number. Corresponds to the \'_atom_site.auth_seq_id\' field.' },
// label_comp_id: <QueryParamInfo>{ name: 'label_comp_id', type: QueryParamType.String, description: 'Residue name. Corresponds to the \'_atom_site.label_comp_id\' field.' },
// auth_comp_id: <QueryParamInfo>{ name: 'auth_comp_id', type: QueryParamType.String, exampleValue: 'REA', description: 'Author residue name. Corresponds to the \'_atom_site.auth_comp_id\' field.' },
// pdbx_PDB_ins_code: <QueryParamInfo>{ name: 'pdbx_PDB_ins_code', type: QueryParamType.String, description: 'Corresponds to the \'_atom_site.pdbx_PDB_ins_code\' field.' },
// };
function chainTest(params: any): QueryPredicate | undefined {
if (typeof params.label_asym_id !== 'undefined') {
const p = Props.chain.label_asym_id, id = '' + params.label_asym_id;
return ctx => p(ctx.element) === id;
}
if (typeof params.auth_asym_id !== 'undefined') {
const p = Props.chain.auth_asym_id, id = '' + params.auth_asym_id;
return ctx => p(ctx.element) === id;
}
return void 0;
}
function residueTest(params: any): QueryPredicate | undefined {
const props: StructureElement.Property<any>[] = [], values: any[] = [];
if (typeof params.label_seq_id !== 'undefined') {
props.push(Props.residue.label_seq_id);
values.push(+params.label_seq_id);
}
if (typeof params.auth_seq_id !== 'undefined') {
props.push(Props.residue.auth_seq_id);
values.push(+params.auth_seq_id);
}
if (typeof params.label_comp_id !== 'undefined') {
props.push(Props.residue.label_comp_id);
values.push(params.label_comp_id);
}
const AtomSiteTestParams: QueryParamInfo = {
name: 'atom_site',
type: QueryParamType.JSON,
description: 'Object or array of objects describing atom properties. Name are same as in wwPDB mmCIF dictionary of the atom_site category.',
exampleValues: [`{ label_comp_id: 'ALA' }`, `{ label_seq_id: 123, label_asym_id: 'A' }`]
};
if (typeof params.auth_comp_id !== 'undefined') {
props.push(Props.residue.auth_comp_id);
values.push(params.auth_comp_id);
const RadiusParam: QueryParamInfo = {
name: 'radius',
type: QueryParamType.Float,
defaultValue: 5,
exampleValues: ['5'],
description: 'Value in Angstroms.',
validation(v: any) {
if (v < 1 || v > 10) {
throw `Invalid radius for residue interaction query (must be a value between 1 and 10).`;
}
if (typeof params.pdbx_PDB_ins_code !== 'undefined') {
props.push(Props.residue.pdbx_PDB_ins_code);
values.push(params.pdbx_PDB_ins_code);
}
switch (props.length) {
case 0: return void 0;
case 1: return ctx => props[0](ctx.element) === values[0];
case 2: return ctx => props[0](ctx.element) === values[0] && props[1](ctx.element) === values[1];
case 3: return ctx => props[0](ctx.element) === values[0] && props[1](ctx.element) === values[1] && props[2](ctx.element) === values[2];
default: {
const len = props.length;
return ctx => {
for (let i = 0; i < len; i++) if (!props[i](ctx.element) !== values[i]) return false;
return true;
};
}
}
}
// function buildResiduesQuery(params: any): Query.Provider {
// return Queries.generators.atoms({ entityTest: entityTest(params), chainTest: chainTest(params), residueTest: residueTest(params) });
// }
const QueryMap: { [id: string]: Partial<QueryDefinition> } = {
'full': { niceName: 'Full Structure', query: () => Queries.generators.all, description: 'The full structure.' },
'atoms': {
niceName: 'Atoms',
description: 'Atoms satisfying the given criteria.',
query: p => Queries.combinators.merge(getAtomsTests(p.atom_site).map(test => Queries.generators.atoms(test))),
params: [ AtomSiteTestParams ]
},
'symmetryMates': {
niceName: 'Symmetry Mates',
description: 'Computes crystal symmetry mates within the specified radius.',
query: () => Queries.generators.all,
structureTransform(p, s) {
return StructureSymmetry.builderSymmetryMates(s, p.radius).run();
},
},
'assembly': {
niceName: 'Assembly',
description: 'Computes structural assembly.',
query: () => Queries.generators.all,
structureTransform(p, s) {
return StructureSymmetry.buildAssembly(s, '' + p.name).run();
},
params: [{
name: 'name',
type: QueryParamType.String,
defaultValue: '1',
exampleValues: ['1'],
description: 'Assembly name.'
}]
},
'residueInteraction': {
niceName: 'Residues Inside a Sphere',
description: 'Identifies all residues within the given radius from the source residue.',
niceName: 'Residue Interaction',
description: 'Identifies all residues within the given radius from the source residue. Takes crystal symmetry into account.',
query(p) {
const center = Queries.generators.atoms({ entityTest: entityTest1_555(p), chainTest: chainTest(p), residueTest: residueTest(p) });
const tests = getAtomsTests(p.atom_site);
const center = Queries.combinators.merge(tests.map(test => Queries.generators.atoms({
...test,
entityTest: test.entityTest
? ctx => test.entityTest!(ctx) && ctx.element.unit.conformation.operator.isIdentity
: ctx => ctx.element.unit.conformation.operator.isIdentity
})));
return Queries.modifiers.includeSurroundings(center, { radius: p.radius, wholeResidues: true });
},
structureTransform(p, s) {
return StructureSymmetry.builderSymmetryMates(s, p.radius).run();
},
params: [
AtomSiteParameters.entity_id,
AtomSiteParameters.label_asym_id,
AtomSiteParameters.auth_asym_id,
AtomSiteParameters.label_comp_id,
AtomSiteParameters.auth_comp_id,
AtomSiteParameters.pdbx_PDB_ins_code,
AtomSiteParameters.label_seq_id,
AtomSiteParameters.auth_seq_id,
{
name: 'radius',
type: QueryParamType.Float,
defaultValue: 5,
exampleValue: '5',
description: 'Value in Angstroms.',
validation(v: any) {
if (v < 1 || v > 10) {
throw `Invalid radius for residue interaction query (must be a value between 1 and 10).`;
}
}
},
]
params: [ AtomSiteTestParams, RadiusParam ]
},
};
......@@ -173,30 +138,32 @@ export const QueryList = (function () {
}
})();
function _normalizeQueryParams(params: { [p: string]: string }, paramList: QueryParamInfo[]): { [p: string]: string | number | boolean } {
const ret: any = {};
for (const p of paramList) {
const key = p.name;
const value = params[key];
if (typeof value === 'undefined' || (typeof value !== 'undefined' && value !== null && value['length'] === 0)) {
if (p.required) {
throw `The parameter '${key}' is required.`;
}
if (typeof p.defaultValue !== 'undefined') ret[key] = p.defaultValue;
} else {
switch (p.type) {
case QueryParamType.String: ret[key] = value; break;
case QueryParamType.Integer: ret[key] = parseInt(value); break;
case QueryParamType.Float: ret[key] = parseFloat(value); break;
}
// function _normalizeQueryParams(params: { [p: string]: string }, paramList: QueryParamInfo[]): { [p: string]: string | number | boolean } {
// const ret: any = {};
// for (const p of paramList) {
// const key = p.name;
// const value = params[key];
// if (typeof value === 'undefined' || (typeof value !== 'undefined' && value !== null && value['length'] === 0)) {
// if (p.required) {
// throw `The parameter '${key}' is required.`;
// }
// if (typeof p.defaultValue !== 'undefined') ret[key] = p.defaultValue;
// } else {
// switch (p.type) {
// case QueryParamType.JSON: ret[key] = JSON.parse(value); break;
// case QueryParamType.String: ret[key] = value; break;
// case QueryParamType.Integer: ret[key] = parseInt(value); break;
// case QueryParamType.Float: ret[key] = parseFloat(value); break;
// }
if (p.validation) p.validation(ret[key]);
}
}
// if (p.validation) p.validation(ret[key]);
// }
// }
return ret;
}
// return ret;
// }
export function normalizeQueryParams(query: QueryDefinition, params: any) {
return _normalizeQueryParams(params, query.params);
return params;
//return _normalizeQueryParams(params, query.params);
}
\ No newline at end of file
......@@ -137,7 +137,7 @@ const _model_server_params: CifWriter.Category<Job> = {
instance(job) {
const params: string[][] = [];
for (const k of Object.keys(job.normalizedParams)) {
params.push([k, '' + job.normalizedParams[k]]);
params.push([k, JSON.stringify(job.normalizedParams[k])]);
}
return {
data: params,
......
......@@ -107,7 +107,10 @@ async function readStructure(key: string, sourceId: string, entryId: string) {
perf.end('createModel');
perf.start('attachProps');
await attachModelProperties(models[0]);
const modelProps = attachModelProperties(models[0]);
for (const p of modelProps) {
await tryAttach(key, p);
}
perf.end('attachProps');
const structure = Structure.ofModel(models[0]);
......@@ -129,3 +132,11 @@ async function readStructure(key: string, sourceId: string, entryId: string) {
return ret;
}
async function tryAttach(key: string, promise: Promise<any>) {
try {
await promise;
} catch (e) {
ConsoleLogger.errorId(key, 'Custom prop:' + e);
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment