Skip to content
Snippets Groups Projects
Commit 64276543 authored by David Sehnal's avatar David Sehnal
Browse files

mol-plugin: initial custom property support

parent d9dced28
No related branches found
No related tags found
No related merge requests found
......@@ -15,6 +15,7 @@ import { CustomPropSymbol } from 'mol-script/language/symbol';
import Type from 'mol-script/language/type';
import { QuerySymbolRuntime } from 'mol-script/runtime/query/compiler';
import { PropertyWrapper } from '../common/wrapper';
import { Task } from 'mol-task';
export namespace StructureQualityReport {
export type IssueMap = IndexedCustomProperty.Residue<string[]>
......@@ -82,6 +83,33 @@ export namespace StructureQualityReport {
}
}
export function createAttachTask(mapUrl: (model: Model) => string, fetch: (url: string, type: 'string' | 'binary') => Task<string | Uint8Array>) {
return (model: Model) => Task.create('PDBe Structure Quality Report', async ctx => {
if (get(model)) return true;
let issueMap: IssueMap | undefined;
let info;
// TODO: return from CIF support once the data is recomputed
// = PropertyWrapper.tryGetInfoFromCif('pdbe_structure_quality_report', model);
// if (info) {
// const data = getCifData(model);
// issueMap = createIssueMapFromCif(model, data.residues, data.groups);
// } else
{
const url = mapUrl(model);
const dataStr = await fetch(url, 'string').runInContext(ctx) as string;
const data = JSON.parse(dataStr)[model.label.toLowerCase()];
if (!data) return false;
info = PropertyWrapper.createInfo();
issueMap = createIssueMapFromJson(model, data);
}
model.customProperties.add(Descriptor);
set(model, { info, data: issueMap });
return false;
});
}
export async function attachFromCifOrApi(model: Model, params: {
// optional JSON source
PDBe_apiSourceJson?: (model: Model) => Promise<any>
......
......@@ -13,6 +13,7 @@ import * as StaticMisc from './behavior/static/misc'
import * as DynamicRepresentation from './behavior/dynamic/representation'
import * as DynamicCamera from './behavior/dynamic/camera'
import * as DynamicCustomProps from './behavior/dynamic/custom-props'
export const BuiltInPluginBehaviors = {
State: StaticState,
......@@ -24,4 +25,5 @@ export const BuiltInPluginBehaviors = {
export const PluginBehaviors = {
Representation: DynamicRepresentation,
Camera: DynamicCamera,
CustomProps: DynamicCustomProps
}
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { ParamDefinition } from 'mol-util/param-definition';
import { PluginBehavior } from '../behavior';
import { StructureQualityReport } from 'mol-model-props/pdbe/structure-quality-report';
import { CustomPropertyRegistry } from 'mol-plugin/util/custom-prop-registry';
import { Loci } from 'mol-model/loci';
import { StructureElement } from 'mol-model/structure';
import { OrderedSet } from 'mol-data/int';
// TODO: make auto attach working better for "initial state" by supporting default props in state updates
export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: boolean }>({
name: 'pdbe-structure-quality-report-prop',
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean }> {
private attach = StructureQualityReport.createAttachTask(
m => `https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/${m.label.toLowerCase()}`,
this.ctx.fetch
);
private provider: CustomPropertyRegistry.Provider = {
option: [StructureQualityReport.Descriptor.name, 'PDBe Structure Quality Report'],
descriptor: StructureQualityReport.Descriptor,
defaultSelected: false,
attachableTo: () => true,
attach: this.attach
}
register(): void {
this.ctx.customModelProperties.register(this.provider);
this.ctx.lociLabels.addProvider(labelPDBeValidation);
}
update(p: { autoAttach: boolean }) {
let updated = this.params.autoAttach !== p.autoAttach
this.params.autoAttach = p.autoAttach;
this.provider.defaultSelected = p.autoAttach;
return updated;
}
unregister() {
this.ctx.customModelProperties.unregister(StructureQualityReport.Descriptor.name);
this.ctx.lociLabels.removeProvider(labelPDBeValidation);
}
},
params: () => ({
autoAttach: ParamDefinition.Boolean(false)
}),
display: { name: 'Focus Loci on Select', group: 'Camera' }
});
function labelPDBeValidation(loci: Loci): string | undefined {
switch (loci.kind) {
case 'element-loci':
const e = loci.elements[0];
const u = e.unit;
if (!u.model.customProperties.has(StructureQualityReport.Descriptor)) return void 0;
const se = StructureElement.create(u, u.elements[OrderedSet.getAt(e.indices, 0)]);
const issues = StructureQualityReport.getIssues(se);
if (issues.length === 0) return 'PDBe Validation: No Issues';
return `PDBe Validation: ${issues.join(', ')}`;
default: return void 0;
}
}
\ No newline at end of file
......@@ -24,6 +24,7 @@ import { TaskManager } from './util/task-manager';
import { Color } from 'mol-util/color';
import { LociLabelEntry, LociLabelManager } from './util/loci-label-manager';
import { ajaxGet } from 'mol-util/data-source';
import { CustomPropertyRegistry } from './util/custom-prop-registry';
export class PluginContext {
private disposed = false;
......@@ -58,13 +59,6 @@ export class PluginContext {
}
};
readonly lociLabels: LociLabelManager;
readonly structureRepresentation = {
registry: new StructureRepresentationRegistry(),
themeCtx: { colorThemeRegistry: new ColorTheme.Registry(), sizeThemeRegistry: new SizeTheme.Registry() } as ThemeRegistryContext
}
readonly behaviors = {
canvas: {
highlightLoci: this.ev.behavior<{ loci: Loci, repr?: Representation.Any }>({ loci: EmptyLoci }),
......@@ -75,6 +69,14 @@ export class PluginContext {
readonly canvas3d: Canvas3D;
readonly lociLabels: LociLabelManager;
readonly structureRepresentation = {
registry: new StructureRepresentationRegistry(),
themeCtx: { colorThemeRegistry: new ColorTheme.Registry(), sizeThemeRegistry: new SizeTheme.Registry() } as ThemeRegistryContext
}
readonly customModelProperties = new CustomPropertyRegistry();
initViewer(canvas: HTMLCanvasElement, container: HTMLDivElement) {
try {
......
......@@ -35,7 +35,8 @@ const DefaultSpec: PluginSpec = {
PluginSpec.Behavior(PluginBehaviors.Representation.HighlightLoci),
PluginSpec.Behavior(PluginBehaviors.Representation.SelectLoci),
PluginSpec.Behavior(PluginBehaviors.Representation.DefaultLociLabelProvider),
PluginSpec.Behavior(PluginBehaviors.Camera.FocusLociOnSelect, { minRadius: 20, extraRadius: 4 })
PluginSpec.Behavior(PluginBehaviors.Camera.FocusLociOnSelect, { minRadius: 20, extraRadius: 4 }),
PluginSpec.Behavior(PluginBehaviors.CustomProps.PDBeStructureQualityReport, { autoAttach: false })
]
}
......
......@@ -80,6 +80,7 @@ function createStructureTree(b: StateTreeBuilder.To<PluginStateObject.Data.Binar
.apply(StateTransforms.Data.ParseCif)
.apply(StateTransforms.Model.TrajectoryFromMmCif, {})
.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 })
.apply(StateTransforms.Model.CustomModelProperties, { properties: [] })
.apply(StateTransforms.Model.StructureAssemblyFromModel);
complexRepresentation(root);
......
......@@ -6,7 +6,7 @@
import { PluginStateTransform } from '../objects';
import { PluginStateObject as SO } from '../objects';
import { Task } from 'mol-task';
import { Task, RuntimeContext } from 'mol-task';
import { Model, Format, Structure, ModelSymmetry, StructureSymmetry, QueryContext, StructureSelection as Sel, StructureQuery, Queries } from 'mol-model/structure';
import { ParamDefinition as PD } from 'mol-util/param-definition';
import Expression from 'mol-script/language/expression';
......@@ -168,3 +168,25 @@ const StructureComplexElement = PluginStateTransform.BuiltIn({
}
});
export { CustomModelProperties }
type CustomModelProperties = typeof CustomModelProperties
const CustomModelProperties = PluginStateTransform.BuiltIn({
name: 'custom-model-properties',
display: { name: 'Custom Model Properties' },
from: SO.Molecule.Model,
to: SO.Molecule.Model,
params: (a, ctx: PluginContext) => ({ properties: ctx.customModelProperties.getSelect(a.data) })
})({
apply({ a, params }, ctx: PluginContext) {
return Task.create('Custom Props', async taskCtx => {
await attachProps(a.data, ctx, taskCtx, params.properties);
return new SO.Molecule.Model(a.data, { label: a.label, description: 'Custom Props' });
});
}
});
async function attachProps(model: Model, ctx: PluginContext, taskCtx: RuntimeContext, names: string[]) {
for (const name of names) {
const p = ctx.customModelProperties.get(name);
await p.attach(model).runInContext(taskCtx);
}
}
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { ModelPropertyDescriptor, Model } from 'mol-model/structure';
import { OrderedMap } from 'immutable';
import { ParamDefinition } from 'mol-util/param-definition';
import { Task } from 'mol-task';
export { CustomPropertyRegistry }
class CustomPropertyRegistry {
private providers = OrderedMap<string, CustomPropertyRegistry.Provider>().asMutable();
getSelect(model: Model) {
const values = this.providers.values();
const options: [string, string][] = [], selected: string[] = [];
while (true) {
const v = values.next();
if (v.done) break;
if (!v.value.attachableTo(model)) continue;
options.push(v.value.option);
if (v.value.defaultSelected) selected.push(v.value.option[0]);
}
return ParamDefinition.MultiSelect(selected, options);
}
getDefault(model: Model) {
const values = this.providers.values();
const selected: string[] = [];
while (true) {
const v = values.next();
if (v.done) break;
if (!v.value.attachableTo(model)) continue;
if (v.value.defaultSelected) selected.push(v.value.option[0]);
}
return selected;
}
get(name: string) {
const prop = this.providers.get(name);
if (!prop) throw new Error(`Custom prop '${name}' is not registered.`);
return this.providers.get(name);
}
register(provider: CustomPropertyRegistry.Provider) {
this.providers.set(provider.descriptor.name, provider);
}
unregister(name: string) {
this.providers.delete(name);
}
}
namespace CustomPropertyRegistry {
export interface Provider {
option: [string, string],
defaultSelected: boolean,
descriptor: ModelPropertyDescriptor<any, any>,
attachableTo: (model: Model) => boolean,
attach: (model: Model) => Task<boolean>
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment