diff --git a/src/mol-io/reader/3dg/parser.ts b/src/mol-io/reader/3dg/parser.ts new file mode 100644 index 0000000000000000000000000000000000000000..bc632a7144b305c656153c6c3202a731e0e46eea --- /dev/null +++ b/src/mol-io/reader/3dg/parser.ts @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ReaderResult as Result } from '../result' +import { Task } from '../../../mol-task' +import { parseCsv } from '../csv/parser'; +import { Column, Table } from '../../../mol-data/db'; +import { toTable } from '../cif/schema'; + +import Schema = Column.Schema +import { CsvTable } from '../csv/data-model'; + + +export const Schema3DG = { + /** Chromosome name */ + chromosome: Schema.str, + /** Base position */ + position: Schema.int, + /** X coordinate */ + x: Schema.float, + /** Y coordinate */ + y: Schema.float, + /** Z coordinate */ + z: Schema.float, +} +export type Schema3DG = typeof Schema3DG + +export interface File3DG { + table: Table<Schema3DG> +} + +const FieldNames = [ 'chromosome', 'position', 'x', 'y', 'z' ] + +function categoryFromTable(name: string, table: CsvTable) { + return { + name, + rowCount: table.rowCount, + fieldNames: FieldNames, + getField: (name: string) => { + return table.getColumn(FieldNames.indexOf(name).toString()) + } + } +} + +export function parse3DG(data: string) { + return Task.create<Result<File3DG>>('Parse 3DG', async ctx => { + const opts = { quote: '', comment: '#', delimiter: '\t', noColumnNames: true } + const csvFile = await parseCsv(data, opts).runInContext(ctx) + if (csvFile.isError) return Result.error(csvFile.message, csvFile.line) + const category = categoryFromTable('3dg', csvFile.result.table) + const table = toTable(Schema3DG, category) + return Result.success({ table }) + }); +} \ No newline at end of file diff --git a/src/mol-io/reader/_spec/3dg.spec.ts b/src/mol-io/reader/_spec/3dg.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..2a44987e991f93f462358c4d924d9d14b2307cad --- /dev/null +++ b/src/mol-io/reader/_spec/3dg.spec.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { parse3DG } from '../3dg/parser'; + +const basic3dgString = `1(mat) 1420000 0.791377837067 10.9947291355 -13.1882897693 +1(mat) 1440000 -0.268241283699 10.5200875887 -13.0896257278 +1(mat) 1460000 -1.3853075236 10.5513787498 -13.1440142173 +1(mat) 1480000 -1.55984101733 11.4340829129 -13.6026301209 +1(mat) 1500000 -0.770991778399 11.4758488546 -14.5881137222 +1(mat) 1520000 -0.0848245107875 12.2624690808 -14.354289628 +1(mat) 1540000 -0.458643807046 12.5985791771 -13.4701149287 +1(mat) 1560000 -0.810322906201 12.2461643989 -12.3172933413 +1(mat) 1580000 -2.08211172035 12.8886838656 -12.8742007778 +1(mat) 1600000 -3.52093948201 13.1850935438 -12.4118684428` + +describe('3dg reader', () => { + it('basic', async () => { + const parsed = await parse3DG(basic3dgString).run(); + expect(parsed.isError).toBe(false) + + if (parsed.isError) return; + const { chromosome, position, x, y, z } = parsed.result.table; + expect(chromosome.value(0)).toBe('1(mat)') + expect(position.value(1)).toBe(1440000) + expect(x.value(5)).toBe(-0.0848245107875) + expect(y.value(5)).toBe(12.2624690808) + expect(z.value(5)).toBe(-14.354289628) + }); +}); \ No newline at end of file diff --git a/src/mol-model-formats/structure/3dg.ts b/src/mol-model-formats/structure/3dg.ts new file mode 100644 index 0000000000000000000000000000000000000000..f72aff712cfc7d96f4bd3d7a33bd2d30d7942f17 --- /dev/null +++ b/src/mol-model-formats/structure/3dg.ts @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Model } from '../../mol-model/structure/model'; +import { Task } from '../../mol-task'; +import { ModelFormat } from './format'; +import { _parse_mmCif } from './mmcif/parser'; +import { CifCategory, CifField } from '../../mol-io/reader/cif'; +import { Column } from '../../mol-data/db'; +import { mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif'; +import { EntityBuilder } from './common/entity'; +import { File3DG } from '../../mol-io/reader/3dg/parser'; +import { fillSerial } from '../../mol-util/array'; +import { MoleculeType } from '../../mol-model/structure/model/types'; + +function getCategories(table: File3DG['table']) { + const entityIds = new Array<string>(table._rowCount) + const entityBuilder = new EntityBuilder() + + const seqIdStarts = table.position.toArray({ array: Uint32Array }) + const seqIdEnds = new Uint32Array(table._rowCount) + const stride = seqIdStarts[1] - seqIdStarts[0] + + const objectRadius = stride / 3500 + + for (let i = 0, il = table._rowCount; i < il; ++i) { + const chr = table.chromosome.value(i) + const entityId = entityBuilder.getEntityId(chr, MoleculeType.DNA, chr) + entityIds[i] = entityId + seqIdEnds[i] = seqIdStarts[i] + stride - 1 + } + + const ihm_sphere_obj_site: CifCategory.SomeFields<mmCIF_Schema['ihm_sphere_obj_site']> = { + id: CifField.ofNumbers(fillSerial(new Uint32Array(table._rowCount))), + entity_id: CifField.ofStrings(entityIds), + seq_id_begin: CifField.ofNumbers(seqIdStarts), + seq_id_end: CifField.ofNumbers(seqIdEnds), + asym_id: CifField.ofColumn(table.chromosome), + + Cartn_x: CifField.ofNumbers(Column.mapToArray(table.x, x => x * 10, Float32Array)), + Cartn_y: CifField.ofNumbers(Column.mapToArray(table.y, y => y * 10, Float32Array)), + Cartn_z: CifField.ofNumbers(Column.mapToArray(table.z, z => z * 10, Float32Array)), + + object_radius: CifField.ofColumn(Column.ofConst(objectRadius, table._rowCount, Column.Schema.float)), + rmsf: CifField.ofColumn(Column.ofConst(0, table._rowCount, Column.Schema.float)), + model_id: CifField.ofColumn(Column.ofConst(1, table._rowCount, Column.Schema.int)), + } + + return { + entity: entityBuilder.getEntityCategory(), + ihm_model_list: CifCategory.ofFields('ihm_model_list', { + model_id: CifField.ofNumbers([1]), + model_name: CifField.ofStrings(['3DG Model']), + }), + ihm_sphere_obj_site: CifCategory.ofFields('ihm_sphere_obj_site', ihm_sphere_obj_site) + } +} + +async function mmCifFrom3dg(file3dg: File3DG) { + const categories = getCategories(file3dg.table) + + return { + header: '3DG', + categoryNames: Object.keys(categories), + categories + }; +} + +export function trajectoryFrom3DG(file3dg: File3DG): Task<Model.Trajectory> { + return Task.create('Parse 3DG', async ctx => { + await ctx.update('Converting to mmCIF'); + const cif = await mmCifFrom3dg(file3dg); + const format = ModelFormat.mmCIF(cif); + return _parse_mmCif(format, ctx); + }) +} diff --git a/src/mol-plugin/state/actions/data-format.ts b/src/mol-plugin/state/actions/data-format.ts index dacd03fd7dff0b1da9669eaca97f201ca8e76c6a..f2d99a56c1b1e0c40880c5c6fe8989fe59794bba 100644 --- a/src/mol-plugin/state/actions/data-format.ts +++ b/src/mol-plugin/state/actions/data-format.ts @@ -12,7 +12,7 @@ import { PluginStateObject } from '../objects'; import { ParamDefinition as PD } from '../../../mol-util/param-definition'; import { Ccp4Provider, Dsn6Provider, DscifProvider } from './volume'; import { StateTransforms } from '../transforms'; -import { MmcifProvider, PdbProvider, GroProvider } from './structure'; +import { MmcifProvider, PdbProvider, GroProvider, Provider3dg } from './structure'; import msgpackDecode from '../../../mol-io/common/msgpack/decode' import { PlyProvider } from './shape'; @@ -55,6 +55,7 @@ export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | Plugin } constructor() { + this.add('3dg', Provider3dg) this.add('ccp4', Ccp4Provider) this.add('dscif', DscifProvider) this.add('dsn6', Dsn6Provider) diff --git a/src/mol-plugin/state/actions/structure.ts b/src/mol-plugin/state/actions/structure.ts index c0f9c5a55a369cbc6e3d45391fdb2b6a8ef746e7..7cfa14dd6e73f839576efd7e4165edb99dfbd9ff 100644 --- a/src/mol-plugin/state/actions/structure.ts +++ b/src/mol-plugin/state/actions/structure.ts @@ -69,7 +69,23 @@ export const GroProvider: DataFormatProvider<any> = { } } -type StructureFormat = 'pdb' | 'cif' | 'gro' +export const Provider3dg: DataFormatProvider<any> = { + label: '3DG', + description: '3DG', + stringExtensions: ['3dg'], + binaryExtensions: [], + isApplicable: (info: FileInfo, data: string) => { + return info.ext === '3dg' + }, + getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.String>, options: DataFormatBuilderOptions, state: State) => { + return Task.create('3DG default builder', async taskCtx => { + const traj = createModelTree(data, '3dg'); + await state.updateTree(options.visuals ? createStructureTree(ctx, traj, false) : traj).runInContext(taskCtx) + }) + } +} + +type StructureFormat = 'pdb' | 'cif' | 'gro' | '3dg' // @@ -219,6 +235,9 @@ export function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary case 'gro': parsed = b.apply(StateTransforms.Model.TrajectoryFromGRO); break + case '3dg': + parsed = b.apply(StateTransforms.Model.TrajectoryFrom3DG); + break default: throw new Error('unsupported format') } diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts index 2b41362937df3b6f08a5f29a2fb9ac4ace2542a7..5bc63f833d33c86bf468ac312c43e1c4e72d8275 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/src/mol-plugin/state/transforms/model.ts @@ -26,11 +26,14 @@ import { shapeFromPly } from '../../../mol-model-formats/shape/ply'; import { SymmetryOperator } from '../../../mol-math/geometry'; import { ensureSecondaryStructure } from './helpers'; import { Script } from '../../../mol-script/script'; +import { parse3DG } from '../../../mol-io/reader/3dg/parser'; +import { trajectoryFrom3DG } from '../../../mol-model-formats/structure/3dg'; export { TrajectoryFromBlob }; export { TrajectoryFromMmCif }; export { TrajectoryFromPDB }; export { TrajectoryFromGRO }; +export { TrajectoryFrom3DG }; export { ModelFromTrajectory }; export { StructureFromTrajectory }; export { StructureFromModel }; @@ -137,6 +140,24 @@ const TrajectoryFromGRO = PluginStateTransform.BuiltIn({ } }); +type TrajectoryFrom3DG = typeof TrajectoryFrom3DG +const TrajectoryFrom3DG = PluginStateTransform.BuiltIn({ + name: 'trajectory-from-3dg', + display: { name: 'Parse 3DG', description: 'Parse 3DG string and create trajectory.' }, + from: [SO.Data.String], + to: SO.Molecule.Trajectory +})({ + apply({ a }) { + return Task.create('Parse 3DG', async ctx => { + const parsed = await parse3DG(a.data).runInContext(ctx); + if (parsed.isError) throw new Error(parsed.message); + const models = await trajectoryFrom3DG(parsed.result).runInContext(ctx); + const props = { label: `${models[0].entry}`, description: `${models.length} model${models.length === 1 ? '' : 's'}` }; + return new SO.Molecule.Trajectory(models, props); + }); + } +}); + const plus1 = (v: number) => v + 1, minus1 = (v: number) => v - 1; type ModelFromTrajectory = typeof ModelFromTrajectory const ModelFromTrajectory = PluginStateTransform.BuiltIn({