UNPKG

molstar

Version:

A comprehensive macromolecular library.

219 lines (218 loc) 8.85 kB
/** * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. * * Taken/adapted from DensityServer (https://github.com/dsehnal/DensityServer) * * @author David Sehnal <david.sehnal@gmail.com> * @author Gianluca Tomasello <giagitom@gmail.com> */ import * as DataFormat from '../../common/data-format'; import * as Coords from '../algebra/coordinate'; import * as Box from '../algebra/box'; import { ConsoleLogger } from '../../../../mol-util/console-logger'; import { State } from '../state'; import { findUniqueBlocks } from './identify'; import { compose } from './compose'; import { encode } from './encode'; import { SpacegroupCell } from '../../../../mol-math/geometry'; import { Vec3 } from '../../../../mol-math/linear-algebra'; import { UUID } from '../../../../mol-util'; import { createTypedArray } from '../../../../mol-io/common/typed-array'; import { LimitsConfig } from '../../config'; import { fileHandleFromPathOrUrl } from '../../../common/file-handle'; export async function execute(params, outputProvider) { const start = getTime(); State.pendingQueries++; const guid = UUID.create22(); params.detail = Math.min(Math.max(0, params.detail | 0), LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel.length - 1); ConsoleLogger.logId(guid, 'Info', `id=${params.sourceId},encoding=${params.asBinary ? 'binary' : 'text'},detail=${params.detail},${queryBoxToString(params.box)}`); let sourceFile; try { sourceFile = await fileHandleFromPathOrUrl(params.sourceFilename, params.sourceFilename); await _execute(sourceFile, params, guid, outputProvider); return true; } catch (e) { ConsoleLogger.errorId(guid, e); return false; } finally { if (sourceFile) sourceFile.close(); ConsoleLogger.logId(guid, 'Time', `${Math.round(getTime() - start)}ms`); State.pendingQueries--; } } function getTime() { const t = process.hrtime(); return t[0] * 1000 + t[1] / 1000000; } function blockDomain(domain, blockSize) { const delta = Coords.fractional(blockSize * domain.delta[0], blockSize * domain.delta[1], blockSize * domain.delta[2]); return Coords.domain('Block', { origin: domain.origin, dimensions: domain.dimensions, delta, sampleCount: Coords.sampleCounts(domain.dimensions, delta) }); } function createSampling(header, index, dataOffset) { const sampling = header.sampling[index]; const dataDomain = Coords.domain('Data', { origin: Coords.fractional(header.origin[0], header.origin[1], header.origin[2]), dimensions: Coords.fractional(header.dimensions[0], header.dimensions[1], header.dimensions[2]), delta: Coords.fractional(header.dimensions[0] / sampling.sampleCount[0], header.dimensions[1] / sampling.sampleCount[1], header.dimensions[2] / sampling.sampleCount[2]), sampleCount: sampling.sampleCount }); return { index, rate: sampling.rate, byteOffset: sampling.byteOffset + dataOffset, dataDomain, blockDomain: blockDomain(dataDomain, header.blockSize) }; } async function createDataContext(file) { const { header, dataOffset } = await DataFormat.readHeader(file); const origin = Coords.fractional(header.origin[0], header.origin[1], header.origin[2]); const dimensions = Coords.fractional(header.dimensions[0], header.dimensions[1], header.dimensions[2]); return { file, header, spacegroup: SpacegroupCell.create(header.spacegroup.number, Vec3.ofArray(header.spacegroup.size), Vec3.scale(Vec3.zero(), Vec3.ofArray(header.spacegroup.angles), Math.PI / 180)), dataBox: { a: origin, b: Coords.add(origin, dimensions) }, sampling: header.sampling.map((s, i) => createSampling(header, i, dataOffset)) }; } function createQuerySampling(data, sampling, queryBox, queryParamsBox) { const fractionalBox = queryParamsBox.kind === 'Cell' ? Box.gridToFractional(Box.fractionalToGrid(queryBox, sampling.dataDomain)) : Box.gridToFractional(Box.expandGridBox(Box.fractionalToGrid(queryBox, sampling.dataDomain), 1)); const blocks = findUniqueBlocks(data, sampling, fractionalBox); const ret = { sampling, fractionalBox, gridDomain: Box.fractionalToDomain(fractionalBox, 'Query', sampling.dataDomain.delta), blocks }; return ret; } function pickSampling(data, queryBox, forcedLevel, precision, queryParamsBox) { if (forcedLevel > 0) { return createQuerySampling(data, data.sampling[Math.min(data.sampling.length, forcedLevel) - 1], queryBox, queryParamsBox); } const sizeLimit = LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel[precision] || (2 * 1024 * 1024); for (const s of data.sampling) { const gridBox = Box.fractionalToGrid(queryBox, s.dataDomain); const approxSize = Box.volume(gridBox); if (approxSize <= sizeLimit) { const sampling = createQuerySampling(data, s, queryBox, queryParamsBox); if (sampling.blocks.length <= LimitsConfig.maxRequestBlockCount) { return sampling; } } } return createQuerySampling(data, data.sampling[data.sampling.length - 1], queryBox, queryParamsBox); } function emptyQueryContext(data, params, guid) { return { kind: 'Empty', guid, params, data }; } function getQueryBox(data, queryBox) { switch (queryBox.kind) { case 'Cartesian': return Box.fractionalBoxReorderAxes(Box.cartesianToFractional(queryBox, data.spacegroup), data.header.axisOrder); case 'Fractional': return Box.fractionalBoxReorderAxes(queryBox, data.header.axisOrder); default: return data.dataBox; } } function allocateValues(domain, numChannels, valueType) { const values = []; for (let i = 0; i < numChannels; i++) { values[values.length] = createTypedArray(valueType, domain.sampleVolume); } return values; } function createQueryContext(data, params, guid) { const inputQueryBox = getQueryBox(data, params.box); let queryBox; if (!data.header.spacegroup.isPeriodic) { if (!Box.areIntersecting(data.dataBox, inputQueryBox)) { return emptyQueryContext(data, params, guid); } queryBox = Box.intersect(data.dataBox, inputQueryBox); } else { queryBox = inputQueryBox; } const dimensions = Box.dimensions(queryBox); if (dimensions.some(d => isNaN(d))) { throw new Error('The query box is not defined.'); } if (dimensions[0] * dimensions[1] * dimensions[2] > LimitsConfig.maxFractionalBoxVolume) { throw new Error('The query box volume is too big.'); } const samplingInfo = pickSampling(data, queryBox, params.forcedSamplingLevel !== void 0 ? params.forcedSamplingLevel : 0, params.detail, params.box); if (samplingInfo.blocks.length === 0) return emptyQueryContext(data, params, guid); return { kind: 'Data', guid, data, params, samplingInfo, values: allocateValues(samplingInfo.gridDomain, data.header.channels.length, data.header.valueType) }; } async function _execute(file, params, guid, outputProvider) { let output = void 0; try { // Step 1a: Create data context const data = await createDataContext(file); // Step 1b: Create query context const query = createQueryContext(data, params, guid); if (query.kind === 'Data') { // Step 3b: Compose the result data await compose(query); } // Step 4: Encode the result output = outputProvider(); encode(query, output); output.end(); } catch (e) { if (e.isFileNotFound) { // Just let respond with 404 throw e; } else { // Try to respond with body with error details const query = { kind: 'Error', guid, params, message: `${e}` }; try { if (!output) output = outputProvider(); encode(query, output); } catch (f) { throw f; } throw e; } } finally { if (output) output.end(); } } function roundCoord(c) { return Math.round(100000 * c) / 100000; } function queryBoxToString(queryBox) { switch (queryBox.kind) { case 'Cartesian': case 'Fractional': const { a, b } = queryBox; const r = roundCoord; return `box-type=${queryBox.kind},box-a=(${r(a[0])},${r(a[1])},${r(a[2])}),box-b=(${r(b[0])},${r(b[1])},${r(b[2])})`; default: return `box-type=${queryBox.kind}`; } }