diff --git a/package-lock.json b/package-lock.json index 763951320022faa79a1de613aa74388c26a6ec04..2d5ba0706c841759e5799a5ea660666943a15eda 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index 2ba5f74e9f1abf9992be6e25283dc89d4aead432..b7d259bc66d9259e5ec1ca239f302d163a68bd69 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "@types/react": "^16.8.4", "@types/react-dom": "^16.8.2", "@types/webgl2": "0.0.4", + "@types/swagger-ui-dist": "3.0.0", "benchmark": "^2.1.4", "circular-dependency-plugin": "^5.0.2", "concurrently": "^4.1.0", @@ -123,6 +124,7 @@ "node-fetch": "^2.3.0", "react": "^16.8.2", "react-dom": "^16.8.2", - "rxjs": "^6.4.0" + "rxjs": "^6.4.0", + "swagger-ui-dist": "^3.20.9" } } diff --git a/src/servers/common/swagger-ui/index.ts b/src/servers/common/swagger-ui/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..ae48a4b1c345f8858fb427f3be7a5eeee82417ac --- /dev/null +++ b/src/servers/common/swagger-ui/index.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as express from 'express' +import * as fs from 'fs' +import { getAbsoluteFSPath } from 'swagger-ui-dist' +import { ServeStaticOptions } from 'serve-static'; +import { interpolate } from 'mol-util/string'; + +export function swaggerUiAssetsHandler(options?: ServeStaticOptions) { + const opts = options || {} + opts.index = false + return express.static(getAbsoluteFSPath(), opts) +} + +function createHTML(swaggerUrl: string, apiPrefix: string) { + const htmlTemplate = fs.readFileSync(`${__dirname}/indexTemplate.html`).toString() + return interpolate(htmlTemplate, { swaggerUrl, apiPrefix }) +} + +export function swaggerUiIndexHandler(swaggerUrl: string, apiPrefix: string): express.Handler { + const html = createHTML(swaggerUrl, apiPrefix) + return (req: express.Request, res: express.Response) => { + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end(html); + } +} \ No newline at end of file diff --git a/src/servers/common/swagger-ui/indexTemplate.html b/src/servers/common/swagger-ui/indexTemplate.html new file mode 100644 index 0000000000000000000000000000000000000000..0f334da2c2270b1df2e2ab8b8ffda388609d444c --- /dev/null +++ b/src/servers/common/swagger-ui/indexTemplate.html @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8"> + <title>Swagger UI</title> + <link rel="stylesheet" type="text/css" href="${apiPrefix}/swagger-ui.css" > + <style> + html + { + box-sizing: border-box; + overflow: -moz-scrollbars-vertical; + overflow-y: scroll; + } + *, + *:before, + *:after + { + box-sizing: inherit; + } + body + { + margin:0; + background: #fafafa; + } + </style> + </head> + + <body> + <div id="swagger-ui"></div> + + <script src="${apiPrefix}/swagger-ui-bundle.js"> </script> + <script src="${apiPrefix}/swagger-ui-standalone-preset.js"> </script> + <script> + function HidePlugin() { + // this plugin overrides some components to return nothing + return { + components: { + Topbar: function () { return null }, + Models: function () { return null }, + } + } + } + window.onload = function () { + var ui = SwaggerUIBundle({ + url: '${swaggerUrl}', + validatorUrl: null, + docExpansion: 'list', + dom_id: '#swagger-ui', + deepLinking: true, + presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIStandalonePreset + ], + plugins: [ + SwaggerUIBundle.plugins.DownloadUrl, + HidePlugin + ], + layout: 'StandaloneLayout' + }) + window.ui = ui + } + </script> + </body> +</html> \ No newline at end of file diff --git a/src/servers/volume/common/data-format.ts b/src/servers/volume/common/data-format.ts index 2f0525475388dd923732969a3f110181ba32d926..e3a51fe8cc89353ca46d4801c975378f64330d91 100644 --- a/src/servers/volume/common/data-format.ts +++ b/src/servers/volume/common/data-format.ts @@ -32,7 +32,7 @@ export interface Sampling { rate: number, valuesInfo: ValuesInfo[], - /** Number of samples along each axis, in axisOrder */ + /** Number of samples along each axis, in axisOrder */ sampleCount: number[] } diff --git a/src/servers/volume/server/api.ts b/src/servers/volume/server/api.ts index 6fa227adf6ded744658c075b3eb43e008d925a54..e3a9c247ab5808dc5a10e5e0a88b7302f0777154 100644 --- a/src/servers/volume/server/api.ts +++ b/src/servers/volume/server/api.ts @@ -26,16 +26,21 @@ export function getOutputFilename(source: string, id: string, { asBinary, box, d return `${n(source)}_${n(id)}-${boxInfo}_${det}.${asBinary ? 'bcif' : 'cif'}`; } +export interface ExtendedHeader extends DataFormat.Header { + availablePrecisions: { precision: number, maxVoxels: number }[] + isAvailable: boolean +} + /** Reads the header and includes information about available detail levels */ -export async function getHeaderJson(filename: string | undefined, sourceId: string) { +export async function getExtendedHeaderJson(filename: string | undefined, sourceId: string) { ConsoleLogger.log('Header', sourceId); try { if (!filename || !File.exists(filename)) { ConsoleLogger.error(`Header ${sourceId}`, 'File not found.'); return void 0; } - const header = { ...await readHeader(filename, sourceId) } as DataFormat.Header; - const { sampleCount } = header!.sampling[0]; + const header: Partial<ExtendedHeader> = { ...await readHeader(filename, sourceId) }; + const { sampleCount } = header.sampling![0]; const maxVoxelCount = sampleCount[0] * sampleCount[1] * sampleCount[2]; const precisions = LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel .map((maxVoxels, precision) => ({ precision, maxVoxels })); @@ -44,8 +49,8 @@ export async function getHeaderJson(filename: string | undefined, sourceId: stri availablePrecisions.push(p); if (p.maxVoxels > maxVoxelCount) break; } - (header as any).availablePrecisions = availablePrecisions; - (header as any).isAvailable = true; + header.availablePrecisions = availablePrecisions; + header.isAvailable = true; return JSON.stringify(header, null, 2); } catch (e) { ConsoleLogger.error(`Header ${sourceId}`, e); diff --git a/src/servers/volume/server/documentation.ts b/src/servers/volume/server/documentation.ts deleted file mode 100644 index 4713359d9342b46c7ca4cc67e2b6433719422ca2..0000000000000000000000000000000000000000 --- a/src/servers/volume/server/documentation.ts +++ /dev/null @@ -1,177 +0,0 @@ -/** - * Copyright (c) 2018-2019 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 Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import VERSION from './version' -import { LimitsConfig } from '../config'; - -export function getDocumentation() { - function detail(i: number) { - return `<span class='id'>${i}</span><small> (${Math.round(100 * LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel[i] / 1000 / 1000) / 100 }M voxels)</small>`; - } - const detailMax = LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel.length - 1; - - // TODO get from config - const dataSource = `Specifies the data source (determined by the experiment method). Currently, <span class='id'>x-ray</span> and <span class='id'>em</span> sources are supported.`; - const entryId = `Id of the entry. For <span class='id'>x-ray</span>, use PDB ID (i.e. <span class='id'>1cbs</span>) and for <span class='id'>em</span> use EMDB id (i.e. <span class='id'>emd-8116</span>).`; - - return `<!DOCTYPE html> -<html xmlns="http://www.w3.org/1999/xhtml"> -<head> -<meta charset="utf-8" /> -<link rel='shortcut icon' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAnUExURQAAAMIrHrspHr0oH7soILonHrwqH7onILsoHrsoH7soH7woILwpIKgVokoAAAAMdFJOUwAQHzNxWmBHS5XO6jdtAmoAAACZSURBVDjLxZNRCsQgDAVNXmwb9f7nXZEaLRgXloXOhwQdjMYYwpOLw55fBT46KhbOKhmRR2zLcFJQj8UR+HxFgArIF5BKJbEncC6NDEdI5SatBRSDJwGAoiFDONrEJXWYhGMIcRJGCrb1TOtDahfUuQXd10jkFYq0ViIrbUpNcVT6redeC1+b9tH2WLR93Sx2VCzkv/7NjfABxjQHksGB7lAAAAAASUVORK5CYII=' /> -<title>VolumeServer (${VERSION})</title> -<style> -html { -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; } -body { margin: 0; font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; font-weight: 300; color: #333; line-height: 1.42857143; font-size: 14px } -.container { padding: 0 15px; max-width: 970px; margin: 0 auto; } -small { font-size: 80% } -h2, h4 { font-weight: 500; line-height: 1.1; } -h2 { color: black; font-size: 24px; } -h4 { font-size: 18px; margin: 20px 0 10px 0 } -h2 small { color: #777; font-weight: 300 } -hr { box-sizing: content-box; height: 0; overflow: visible; } -a { background-color: transparent; -webkit-text-decoration-skip: objects; text-decoration: none } -a:active, a:hover { outline-width: 0; } -a:focus, a:hover { text-decoration: underline; color: #23527c } -.list-unstyled { padding: 0; list-style: none; margin: 0 0 10px 0 } -.cs-docs-query-wrap { padding: 24px 0; border-bottom: 1px solid #eee } -.cs-docs-query-wrap > h2 { margin: 0; color: black; } -.cs-docs-query-wrap > h2 > span { color: #DE4D4E; font-family: Menlo,Monaco,Consolas,"Courier New",monospace; font-size: 90% } -.cs-docs-param-name, .cs-docs-template-link { color: #DE4D4E; font-family: Menlo,Monaco,Consolas,"Courier New",monospace } -table {margin: 0; padding: 0; } -table th { font-weight: bold; border-bottom: none; text-align: left; padding: 6px 12px } -td { padding: 6px 12px } -td:not(:last-child), th:not(:last-child) { border-right: 1px dotted #ccc } -tr:nth-child(even) { background: #f9f9f9 } -span.id { color: #DE4D4E; font-family: Menlo,Monaco,Consolas,"Courier New",monospace; } -</style> -</head> -<body> -<div class="container"> -<div style='text-align: center; margin-top: 24px;'><span style='font-weight: bold; font-size: 16pt'>VolumeServer</span> <span>${VERSION}</span></div> - -<div style='text-align: justify; padding: 24px 0; border-bottom: 1px solid #eee'> - <p> - <b>VolumeServer</b> is a service for accessing subsets of volumetric density data. It automatically downsamples the data - depending on the volume of the requested region to reduce the bandwidth requirements and provide near-instant access to even the - largest data sets. - </p> - <p> - It uses the text based <a href='https://en.wikipedia.org/wiki/Crystallographic_Information_File'>CIF</a> and binary - <a href='https://github.com/dsehnal/BinaryCIF' style='font-weight: bold'>BinaryCIF</a> - formats to deliver the data to the client. - The server support is integrated into the <a href='https://github.com/dsehnal/LiteMol' style='font-weight: bold'>LiteMol Viewer</a>. - </p> -</div> - -<div class="cs-docs-query-wrap"> - <h2>Data Header / Check Availability <span>/<source>/<id></span><br> - <small>Returns a JSON response specifying if data is available and the maximum region that can be queried.</small></h2> - <div id="coordserver-documentation-ambientResidues-body" style="margin: 24px 24px 0 24px"> - <h4>Examples</h4> - <a href="/VolumeServer/x-ray/1cbs" class="cs-docs-template-link" target="_blank" rel="nofollow">/x-ray/1cbs</a><br> - <a href="/VolumeServer/em/emd-8116" class="cs-docs-template-link" target="_blank" rel="nofollow">/em/emd-8116</a> - <h4>Parameters</h4> - <table cellpadding="0" cellspacing="0" style='width: 100%'> - <tbody><tr><th style='width: 80px'>Name</th><th>Description</th></tr> - <tr> - <td class="cs-docs-param-name">source</td> - <td>${dataSource}</td> - </tr> - <tr> - <td class="cs-docs-param-name">id</td> - <td>${entryId}</td> - </tr> - </tbody></table> - </div> -</div> - -<div class="cs-docs-query-wrap"> - <h2>Box <span>/<source>/<id>/box/<a,b,c>/<u,v,w>?<optional parameters></span><br> - <small>Returns density data inside the specified box for the given entry. For X-ray data, returns 2Fo-Fc and Fo-Fc volumes in a single response.</small></h2> - <div style="margin: 24px 24px 0 24px"> - <h4>Examples</h4> - <a href="/VolumeServer/em/emd-8003/box/-2,7,10/4,10,15.5?encoding=cif&space=cartesian" class="cs-docs-template-link" target="_blank" rel="nofollow">/em/emd-8003/box/-2,7,10/4,10,15.5?excoding=cif&space=cartesian</a><br> - <a href="/VolumeServer/x-ray/1cbs/box/0.1,0.1,0.1/0.23,0.31,0.18?space=fractional" class="cs-docs-template-link" target="_blank" rel="nofollow">/x-ray/1cbs/box/0.1,0.1,0.1/0.23,0.31,0.18?space=fractional</a> - <h4>Parameters</h4> - <table cellpadding="0" cellspacing="0" style='width: 100%'> - <tbody><tr><th style='width: 80px'>Name</th><th>Description</th></tr> - <tr> - <td class="cs-docs-param-name">source</td> - <td>${dataSource}</td> - </tr> - <tr> - <td class="cs-docs-param-name">id</td> - <td>${entryId}</td> - </tr> - <tr> - <td class="cs-docs-param-name">a,b,c</td> - <td>Bottom left corner of the query region in Cartesian or fractional coordinates (determined by the <span class='id'>&space</span> query parameter).</td> - </tr> - <tr> - <td class="cs-docs-param-name">u,v,w</td> - <td>Top right corner of the query region in Cartesian or fractional coordinates (determined by the <span class='id'>&space</span> query parameter).</td> - </tr> - <tr> - <td class="cs-docs-param-name">encoding</td> - <td>Determines if text based <span class='id'>CIF</span> or binary <span class='id'>BinaryCIF</span> encoding is used. An optional argument, default is <span class='id'>BinaryCIF</span> encoding.</td> - </tr> - <tr> - <td class="cs-docs-param-name">space</td> - <td>Determines the coordinate space the query is in. Can be <span class='id'>cartesian</span> or <span class='id'>fractional</span>. An optional argument, default values is <span class='id'>cartesian</span>.</td> - </tr> - <tr> - <td class="cs-docs-param-name">detail</td> - <td> - Determines the maximum number of voxels the query can return. Possible values are in the range from ${detail(0)} to ${detail(detailMax)}. - Default value is <span class='id'>0</span>. Note: different detail levels might lead to the same result. - </td> - </tr> - </tbody></table> - </div> -</div> - -<div class="cs-docs-query-wrap"> - <h2>Cell <span>/<source>/<id>/cell?<optional parameters></span><br> - <small>Returns (downsampled) volume data for the entire "data cell". For X-ray data, returns unit cell of 2Fo-Fc and Fo-Fc volumes, for EM data returns everything.</small></h2> - <div style="margin: 24px 24px 0 24px"> - <h4>Example</h4> - <a href="/VolumeServer/em/emd-8116/cell?detail=1" class="cs-docs-template-link" target="_blank" rel="nofollow">/em/emd-8116/cell?detail=1</a><br> - <h4>Parameters</h4> - <table cellpadding="0" cellspacing="0" style='width: 100%'> - <tbody><tr><th style='width: 80px'>Name</th><th>Description</th></tr> - <tr> - <td class="cs-docs-param-name">source</td> - <td>${dataSource}</td> - </tr> - <tr> - <td class="cs-docs-param-name">id</td> - <td>${entryId}</td> - </tr> - <tr> - <td class="cs-docs-param-name">encoding</td> - <td>Determines if text based <span class='id'>CIF</span> or binary <span class='id'>BinaryCIF</span> encoding is used. An optional argument, default is <span class='id'>BinaryCIF</span> encoding.</td> - </tr> - <tr> - <td class="cs-docs-param-name">detail</td> - <td> - Determines the maximum number of voxels the query can return. Possible values are in the range from ${detail(0)} to ${detail(detailMax)}. - Default value is <span class='id'>0</span>. Note: different detail levels might lead to the same result. - </td> - </tr> - </tbody></table> - </div> -</div> - - -<div style="color: #999;font-size:smaller;margin: 20px 0; text-align: right">© 2016 – now, David Sehnal | Node ${process.version}</div> - -</body> -</html>`; -} \ No newline at end of file diff --git a/src/servers/volume/server/web-api.ts b/src/servers/volume/server/web-api.ts index 0b9dbc1d0f93c7a5cf3ac572f269123725e07b6f..4ac65fcf96ca40ad9b12eb5f8209e692acbebb6b 100644 --- a/src/servers/volume/server/web-api.ts +++ b/src/servers/volume/server/web-api.ts @@ -10,19 +10,19 @@ import * as express from 'express' import * as Api from './api' - import * as Data from './query/data-model' import * as Coords from './algebra/coordinate' -import { getDocumentation } from './documentation' import { ConsoleLogger } from 'mol-util/console-logger' import { State } from './state' import { LimitsConfig, ServerConfig } from '../config'; import { interpolate } from 'mol-util/string'; +import { getSchema } from './web-schema'; +import { swaggerUiIndexHandler, swaggerUiAssetsHandler } from 'servers/common/swagger-ui'; export default function init(app: express.Express) { app.locals.mapFile = getMapFileFn() function makePath(p: string) { - return ServerConfig.apiPrefix + '/' + p; + return `${ServerConfig.apiPrefix}/${p}`; } // Header @@ -32,10 +32,17 @@ export default function init(app: express.Express) { // Cell /:src/:id/cell/?text=0|1&space=cartesian|fractional app.get(makePath(':source/:id/cell/?'), (req, res) => queryBox(req, res, getQueryParams(req, true))); - app.get('*', (req, res) => { - res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); - res.end(getDocumentation()); + app.get(makePath('openapi.json'), (req, res) => { + res.writeHead(200, { + 'Content-Type': 'application/json; charset=utf-8', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'X-Requested-With' + }); + res.end(JSON.stringify(getSchema())); }); + + app.use(makePath(''), swaggerUiAssetsHandler()); + app.get(makePath(''), swaggerUiIndexHandler(makePath('openapi.json'), ServerConfig.apiPrefix)); } function getMapFileFn() { @@ -115,7 +122,7 @@ async function getHeader(req: express.Request, res: express.Response) { try { const { filename, id } = getSourceInfo(req); - const header = await Api.getHeaderJson(filename, id); + const header = await Api.getExtendedHeaderJson(filename, id); if (!header) { res.writeHead(404); return; diff --git a/src/servers/volume/server/web-schema.ts b/src/servers/volume/server/web-schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..e24eb39a4ce8252aa6995ca4f0b6da4b532a0a46 --- /dev/null +++ b/src/servers/volume/server/web-schema.ts @@ -0,0 +1,259 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import VERSION from './version' +import { LimitsConfig, ServerConfig } from '../config'; + +export function getSchema() { + function detail(i: number) { + return `${i} (${Math.round(100 * LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel[i] / 1000 / 1000) / 100 }M voxels)`; + } + const detailMax = LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel.length - 1; + const sources = ServerConfig.idMap.map(m => m[0]) + + return { + openapi: '3.0.0', + info: { + version: VERSION, + title: 'Volume Server', + description: 'The VolumeServer is a service for accessing subsets of volumetric data. It automatically downsamples the data depending on the volume of the requested region to reduce the bandwidth requirements and provide near-instant access to even the largest data sets.', + }, + tags: [ + { + name: 'General', + } + ], + paths: { + [`${ServerConfig.apiPrefix}/{source}/{id}/`]: { + get: { + tags: ['General'], + summary: 'Returns a JSON response specifying if data is available and the maximum region that can be queried.', + operationId: 'getInfo', + parameters: [ + { $ref: '#/components/parameters/source' }, + { $ref: '#/components/parameters/id' }, + ], + responses: { + 200: { + description: 'Volume availability and info', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/info' } + } + } + }, + }, + } + }, + [`${ServerConfig.apiPrefix}/{source}/{id}/box/{a1,a2,a3}/{b1,b2,b3}/`]: { + get: { + tags: ['General'], + summary: 'Returns density data inside the specified box for the given entry. For X-ray data, returns 2Fo-Fc and Fo-Fc volumes in a single response.', + operationId: 'getBox', + parameters: [ + { $ref: '#/components/parameters/source' }, + { $ref: '#/components/parameters/id' }, + { + name: 'bottomLeftCorner', + in: 'path', + description: 'Bottom left corner of the query region in Cartesian or fractional coordinates (determined by the `space` query parameter).', + required: true, + schema: { + type: 'list', + items: { + type: 'float', + } + }, + style: 'simple' + }, + { + name: 'topRightCorner', + in: 'path', + description: 'Top right corner of the query region in Cartesian or fractional coordinates (determined by the `space` query parameter).', + required: true, + schema: { + type: 'list', + items: { + type: 'float', + } + }, + style: 'simple' + }, + { $ref: '#/components/parameters/encoding' }, + { $ref: '#/components/parameters/detail' }, + { + name: 'space', + in: 'query', + description: 'Determines the coordinate space the query is in. Can be cartesian or fractional. An optional argument, default values is cartesian.', + schema: { + type: 'string', + enum: ['cartesian', 'fractional'] + }, + style: 'form' + } + ], + responses: { + 200: { + description: 'Volume box', + content: { + 'text/plain': {}, + 'application/octet-stream': {}, + } + }, + }, + } + }, + [`${ServerConfig.apiPrefix}/{source}/{id}/cell/`]: { + get: { + tags: ['General'], + summary: 'Returns (downsampled) volume data for the entire "data cell". For X-ray data, returns unit cell of 2Fo-Fc and Fo-Fc volumes, for EM data returns everything.', + operationId: 'getCell', + parameters: [ + { $ref: '#/components/parameters/source' }, + { $ref: '#/components/parameters/id' }, + { $ref: '#/components/parameters/encoding' }, + { $ref: '#/components/parameters/detail' }, + ], + responses: { + 200: { + description: 'Volume cell', + content: { + 'text/plain': {}, + 'application/octet-stream': {}, + } + }, + }, + } + } + }, + components: { + schemas: { + // TODO how to keep in sync with (or derive from) `api.ts/ExtendedHeader` + info: { + properties: { + formatVersion: { + type: 'string', + description: 'Format version number' + }, + axisOrder: { + type: 'array', + items: { type: 'number' }, + description: 'Axis order from the slowest to fastest moving, same as in CCP4' + }, + origin: { + type: 'array', + items: { type: 'number' }, + description: 'Origin in fractional coordinates, in axisOrder' + }, + dimensions: { + type: 'array', + items: { type: 'number' }, + description: 'Dimensions in fractional coordinates, in axisOrder' + }, + spacegroup: { + properties: { + number: { type: 'number' }, + size: { + type: 'array', + items: { type: 'number' } + }, + angles: { + type: 'array', + items: { type: 'number' } + }, + isPeriodic: { + type: 'boolean', + description: 'Determine if the data should be treated as periodic or not. (e.g. X-ray = periodic, EM = not periodic)' + }, + } + }, + channels: { + type: 'array', + items: { type: 'string' } + }, + valueType: { + type: 'string', + enum: ['float32', 'int16', 'int8'], + description: 'Determines the data type of the values' + }, + blockSize: { + type: 'number', + description: 'The value are stored in blockSize^3 cubes' + }, + sampling: { + type: 'array', + items: { + properties: { + byteOffset: { type: 'number' }, + rate: { + type: 'number', + description: 'How many values along each axis were collapsed into 1' + }, + valuesInfo: { + properties: { + mean: { type: 'number' }, + sigma: { type: 'number' }, + min: { type: 'number' }, + max: { type: 'number' }, + } + }, + sampleCount: { + type: 'array', + items: { type: 'number' }, + description: 'Number of samples along each axis, in axisOrder' + }, + } + } + } + } + } + }, + parameters: { + source: { + name: 'source', + in: 'path', + description: `Specifies the data source (determined by the experiment method). Currently supported sources are: ${sources.join(', ')}.`, + required: true, + schema: { + type: 'string', + enum: sources + }, + style: 'simple' + }, + id: { + name: 'id', + in: 'path', + description: 'Id of the entry. For x-ray, use PDB ID (i.e. 1cbs) and for em use EMDB id (i.e. emd-8116).', + required: true, + schema: { + type: 'string', + }, + style: 'simple' + }, + encoding: { + name: 'encoding', + in: 'query', + description: 'Determines if text based CIF or binary BinaryCIF encoding is used. An optional argument, default is BinaryCIF encoding.', + schema: { + type: 'string', + enum: ['cif', 'bcif'] + }, + style: 'form' + }, + detail: { + name: 'detail', + in: 'query', + description: `Determines the maximum number of voxels the query can return. Possible values are in the range from ${detail(0)} to ${detail(detailMax)}. Default value is 0. Note: different detail levels might lead to the same result.`, + schema: { + type: 'integer', + }, + style: 'form' + } + } + } + } +}