Compare commits

...

14 Commits

Author SHA1 Message Date
dsehnal
3d2e4115ed 1.2.9 2021-01-08 15:17:42 +01:00
dsehnal
dbce1ccb3d alpha-orbitals: ability to clamp volume values 2021-01-08 15:16:08 +01:00
David Sehnal
03aa2be978 Merge pull request #116 from JonStargaryen/modelserverfixes
ModelServer: Add option to download text files
2021-01-07 17:33:38 +01:00
JonStargaryen
8dfc52e1ab cleanup 2021-01-07 14:49:32 +01:00
JonStargaryen
6058179f10 cleanup 2021-01-07 14:36:11 +01:00
JonStargaryen
ea9e25b03c ResultWriterParams 2021-01-07 14:30:01 +01:00
JonStargaryen
d60c3ddce3 handle non-string params faithfully 2021-01-07 14:21:32 +01:00
JonStargaryen
724e79bddf version/changelog 2021-01-07 12:53:44 +01:00
JonStargaryen
2de61215c4 better description 2021-01-07 12:35:16 +01:00
JonStargaryen
e783d9a9f1 Merge remote-tracking branch 'upstream/master' into modelserverfixes 2021-01-07 12:31:26 +01:00
JonStargaryen
e9e971d4f3 lock 2021-01-07 12:31:20 +01:00
JonStargaryen
96dea14cb1 cleanup 2021-01-07 12:26:50 +01:00
JonStargaryen
04fc157340 ModelServer: Save As param 2021-01-07 11:53:24 +01:00
dsehnal
cfc24fa99e SequenceView: fix polymers & everything modes 2021-01-07 11:22:57 +01:00
13 changed files with 104 additions and 23 deletions

4
package-lock.json generated
View File

@@ -1,11 +1,11 @@
{
"name": "molstar",
"version": "1.2.8",
"version": "1.2.9",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "1.2.8",
"version": "1.2.9",
"license": "MIT",
"dependencies": {
"@types/argparse": "^1.0.38",

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "1.2.8",
"version": "1.2.9",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {

View File

@@ -19,6 +19,7 @@ import { Theme } from '../../mol-theme/theme';
import { VolumeRepresentation3DHelpers } from '../../mol-plugin-state/transforms/representation';
import { AlphaOrbital, Basis, CubeGrid } from './data-model';
import { createSphericalCollocationDensityGrid } from './density';
import { Tensor } from '../../mol-math/linear-algebra';
export class BasisAndOrbitals extends PluginStateObject.Create<{ basis: Basis, order: SphericalBasisOrder, orbitals: AlphaOrbital[] }>({ name: 'Basis', typeClass: 'Object' }) { }
@@ -49,9 +50,43 @@ const CreateOrbitalVolumeParamBase = {
{ atomCount: 25, spacing: 0.4 },
{ atomCount: 0, spacing: 0.35 },
]
}),
clampValues: PD.MappedStatic('off', {
off: PD.EmptyGroup(),
on: PD.Group({
sigma: PD.Numeric(8, { min: 1, max: 20 }, { description: 'Clamp values to range [sigma * negIsoValue, sigma * posIsoValue].' })
})
})
};
function clampData(matrix: Tensor.Data, min: number, max: number) {
for (let i = 0, _i = matrix.length; i < _i; i++) {
const v = matrix[i];
if (v < min) matrix[i] = min;
else if (v > max) matrix[i] = max;
}
}
function clampGrid(data: CubeGrid, v: number) {
const grid = data.grid;
const min = (data.isovalues?.negative ?? data.grid.stats.min) * v;
const max = (data.isovalues?.positive ?? data.grid.stats.max) * v;
// clamp values for better direct volume resolution
// current implementation uses Byte array for values
// if this is not enough, update mol* to use float
// textures instead
if (grid.stats.min < min || grid.stats.max > max) {
clampData(data.grid.cells.data, min, max);
if (grid.stats.min < min) {
(grid.stats.min as number) = min;
}
if (grid.stats.max > max) {
(grid.stats.max as number) = max;
}
}
}
export const CreateOrbitalVolume = PluginStateTransform.BuiltIn({
name: 'create-orbital-volume',
display: 'Orbital Volume',
@@ -84,6 +119,10 @@ export const CreateOrbitalVolume = PluginStateTransform.BuiltIn({
_propertyData: Object.create(null),
};
if (params.clampValues?.name === 'on') {
clampGrid(data, params.clampValues?.params?.sigma ?? 8);
}
return new PluginStateObject.Volume.Data(volume, { label: 'Orbital Volume' });
});
}
@@ -112,6 +151,10 @@ export const CreateOrbitalDensityVolume = PluginStateTransform.BuiltIn({
_propertyData: Object.create(null),
};
if (params.clampValues?.name === 'on') {
clampGrid(data, params.clampValues?.params?.sigma ?? 8);
}
return new PluginStateObject.Volume.Data(volume, { label: 'Orbital Volume' });
});
}

View File

@@ -377,9 +377,16 @@ export class SequenceView extends PluginUIComponent<{ defaultMode?: SequenceView
<NonEmptySequenceWrapper>
{sequenceWrappers.map((s, i) => {
return typeof s.wrapper === 'string'
const elem = typeof s.wrapper === 'string'
? <div key={i} className='msp-sequence-wrapper'>{s.wrapper}</div>
: <Sequence key={i} sequenceWrapper={s.wrapper} label={values.mode === 'single' ? void 0 : s.label} />;
: <Sequence key={i} sequenceWrapper={s.wrapper} />;
if (values.mode === 'single') return elem;
return <>
<div className='msp-sequence-chain-label'>{s.label}</div>
{elem}
</>;
})}
</NonEmptySequenceWrapper>
</div>;

View File

@@ -20,7 +20,6 @@ type SequenceProps = {
sequenceWrapper: SequenceWrapper.Any,
sequenceNumberPeriod?: number,
hideSequenceNumbers?: boolean,
label?: string
}
/** Note, if this is changed, the CSS for `msp-sequence-number` needs adjustment too */
@@ -301,7 +300,6 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
onMouseLeave={this.mouseLeave}
ref={this.parentDiv}
>
{this.props.label && <div className='msp-sequence-label'>{this.props.label}</div>}
{elems}
</div>;
}

View File

@@ -61,6 +61,16 @@ $sequence-select-height: 24px;
right: 0;
}
.msp-sequence-chain-label {
margin-left: $control-spacing;
margin-top: $control-spacing;
user-select: none;
color: $sequence-number-color;
font-size: 90%;
line-height: 90%;
padding-left: 0.2em;
}
.msp-sequence-wrapper {
span {
cursor: pointer;
@@ -81,7 +91,7 @@ $sequence-select-height: 24px;
padding-bottom: 1em;
padding-left: 0.2em;
}
.msp-sequence-number {
color: $sequence-number-color;
word-break: keep-all;

View File

@@ -1,3 +1,6 @@
# 0.9.6
* optional download parameter
# 0.9.5
* Support molstar_global_model_transform_info category.

View File

@@ -18,7 +18,8 @@ export interface MultipleQueryEntry<Name extends QueryName = QueryName> {
export interface MultipleQuerySpec {
queries: MultipleQueryEntry[],
encoding?: Encoding,
asTarGz?: boolean
asTarGz?: boolean,
download?: boolean
}
export function getMultiQuerySpecFilename() {

View File

@@ -11,7 +11,7 @@ import * as bodyParser from 'body-parser';
import { ModelServerConfig as Config, ModelServerConfig, mapSourceAndIdToFilename } from '../config';
import { ConsoleLogger } from '../../../mol-util/console-logger';
import { resolveJob } from './query';
import { JobManager, JobEntry } from './jobs';
import { JobManager, JobEntry, ResultWriterParams } from './jobs';
import { UUID } from '../../../mol-util';
import { QueryDefinition, normalizeRestQueryParams, normalizeRestCommonParams, QueryList } from './api';
import { getApiSchema, shortcutIconLink } from './api-schema';
@@ -45,17 +45,18 @@ async function processNextJob() {
}
}
export function createResultWriter(response: express.Response, encoding: string, entryId?: string, queryName?: string) {
const filenameBase = entryId && queryName
? `${entryId}_${splitCamelCase(queryName.replace(/\s/g, '_'), '-').toLowerCase()}`
export function createResultWriter(response: express.Response, params: ResultWriterParams) {
const filenameBase = params.entryId && params.queryName
? `${params.entryId}_${splitCamelCase(params.queryName.replace(/\s/g, '_'), '-').toLowerCase()}`
: `result`;
return new SimpleResponseResultWriter(`${filenameBase}.${encoding}`, response, encoding === 'bcif');
return new SimpleResponseResultWriter(`${filenameBase}.${params.encoding}`, response, params.encoding === 'bcif', params.download);
}
function mapQuery(app: express.Express, queryName: string, queryDefinition: QueryDefinition) {
function createJob(queryParams: any, req: express.Request, res: express.Response) {
const entryId = req.params.id;
const commonParams = normalizeRestCommonParams(req.query);
const resultWriterParams = { encoding: commonParams.encoding!, download: !!commonParams.download, entryId, queryName };
const jobId = JobManager.add({
entries: [JobEntry({
sourceId: commonParams.data_source || ModelServerConfig.defaultSource,
@@ -66,7 +67,7 @@ function mapQuery(app: express.Express, queryName: string, queryDefinition: Quer
copyAllCategories: !!commonParams.copy_all_categories,
transform: commonParams.transform
})],
writer: createResultWriter(res, commonParams.encoding!, entryId, queryName),
writer: createResultWriter(res, resultWriterParams),
options: { binary: commonParams.encoding === 'bcif', encoding: commonParams.encoding }
});
responseMap.set(jobId, res);
@@ -122,7 +123,7 @@ function serveStatic(req: express.Request, res: express.Response) {
function createMultiJob(spec: MultipleQuerySpec, res: express.Response) {
const writer = spec.asTarGz
? new TarballResponseResultWriter(getMultiQuerySpecFilename(), res)
: createResultWriter(res, spec.encoding!);
: createResultWriter(res, { encoding: spec.encoding!, download: !!spec.download });
if (spec.queries.length > ModelServerConfig.maxQueryManyQueries) {
writer.doError(400, `query-many queries limit (${ModelServerConfig.maxQueryManyQueries}) exceeded.`);

View File

@@ -48,7 +48,8 @@ export const CommonQueryParamsInfo: QueryParamInfo[] = [
{ name: 'encoding', type: QueryParamType.String, defaultValue: 'cif', description: `Determines the output encoding (text based 'CIF' or binary 'BCIF'). Ligands can also be exported as 'SDF', 'MOL', or 'MOL2'.`, supportedValues: ['cif', 'bcif', 'sdf', 'mol', 'mol2'] },
{ name: 'copy_all_categories', type: QueryParamType.Boolean, defaultValue: false, description: 'If true, copy all categories from the input file.' },
{ name: 'data_source', type: QueryParamType.String, defaultValue: '', description: 'Allows to control how the provided data source ID maps to input file (as specified by the server instance config).' },
{ name: 'transform', type: QueryParamType.String, description: `Transformation to apply to coordinates in '_atom_site'. Accepts a 4x4 transformation matrix, provided as array of 16 float values.` }
{ name: 'transform', type: QueryParamType.String, description: `Transformation to apply to coordinates in '_atom_site'. Accepts a 4x4 transformation matrix, provided as array of 16 float values.` },
{ name: 'download', type: QueryParamType.Boolean, defaultValue: false, description: 'If true, browser will download text files.' }
];
export type Encoding = 'cif' | 'bcif' | 'sdf' | 'mol' | 'mol2';
@@ -57,7 +58,8 @@ export interface CommonQueryParamsInfo {
encoding?: Encoding,
copy_all_categories?: boolean
data_source?: string,
transform?: Mat4
transform?: Mat4,
download?: boolean
}
export const AtomSiteSchemaElement = {
@@ -290,12 +292,20 @@ export function normalizeRestCommonParams(params: any): CommonQueryParamsInfo {
return {
model_nums: params.model_nums ? ('' + params.model_nums).split(',').map(n => n.trim()).filter(n => !!n).map(n => +n) : void 0,
data_source: params.data_source,
copy_all_categories: Boolean(params.copy_all_categories),
copy_all_categories: isTrue(params.copy_all_categories),
encoding: mapEncoding(('' + params.encoding).toLocaleLowerCase()),
transform: params.transform ? ('' + params.transform).split(',').map(n => n.trim()).map(n => +n) as Mat4 : Mat4.identity()
transform: params.transform ? ('' + params.transform).split(',').map(n => n.trim()).map(n => +n) as Mat4 : Mat4.identity(),
download: isTrue(params.download)
};
}
function isTrue(val: any): boolean {
const b = Boolean(val);
if (!b) return false;
if (typeof val === 'string') return val !== '0' && val.toLowerCase() !== 'false';
return b;
}
function mapEncoding(value: string) {
switch (value) {
case 'bcif':

View File

@@ -56,6 +56,13 @@ interface JobEntryDefinition<Name extends QueryName> {
transform?: Mat4
}
export interface ResultWriterParams {
encoding: Encoding,
download: boolean,
entryId?: string,
queryName?: string
}
export function JobEntry<Name extends QueryName>(definition: JobEntryDefinition<Name>): JobEntry {
const queryDefinition = getQueryByName(definition.queryName);
if (!queryDefinition) throw new Error(`Query '${definition.queryName}' is not supported.`);

View File

@@ -48,10 +48,11 @@ export class SimpleResponseResultWriter implements WebResutlWriter {
this.headerWritten = true;
this.res.writeHead(200, {
// TODO there seems to be a bug in swagger-ui - front-end will freeze for cif delivered as text/plain (forcing binary is a hack to circumvent this)
'Content-Type': this.isBinary ? 'application/octet-stream' : 'text/plain; charset=utf-8',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'X-Requested-With',
'Content-Disposition': `inline; filename="${this.fn}"`
'Content-Disposition': `${this.isDownload ? 'attachment' : 'inline'}; filename="${this.fn}"`
});
}
@@ -71,7 +72,7 @@ export class SimpleResponseResultWriter implements WebResutlWriter {
this.ended = true;
}
constructor(private fn: string, private res: express.Response, private isBinary: boolean) {
constructor(private fn: string, private res: express.Response, private isBinary: boolean, private isDownload: boolean) {
}
}

View File

@@ -4,4 +4,4 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
export default '0.9.5';
export default '0.9.6';