molstar
Version:
A comprehensive macromolecular library.
223 lines (222 loc) • 9.35 kB
JavaScript
/**
* 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}`;
}
}
;