data.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. /**
  2. * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import * as CCP4 from '../../mol-io/reader/ccp4/parser';
  8. import { CIF } from '../../mol-io/reader/cif';
  9. import * as DSN6 from '../../mol-io/reader/dsn6/parser';
  10. import * as PLY from '../../mol-io/reader/ply/parser';
  11. import { parsePsf } from '../../mol-io/reader/psf/parser';
  12. import { PluginContext } from '../../mol-plugin/context';
  13. import { StateObject, StateTransformer } from '../../mol-state';
  14. import { Task } from '../../mol-task';
  15. import { ajaxGetMany } from '../../mol-util/data-source';
  16. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  17. import { PluginStateObject as SO, PluginStateTransform } from '../objects';
  18. import { Asset } from '../../mol-util/assets';
  19. import { parseCube } from '../../mol-io/reader/cube/parser';
  20. import { parseDx } from '../../mol-io/reader/dx/parser';
  21. import { ColorNames } from '../../mol-util/color/names';
  22. import { assertUnreachable } from '../../mol-util/type-helpers';
  23. import { parsePrmtop } from '../../mol-io/reader/prmtop/parser';
  24. import { parseTop } from '../../mol-io/reader/top/parser';
  25. export { Download };
  26. export { DownloadBlob };
  27. export { RawData };
  28. export { ReadFile };
  29. export { ParseBlob };
  30. export { ParseCif };
  31. export { ParseCube };
  32. export { ParsePsf };
  33. export { ParsePrmtop };
  34. export { ParseTop };
  35. export { ParsePly };
  36. export { ParseCcp4 };
  37. export { ParseDsn6 };
  38. export { ParseDx };
  39. export { ImportString };
  40. export { ImportJson };
  41. export { ParseJson };
  42. export { LazyVolume };
  43. type Download = typeof Download
  44. const Download = PluginStateTransform.BuiltIn({
  45. name: 'download',
  46. display: { name: 'Download', description: 'Download string or binary data from the specified URL' },
  47. from: [SO.Root],
  48. to: [SO.Data.String, SO.Data.Binary],
  49. params: {
  50. url: PD.Url('https://www.ebi.ac.uk/pdbe/static/entry/1cbs_updated.cif', { description: 'Resource URL. Must be the same domain or support CORS.' }),
  51. label: PD.Optional(PD.Text('')),
  52. isBinary: PD.Optional(PD.Boolean(false, { description: 'If true, download data as binary (string otherwise)' }))
  53. }
  54. })({
  55. apply({ params: p, cache }, plugin: PluginContext) {
  56. return Task.create('Download', async ctx => {
  57. const url = Asset.getUrlAsset(plugin.managers.asset, p.url);
  58. const asset = await plugin.managers.asset.resolve(url, p.isBinary ? 'binary' : 'string').runInContext(ctx);
  59. (cache as any).asset = asset;
  60. return p.isBinary
  61. ? new SO.Data.Binary(asset.data as Uint8Array, { label: p.label ? p.label : url.url })
  62. : new SO.Data.String(asset.data as string, { label: p.label ? p.label : url.url });
  63. });
  64. },
  65. dispose({ cache }) {
  66. ((cache as any)?.asset as Asset.Wrapper | undefined)?.dispose();
  67. },
  68. update({ oldParams, newParams, b }) {
  69. if (oldParams.url !== newParams.url || oldParams.isBinary !== newParams.isBinary) return StateTransformer.UpdateResult.Recreate;
  70. if (oldParams.label !== newParams.label) {
  71. b.label = newParams.label || ((typeof newParams.url === 'string') ? newParams.url : newParams.url.url);
  72. return StateTransformer.UpdateResult.Updated;
  73. }
  74. return StateTransformer.UpdateResult.Unchanged;
  75. }
  76. });
  77. type DownloadBlob = typeof DownloadBlob
  78. const DownloadBlob = PluginStateTransform.BuiltIn({
  79. name: 'download-blob',
  80. display: { name: 'Download Blob', description: 'Download multiple string or binary data from the specified URLs.' },
  81. from: SO.Root,
  82. to: SO.Data.Blob,
  83. params: {
  84. sources: PD.ObjectList({
  85. id: PD.Text('', { label: 'Unique ID' }),
  86. url: PD.Url('https://www.ebi.ac.uk/pdbe/static/entry/1cbs_updated.cif', { description: 'Resource URL. Must be the same domain or support CORS.' }),
  87. isBinary: PD.Optional(PD.Boolean(false, { description: 'If true, download data as binary (string otherwise)' })),
  88. canFail: PD.Optional(PD.Boolean(false, { description: 'Indicate whether the download can fail and not be included in the blob as a result.' }))
  89. }, e => `${e.id}: ${e.url}`),
  90. maxConcurrency: PD.Optional(PD.Numeric(4, { min: 1, max: 12, step: 1 }, { description: 'The maximum number of concurrent downloads.' }))
  91. }
  92. })({
  93. apply({ params, cache }, plugin: PluginContext) {
  94. return Task.create('Download Blob', async ctx => {
  95. const entries: SO.Data.BlobEntry[] = [];
  96. const data = await ajaxGetMany(ctx, plugin.managers.asset, params.sources, params.maxConcurrency || 4);
  97. const assets: Asset.Wrapper[] = [];
  98. for (let i = 0; i < data.length; i++) {
  99. const r = data[i], src = params.sources[i];
  100. if (r.kind === 'error') plugin.log.warn(`Download ${r.id} (${src.url}) failed: ${r.error}`);
  101. else {
  102. assets.push(r.result);
  103. entries.push(src.isBinary
  104. ? { id: r.id, kind: 'binary', data: r.result.data as Uint8Array }
  105. : { id: r.id, kind: 'string', data: r.result.data as string });
  106. }
  107. }
  108. (cache as any).assets = assets;
  109. return new SO.Data.Blob(entries, { label: 'Data Blob', description: `${entries.length} ${entries.length === 1 ? 'entry' : 'entries'}` });
  110. });
  111. },
  112. dispose({ cache }, plugin: PluginContext) {
  113. const assets: Asset.Wrapper[] | undefined = (cache as any)?.assets;
  114. if (!assets) return;
  115. for (const a of assets) {
  116. a.dispose();
  117. }
  118. }
  119. // TODO: ??
  120. // update({ oldParams, newParams, b }) {
  121. // return 0 as any;
  122. // // if (oldParams.url !== newParams.url || oldParams.isBinary !== newParams.isBinary) return StateTransformer.UpdateResult.Recreate;
  123. // // if (oldParams.label !== newParams.label) {
  124. // // (b.label as string) = newParams.label || newParams.url;
  125. // // return StateTransformer.UpdateResult.Updated;
  126. // // }
  127. // // return StateTransformer.UpdateResult.Unchanged;
  128. // }
  129. });
  130. type RawData = typeof RawData
  131. const RawData = PluginStateTransform.BuiltIn({
  132. name: 'raw-data',
  133. display: { name: 'Raw Data', description: 'Raw data supplied by value.' },
  134. from: [SO.Root],
  135. to: [SO.Data.String, SO.Data.Binary],
  136. params: {
  137. data: PD.Value<string | number[] | ArrayBuffer | Uint8Array>('', { isHidden: true }),
  138. label: PD.Optional(PD.Text(''))
  139. }
  140. })({
  141. apply({ params: p }) {
  142. return Task.create('Raw Data', async () => {
  143. if (typeof p.data === 'string') {
  144. return new SO.Data.String(p.data as string, { label: p.label ? p.label : 'String' });
  145. } else if (Array.isArray(p.data)) {
  146. return new SO.Data.Binary(new Uint8Array(p.data), { label: p.label ? p.label : 'Binary' });
  147. } else if (p.data instanceof ArrayBuffer) {
  148. return new SO.Data.Binary(new Uint8Array(p.data), { label: p.label ? p.label : 'Binary' });
  149. } else if (p.data instanceof Uint8Array) {
  150. return new SO.Data.Binary(p.data, { label: p.label ? p.label : 'Binary' });
  151. } else {
  152. assertUnreachable(p.data);
  153. }
  154. });
  155. },
  156. update({ oldParams, newParams, b }) {
  157. if (oldParams.data !== newParams.data) return StateTransformer.UpdateResult.Recreate;
  158. if (oldParams.label !== newParams.label) {
  159. b.label = newParams.label || b.label;
  160. return StateTransformer.UpdateResult.Updated;
  161. }
  162. return StateTransformer.UpdateResult.Unchanged;
  163. },
  164. customSerialization: {
  165. toJSON(p) {
  166. if (typeof p.data === 'string' || Array.isArray(p.data)) {
  167. return p;
  168. } else if (p.data instanceof ArrayBuffer) {
  169. const v = new Uint8Array(p.data);
  170. const data = new Array(v.length);
  171. for (let i = 0, _i = v.length; i < _i; i++) data[i] = v[i];
  172. return { data, label: p.label };
  173. } else if (p.data instanceof Uint8Array) {
  174. const data = new Array(p.data.length);
  175. for (let i = 0, _i = p.data.length; i < _i; i++) data[i] = p.data[i];
  176. return { data, label: p.label };
  177. }
  178. },
  179. fromJSON(data: any) {
  180. return data;
  181. }
  182. }
  183. });
  184. type ReadFile = typeof ReadFile
  185. const ReadFile = PluginStateTransform.BuiltIn({
  186. name: 'read-file',
  187. display: { name: 'Read File', description: 'Read string or binary data from the specified file' },
  188. from: SO.Root,
  189. to: [SO.Data.String, SO.Data.Binary],
  190. params: {
  191. file: PD.File(),
  192. label: PD.Optional(PD.Text('')),
  193. isBinary: PD.Optional(PD.Boolean(false, { description: 'If true, open file as as binary (string otherwise)' }))
  194. }
  195. })({
  196. apply({ params: p, cache }, plugin: PluginContext) {
  197. return Task.create('Open File', async ctx => {
  198. if (p.file === null) {
  199. plugin.log.error('No file(s) selected');
  200. return StateObject.Null;
  201. }
  202. const asset = await plugin.managers.asset.resolve(p.file, p.isBinary ? 'binary' : 'string').runInContext(ctx);
  203. (cache as any).asset = asset;
  204. const o = p.isBinary
  205. ? new SO.Data.Binary(asset.data as Uint8Array, { label: p.label ? p.label : p.file.name })
  206. : new SO.Data.String(asset.data as string, { label: p.label ? p.label : p.file.name });
  207. return o;
  208. });
  209. },
  210. dispose({ cache }) {
  211. ((cache as any)?.asset as Asset.Wrapper | undefined)?.dispose();
  212. },
  213. update({ oldParams, newParams, b }) {
  214. if (oldParams.label !== newParams.label) {
  215. (b.label as string) = newParams.label || oldParams.file?.name || '';
  216. return StateTransformer.UpdateResult.Updated;
  217. }
  218. return StateTransformer.UpdateResult.Unchanged;
  219. },
  220. isSerializable: () => ({ isSerializable: false, reason: 'Cannot serialize user loaded files.' })
  221. });
  222. type ParseBlob = typeof ParseBlob
  223. const ParseBlob = PluginStateTransform.BuiltIn({
  224. name: 'parse-blob',
  225. display: { name: 'Parse Blob', description: 'Parse multiple data enties' },
  226. from: SO.Data.Blob,
  227. to: SO.Format.Blob,
  228. params: {
  229. formats: PD.ObjectList({
  230. id: PD.Text('', { label: 'Unique ID' }),
  231. format: PD.Select<'cif'>('cif', [['cif', 'cif']])
  232. }, e => `${e.id}: ${e.format}`)
  233. }
  234. })({
  235. apply({ a, params }, plugin: PluginContext) {
  236. return Task.create('Parse Blob', async ctx => {
  237. const map = new Map<string, string>();
  238. for (const f of params.formats) map.set(f.id, f.format);
  239. const entries: SO.Format.BlobEntry[] = [];
  240. for (const e of a.data) {
  241. if (!map.has(e.id)) continue;
  242. const parsed = await (e.kind === 'string' ? CIF.parse(e.data) : CIF.parseBinary(e.data)).runInContext(ctx);
  243. if (parsed.isError) throw new Error(`${e.id}: ${parsed.message}`);
  244. entries.push({ id: e.id, kind: 'cif', data: parsed.result });
  245. }
  246. return new SO.Format.Blob(entries, { label: 'Format Blob', description: `${entries.length} ${entries.length === 1 ? 'entry' : 'entries'}` });
  247. });
  248. },
  249. // TODO: ??
  250. // update({ oldParams, newParams, b }) {
  251. // return 0 as any;
  252. // // if (oldParams.url !== newParams.url || oldParams.isBinary !== newParams.isBinary) return StateTransformer.UpdateResult.Recreate;
  253. // // if (oldParams.label !== newParams.label) {
  254. // // (b.label as string) = newParams.label || newParams.url;
  255. // // return StateTransformer.UpdateResult.Updated;
  256. // // }
  257. // // return StateTransformer.UpdateResult.Unchanged;
  258. // }
  259. });
  260. type ParseCif = typeof ParseCif
  261. const ParseCif = PluginStateTransform.BuiltIn({
  262. name: 'parse-cif',
  263. display: { name: 'Parse CIF', description: 'Parse CIF from String or Binary data' },
  264. from: [SO.Data.String, SO.Data.Binary],
  265. to: SO.Format.Cif
  266. })({
  267. apply({ a }) {
  268. return Task.create('Parse CIF', async ctx => {
  269. const parsed = await (typeof a.data === 'string' ? CIF.parse(a.data) : CIF.parseBinary(a.data)).runInContext(ctx);
  270. if (parsed.isError) throw new Error(parsed.message);
  271. return new SO.Format.Cif(parsed.result);
  272. });
  273. }
  274. });
  275. type ParseCube = typeof ParseCube
  276. const ParseCube = PluginStateTransform.BuiltIn({
  277. name: 'parse-cube',
  278. display: { name: 'Parse Cube', description: 'Parse Cube from String data' },
  279. from: SO.Data.String,
  280. to: SO.Format.Cube
  281. })({
  282. apply({ a }) {
  283. return Task.create('Parse Cube', async ctx => {
  284. const parsed = await parseCube(a.data, a.label).runInContext(ctx);
  285. if (parsed.isError) throw new Error(parsed.message);
  286. return new SO.Format.Cube(parsed.result);
  287. });
  288. }
  289. });
  290. type ParsePsf = typeof ParsePsf
  291. const ParsePsf = PluginStateTransform.BuiltIn({
  292. name: 'parse-psf',
  293. display: { name: 'Parse PSF', description: 'Parse PSF from String data' },
  294. from: [SO.Data.String],
  295. to: SO.Format.Psf
  296. })({
  297. apply({ a }) {
  298. return Task.create('Parse PSF', async ctx => {
  299. const parsed = await parsePsf(a.data).runInContext(ctx);
  300. if (parsed.isError) throw new Error(parsed.message);
  301. return new SO.Format.Psf(parsed.result);
  302. });
  303. }
  304. });
  305. type ParsePrmtop = typeof ParsePrmtop
  306. const ParsePrmtop = PluginStateTransform.BuiltIn({
  307. name: 'parse-prmtop',
  308. display: { name: 'Parse PRMTOP', description: 'Parse PRMTOP from String data' },
  309. from: [SO.Data.String],
  310. to: SO.Format.Prmtop
  311. })({
  312. apply({ a }) {
  313. return Task.create('Parse PRMTOP', async ctx => {
  314. const parsed = await parsePrmtop(a.data).runInContext(ctx);
  315. if (parsed.isError) throw new Error(parsed.message);
  316. return new SO.Format.Prmtop(parsed.result);
  317. });
  318. }
  319. });
  320. type ParseTop = typeof ParseTop
  321. const ParseTop = PluginStateTransform.BuiltIn({
  322. name: 'parse-top',
  323. display: { name: 'Parse TOP', description: 'Parse TOP from String data' },
  324. from: [SO.Data.String],
  325. to: SO.Format.Top
  326. })({
  327. apply({ a }) {
  328. return Task.create('Parse TOP', async ctx => {
  329. const parsed = await parseTop(a.data).runInContext(ctx);
  330. if (parsed.isError) throw new Error(parsed.message);
  331. return new SO.Format.Top(parsed.result);
  332. });
  333. }
  334. });
  335. type ParsePly = typeof ParsePly
  336. const ParsePly = PluginStateTransform.BuiltIn({
  337. name: 'parse-ply',
  338. display: { name: 'Parse PLY', description: 'Parse PLY from String data' },
  339. from: [SO.Data.String],
  340. to: SO.Format.Ply
  341. })({
  342. apply({ a }) {
  343. return Task.create('Parse PLY', async ctx => {
  344. const parsed = await PLY.parsePly(a.data).runInContext(ctx);
  345. if (parsed.isError) throw new Error(parsed.message);
  346. return new SO.Format.Ply(parsed.result, { label: parsed.result.comments[0] || 'PLY Data' });
  347. });
  348. }
  349. });
  350. type ParseCcp4 = typeof ParseCcp4
  351. const ParseCcp4 = PluginStateTransform.BuiltIn({
  352. name: 'parse-ccp4',
  353. display: { name: 'Parse CCP4/MRC/MAP', description: 'Parse CCP4/MRC/MAP from Binary data' },
  354. from: [SO.Data.Binary],
  355. to: SO.Format.Ccp4
  356. })({
  357. apply({ a }) {
  358. return Task.create('Parse CCP4/MRC/MAP', async ctx => {
  359. const parsed = await CCP4.parse(a.data, a.label).runInContext(ctx);
  360. if (parsed.isError) throw new Error(parsed.message);
  361. return new SO.Format.Ccp4(parsed.result);
  362. });
  363. }
  364. });
  365. type ParseDsn6 = typeof ParseDsn6
  366. const ParseDsn6 = PluginStateTransform.BuiltIn({
  367. name: 'parse-dsn6',
  368. display: { name: 'Parse DSN6/BRIX', description: 'Parse CCP4/BRIX from Binary data' },
  369. from: [SO.Data.Binary],
  370. to: SO.Format.Dsn6
  371. })({
  372. apply({ a }) {
  373. return Task.create('Parse DSN6/BRIX', async ctx => {
  374. const parsed = await DSN6.parse(a.data, a.label).runInContext(ctx);
  375. if (parsed.isError) throw new Error(parsed.message);
  376. return new SO.Format.Dsn6(parsed.result);
  377. });
  378. }
  379. });
  380. type ParseDx = typeof ParseDx
  381. const ParseDx = PluginStateTransform.BuiltIn({
  382. name: 'parse-dx',
  383. display: { name: 'Parse DX', description: 'Parse DX from Binary/String data' },
  384. from: [SO.Data.Binary, SO.Data.String],
  385. to: SO.Format.Dx
  386. })({
  387. apply({ a }) {
  388. return Task.create('Parse DX', async ctx => {
  389. const parsed = await parseDx(a.data, a.label).runInContext(ctx);
  390. if (parsed.isError) throw new Error(parsed.message);
  391. return new SO.Format.Dx(parsed.result);
  392. });
  393. }
  394. });
  395. type ImportString = typeof ImportString
  396. const ImportString = PluginStateTransform.BuiltIn({
  397. name: 'import-string',
  398. display: { name: 'Import String', description: 'Import given data as a string' },
  399. from: SO.Root,
  400. to: SO.Data.String,
  401. params: {
  402. data: PD.Value(''),
  403. label: PD.Optional(PD.Text('')),
  404. }
  405. })({
  406. apply({ params: { data, label } }) {
  407. return new SO.Data.String(data, { label: label || '' });
  408. },
  409. update({ oldParams, newParams, b }) {
  410. if (oldParams.data !== newParams.data) return StateTransformer.UpdateResult.Recreate;
  411. if (oldParams.label !== newParams.label) {
  412. b.label = newParams.label || '';
  413. return StateTransformer.UpdateResult.Updated;
  414. }
  415. return StateTransformer.UpdateResult.Unchanged;
  416. },
  417. isSerializable: () => ({ isSerializable: false, reason: 'Cannot serialize user imported strings.' })
  418. });
  419. type ImportJson = typeof ImportJson
  420. const ImportJson = PluginStateTransform.BuiltIn({
  421. name: 'import-json',
  422. display: { name: 'Import JSON', description: 'Import given data as a JSON' },
  423. from: SO.Root,
  424. to: SO.Format.Json,
  425. params: {
  426. data: PD.Value<any>({}),
  427. label: PD.Optional(PD.Text('')),
  428. }
  429. })({
  430. apply({ params: { data, label } }) {
  431. return new SO.Format.Json(data, { label: label || '' });
  432. },
  433. update({ oldParams, newParams, b }) {
  434. if (oldParams.data !== newParams.data) return StateTransformer.UpdateResult.Recreate;
  435. if (oldParams.label !== newParams.label) {
  436. b.label = newParams.label || '';
  437. return StateTransformer.UpdateResult.Updated;
  438. }
  439. return StateTransformer.UpdateResult.Unchanged;
  440. },
  441. isSerializable: () => ({ isSerializable: false, reason: 'Cannot serialize user imported JSON.' })
  442. });
  443. type ParseJson = typeof ParseJson
  444. const ParseJson = PluginStateTransform.BuiltIn({
  445. name: 'parse-json',
  446. display: { name: 'Parse JSON', description: 'Parse JSON from String data' },
  447. from: [SO.Data.String],
  448. to: SO.Format.Json
  449. })({
  450. apply({ a }) {
  451. return Task.create('Parse JSON', async ctx => {
  452. const json = await (new Response(a.data)).json(); // async JSON parsing via fetch API
  453. return new SO.Format.Json(json);
  454. });
  455. }
  456. });
  457. type LazyVolume = typeof LazyVolume
  458. const LazyVolume = PluginStateTransform.BuiltIn({
  459. name: 'lazy-volume',
  460. display: { name: 'Lazy Volume', description: 'A placeholder for lazy loaded volume representation' },
  461. from: SO.Root,
  462. to: SO.Volume.Lazy,
  463. params: {
  464. url: PD.Url(''),
  465. isBinary: PD.Boolean(false),
  466. format: PD.Text('ccp4'), // TODO: use Select based on available formats
  467. entryId: PD.Value<string | string[]>('', { isHidden: true }),
  468. isovalues: PD.ObjectList({
  469. type: PD.Text<'absolute' | 'relative'>('relative'), // TODO: Select
  470. value: PD.Numeric(0),
  471. color: PD.Color(ColorNames.black),
  472. alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
  473. volumeIndex: PD.Numeric(0),
  474. }, e => `${e.type} ${e.value}`)
  475. }
  476. })({
  477. apply({ a, params }) {
  478. return Task.create('Lazy Volume', async ctx => {
  479. const entryId = Array.isArray(params.entryId) ? params.entryId.join(', ') : params.entryId;
  480. return new SO.Volume.Lazy(params, { label: `${entryId || params.url}`, description: 'Lazy Volume' });
  481. });
  482. }
  483. });