diff --git a/package-lock.json b/package-lock.json index 6c89b0f359c83badbc08f0ea4223fd151c4475cc..e79e6e264ae78f3a1bc547133c07fb54afdc54c2 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/src/mol-io/writer/cif.ts b/src/mol-io/writer/cif.ts index c191d8a4bdeca25d9c377abe354980931bcad2f3..762f1988f1868b0c4d1e58f48b86ad06c6f5fd47 100644 --- a/src/mol-io/writer/cif.ts +++ b/src/mol-io/writer/cif.ts @@ -53,5 +53,58 @@ export namespace CifWriter { return ff && ff.binaryEncoding ? ArrayEncoder.fromEncoding(ff.binaryEncoding) : void 0; } } + }; + + export function createEncodingProviderFromJsonConfig(hints: EncodingStrategyHint[]): EncodingProvider { + return { + get(c, f) { + for (let i = 0; i < hints.length; i++) { + const hint = hints[i]; + if (hint.categoryName === c && hint.columnName === f) { + return resolveEncoding(hint); + } + } + } + } } + + function resolveEncoding(hint: EncodingStrategyHint): ArrayEncoder | undefined { + const precision: number | undefined = hint.precision; + if (precision !== void 0) { + const multiplier = Math.pow(10, precision); + const fixedPoint = E.by(E.fixedPoint(multiplier)); + switch (hint.encoding) { + case 'pack': + return fixedPoint.and(E.integerPacking); + case 'rle': + return fixedPoint.and(E.runLength).and(E.integerPacking); + case 'delta': + return fixedPoint.and(E.delta).and(E.integerPacking); + case 'delta-rle': + return fixedPoint.and(E.delta).and(E.runLength).and(E.integerPacking); + }; + } else { + switch (hint.encoding) { + case 'pack': + return E.by(E.integerPacking); + case 'rle': + return E.by(E.runLength).and(E.integerPacking); + case 'delta': + return E.by(E.delta).and(E.integerPacking); + case 'delta-rle': + return E.by(E.delta).and(E.runLength).and(E.integerPacking); + } + } + } +} + +// defines the information needed to encode certain fields: category and column name as well as encoding tag, precision is optional and identifies float columns +// TODO would be nice to infer strategy and precision if needed +export interface EncodingStrategyHint { + categoryName: string, + columnName: string, + // 'pack', 'rle', 'delta', or 'delta-rle' + encoding: string, + // number of decimal places to keep - must be specified to float columns + precision?: number } \ No newline at end of file diff --git a/src/mol-io/writer/cif/encoder/binary.ts b/src/mol-io/writer/cif/encoder/binary.ts index 9212bcea20d8862792026f96bee41745517b59b4..99ee5005375852ddd38feb594f18d2a8071a9358 100644 --- a/src/mol-io/writer/cif/encoder/binary.ts +++ b/src/mol-io/writer/cif/encoder/binary.ts @@ -111,12 +111,13 @@ function getDefaultEncoder(type: Field.Type): ArrayEncoder { } function tryGetEncoder(categoryName: string, field: Field, format: Field.Format | undefined, provider: EncodingProvider | undefined) { - if (format && format.encoder) { + // TODO made provider the first check - might break servers/model/query + if (provider && provider.get(categoryName, field.name)) { + return provider.get(categoryName, field.name); + } else if (format && format.encoder) { return format.encoder; } else if (field.defaultFormat && field.defaultFormat.encoder) { return field.defaultFormat.encoder; - } else if (provider) { - return provider.get(categoryName, field.name); } else { return void 0; } diff --git a/src/tests/browser/encoding-config.ts b/src/tests/browser/encoding-config.ts new file mode 100644 index 0000000000000000000000000000000000000000..b02ed4ea8481a41b2e66095b427d994eb9c7ecaf --- /dev/null +++ b/src/tests/browser/encoding-config.ts @@ -0,0 +1,91 @@ +import './index.html' +import { CIF, CifCategory, CifField, getCifFieldType } from '../../mol-io/reader/cif'; +import { CifWriter } from '../../mol-io/writer/cif'; +import { classifyFloatArray, classifyIntArray } from '../../mol-io/common/binary-cif'; + +async function parseCif(data: string|Uint8Array) { + const comp = CIF.parse(data); + const parsed = await comp.run(); + if (parsed.isError) throw parsed; + return parsed.result; +} + +async function downloadCif(url: string, isBinary: boolean) { + const data = await fetch(url); + return parseCif(isBinary ? new Uint8Array(await data.arrayBuffer()) : await data.text()); +} + +async function downloadFromPdb(pdb: string) { + const parsed = await downloadCif(`https://webchem.ncbr.muni.cz/ModelServer/static/bcif/${pdb}`, true); + return parsed.blocks[0]; +} + +async function init(props = {}) { + const cif = await downloadFromPdb('1brr') + const encoder = CifWriter.createEncoder({ + binary: true, + encoderName: 'mol*', + binaryEncodingPovider: CifWriter.createEncodingProviderFromJsonConfig([ + { + 'categoryName': 'atom_site', + 'columnName': 'Cartn_y', + 'encoding': 'rle', + 'precision': 0 + }, + { + 'categoryName': 'atom_site', + 'columnName': 'Cartn_z', + 'encoding': 'delta', + 'precision': 1 + }, + { + 'categoryName': 'atom_site', + 'columnName': 'label_seq_id', + 'encoding': 'delta-rle' + } + ]) + }); + + encoder.startDataBlock(cif.header); + for (const c of cif.categoryNames) { + const cat = cif.categories[c]; + const fields: CifWriter.Field[] = []; + for (const f of cat.fieldNames) { + fields.push(classify(f, cat.getField(f)!)) + } + + encoder.writeCategory(getCategoryInstanceProvider(cif.categories[c], fields)); + } + const ret = encoder.getData() as Uint8Array; + + const cif2 = (await parseCif(ret)).blocks[0]; + // should be untouched + console.log(cif2.categories['atom_site'].getField('Cartn_x')); + // should have integer precision + console.log(cif2.categories['atom_site'].getField('Cartn_y')); + // should have 1 decimal place + console.log(cif2.categories['atom_site'].getField('Cartn_z')); + console.log(cif2.categories['atom_site'].getField('label_seq_id')); +} + +init() + +function getCategoryInstanceProvider(cat: CifCategory, fields: CifWriter.Field[]): CifWriter.Category { + return { + name: cat.name, + instance: () => CifWriter.categoryInstance(fields, { data: cat, rowCount: cat.rowCount }) + }; +} + +function classify(name: string, field: CifField): CifWriter.Field { + const type = getCifFieldType(field); + if (type['@type'] === 'str') { + return { name, type: CifWriter.Field.Type.Str, value: field.str, valueKind: field.valueKind }; + } else if (type['@type'] === 'float') { + const encoder = classifyFloatArray(field.toFloatArray({ array: Float64Array })); + return CifWriter.Field.float(name, field.float, { valueKind: field.valueKind, encoder, typedArray: Float64Array }); + } else { + const encoder = classifyIntArray(field.toIntArray({ array: Int32Array })); + return CifWriter.Field.int(name, field.int, { valueKind: field.valueKind, encoder, typedArray: Int32Array }); + } +} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 3de9e1565111c706a5f326ff84f7f9f143cc4e28..c171386df79f848f39afbbc8d22abdbcc0666a77 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -98,4 +98,5 @@ module.exports = [ createBrowserTest('render-spheres'), createBrowserTest('render-structure'), createBrowserTest('render-text'), + createBrowserTest('encoding-config') ] \ No newline at end of file