structure-wrapper.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { Structure, Model } from 'mol-model/structure';
  7. import { PerformanceMonitor } from 'mol-util/performance-monitor';
  8. import { Cache } from './cache';
  9. import Config from '../config';
  10. import CIF, { CifFrame } from 'mol-io/reader/cif'
  11. import * as util from 'util'
  12. import * as fs from 'fs'
  13. import * as zlib from 'zlib'
  14. import { Job } from './jobs';
  15. import { ConsoleLogger } from 'mol-util/console-logger';
  16. import { ModelPropertiesProvider } from '../property-provider';
  17. import { trajectoryFromMmCIF } from 'mol-model-formats/structure/mmcif';
  18. require('util.promisify').shim();
  19. export enum StructureSourceType {
  20. File,
  21. Cache
  22. }
  23. export interface StructureInfo {
  24. sourceType: StructureSourceType;
  25. readTime: number;
  26. parseTime: number;
  27. createModelTime: number;
  28. attachPropsTime: number;
  29. sourceId: string,
  30. entryId: string
  31. }
  32. export interface StructureWrapper {
  33. info: StructureInfo,
  34. isBinary: boolean,
  35. key: string,
  36. approximateSize: number,
  37. models: ArrayLike<Model>,
  38. modelMap: Map<number, Model>,
  39. structureModelMap: Map<number, Structure>,
  40. propertyProvider: ModelPropertiesProvider | undefined,
  41. cifFrame: CifFrame,
  42. cache: object
  43. }
  44. export async function createStructureWrapperFromJob(job: Job, propertyProvider: ModelPropertiesProvider | undefined, allowCache = true): Promise<StructureWrapper> {
  45. if (allowCache && Config.cacheParams.useCache) {
  46. const ret = StructureCache.get(job.key);
  47. if (ret) return ret;
  48. }
  49. const ret = await readStructureWrapper(job.key, job.sourceId, job.entryId, propertyProvider);
  50. if (allowCache && Config.cacheParams.useCache) {
  51. StructureCache.add(ret);
  52. }
  53. return ret;
  54. }
  55. export const StructureCache = new Cache<StructureWrapper>(s => s.key, s => s.approximateSize);
  56. const perf = new PerformanceMonitor();
  57. const readFileAsync = util.promisify(fs.readFile);
  58. const unzipAsync = util.promisify<zlib.InputType, Buffer>(zlib.unzip);
  59. async function readFile(filename: string) {
  60. const isGz = /\.gz$/i.test(filename);
  61. if (filename.match(/\.bcif/)) {
  62. let input = await readFileAsync(filename)
  63. if (isGz) input = await unzipAsync(input);
  64. const data = new Uint8Array(input.byteLength);
  65. for (let i = 0; i < input.byteLength; i++) data[i] = input[i];
  66. return data;
  67. } else {
  68. if (isGz) {
  69. const data = await unzipAsync(await readFileAsync(filename));
  70. return data.toString('utf8');
  71. }
  72. return readFileAsync(filename, 'utf8');
  73. }
  74. }
  75. async function parseCif(data: string|Uint8Array) {
  76. const comp = CIF.parse(data);
  77. const parsed = await comp.run();
  78. if (parsed.isError) throw parsed;
  79. return parsed.result;
  80. }
  81. export async function readStructureWrapper(key: string, sourceId: string | '_local_', entryId: string, propertyProvider: ModelPropertiesProvider | undefined) {
  82. const filename = sourceId === '_local_' ? entryId : Config.mapFile(sourceId, entryId);
  83. if (!filename) throw new Error(`Cound not map '${key}' to a valid filename.`);
  84. if (!fs.existsSync(filename)) throw new Error(`Could not find source file for '${key}'.`);
  85. perf.start('read');
  86. let data;
  87. try {
  88. data = await readFile(filename);
  89. } catch (e) {
  90. ConsoleLogger.error(key, '' + e);
  91. throw new Error(`Could not read the file for '${key}' from disk.`);
  92. }
  93. perf.end('read');
  94. perf.start('parse');
  95. const frame = (await parseCif(data)).blocks[0];
  96. perf.end('parse');
  97. perf.start('createModel');
  98. const models = await trajectoryFromMmCIF(frame).run();
  99. perf.end('createModel');
  100. const modelMap = new Map<number, Model>();
  101. for (const m of models) {
  102. modelMap.set(m.modelNum, m);
  103. }
  104. const ret: StructureWrapper = {
  105. info: {
  106. sourceType: StructureSourceType.File,
  107. readTime: perf.time('read'),
  108. parseTime: perf.time('parse'),
  109. createModelTime: perf.time('createModel'),
  110. attachPropsTime: 0, // perf.time('attachProps'),
  111. sourceId,
  112. entryId
  113. },
  114. isBinary: /\.bcif/.test(filename),
  115. key,
  116. approximateSize: typeof data === 'string' ? 2 * data.length : data.length,
  117. models,
  118. modelMap,
  119. structureModelMap: new Map(),
  120. cifFrame: frame,
  121. propertyProvider,
  122. cache: Object.create(null)
  123. };
  124. return ret;
  125. }
  126. export async function resolveStructure(wrapper: StructureWrapper, modelNum?: number) {
  127. if (typeof modelNum === 'undefined') modelNum = wrapper.models[0].modelNum;
  128. if (wrapper.structureModelMap.has(modelNum)) return wrapper.structureModelMap.get(modelNum)!;
  129. if (!wrapper.modelMap.has(modelNum)) {
  130. return void 0;
  131. }
  132. const model = wrapper.modelMap.get(modelNum)!;
  133. const structure = Structure.ofModel(model);
  134. if (wrapper.propertyProvider) {
  135. const modelProps = wrapper.propertyProvider(model, wrapper.cache);
  136. for (const p of modelProps) {
  137. await tryAttach(wrapper.key, p);
  138. }
  139. }
  140. return structure;
  141. }
  142. export async function resolveStructures(wrapper: StructureWrapper, modelNums?: number[]) {
  143. const ret: Structure[] = [];
  144. for (const n of modelNums || (wrapper.models as Model[]).map(m => m.modelNum)) {
  145. const s = await resolveStructure(wrapper, n);
  146. if (s) ret.push(s);
  147. }
  148. return ret;
  149. }
  150. async function tryAttach(key: string, promise: Promise<any>) {
  151. try {
  152. await promise;
  153. } catch (e) {
  154. ConsoleLogger.errorId(key, 'Custom prop:' + e);
  155. }
  156. }