UNPKG

molstar

Version:

A comprehensive macromolecular library.

223 lines (222 loc) 9.35 kB
"use strict"; /** * 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> */ Object.defineProperty(exports, "__esModule", { value: true }); exports.execute = execute; const tslib_1 = require("tslib"); const DataFormat = tslib_1.__importStar(require("../../common/data-format")); const Coords = tslib_1.__importStar(require("../algebra/coordinate")); const Box = tslib_1.__importStar(require("../algebra/box")); const console_logger_1 = require("../../../../mol-util/console-logger"); const state_1 = require("../state"); const identify_1 = require("./identify"); const compose_1 = require("./compose"); const encode_1 = require("./encode"); const geometry_1 = require("../../../../mol-math/geometry"); const linear_algebra_1 = require("../../../../mol-math/linear-algebra"); const mol_util_1 = require("../../../../mol-util"); const typed_array_1 = require("../../../../mol-io/common/typed-array"); const config_1 = require("../../config"); const file_handle_1 = require("../../../common/file-handle"); async function execute(params, outputProvider) { const start = getTime(); state_1.State.pendingQueries++; const guid = mol_util_1.UUID.create22(); params.detail = Math.min(Math.max(0, params.detail | 0), config_1.LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel.length - 1); console_logger_1.ConsoleLogger.logId(guid, 'Info', `id=${params.sourceId},encoding=${params.asBinary ? 'binary' : 'text'},detail=${params.detail},${queryBoxToString(params.box)}`); let sourceFile; try { sourceFile = await (0, file_handle_1.fileHandleFromPathOrUrl)(params.sourceFilename, params.sourceFilename); await _execute(sourceFile, params, guid, outputProvider); return true; } catch (e) { console_logger_1.ConsoleLogger.errorId(guid, e); return false; } finally { if (sourceFile) sourceFile.close(); console_logger_1.ConsoleLogger.logId(guid, 'Time', `${Math.round(getTime() - start)}ms`); state_1.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: geometry_1.SpacegroupCell.create(header.spacegroup.number, linear_algebra_1.Vec3.ofArray(header.spacegroup.size), linear_algebra_1.Vec3.scale(linear_algebra_1.Vec3.zero(), linear_algebra_1.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 = (0, identify_1.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 = config_1.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 <= config_1.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] = (0, typed_array_1.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] > config_1.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 (0, compose_1.compose)(query); } // Step 4: Encode the result output = outputProvider(); (0, encode_1.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(); (0, encode_1.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}`; } }