vitessce
Version:
Vitessce app and React component library
455 lines (401 loc) • 14 kB
JavaScript
/* eslint-disable camelcase */
import uuidv4 from 'uuid/v4';
import cloneDeep from 'lodash/cloneDeep';
import { getNextScope, capitalize } from '../utils';
/**
* A helper function for the `upgrade()` function,
* which helps convert `props.view` (for scatterplot and spatial),
* into new coordination scopes, setting their values
* in the coordination space and returning the new scope mappings.
* This function does mutate the `coordinationSpace` parameter.
* @param {string} prefix The coordination type prefix,
* either 'embedding' or 'spatial'.
* @param {object} view The view prop object containing
* the properties `.target` and `.zoom`.
* @param {object} coordinationSpace The coordination space.
* @returns {object} The new coordination scope names.
*/
function upgradeReplaceViewProp(prefix, view, coordinationSpace) {
const prevZScopes = Object.keys(coordinationSpace[`${prefix}Zoom`]);
const prevTXScopes = Object.keys(coordinationSpace[`${prefix}TargetX`]);
const prevTYScopes = Object.keys(coordinationSpace[`${prefix}TargetY`]);
const nextZScope = getNextScope(prevZScopes);
const nextTXScope = getNextScope(prevTXScopes);
const nextTYScope = getNextScope(prevTYScopes);
const { zoom, target: [targetX, targetY] } = view;
// eslint-disable-next-line no-param-reassign
coordinationSpace[`${prefix}Zoom`][nextZScope] = zoom;
// eslint-disable-next-line no-param-reassign
coordinationSpace[`${prefix}TargetX`][nextTXScope] = targetX;
// eslint-disable-next-line no-param-reassign
coordinationSpace[`${prefix}TargetY`][nextTYScope] = targetY;
return {
[`${prefix}Zoom`]: nextZScope,
[`${prefix}TargetX`]: nextTXScope,
[`${prefix}TargetY`]: nextTYScope,
};
}
/**
* Convert an older view config to a newer view config.
* @param {object} config A v0.1.0 "legacy" view config.
* @returns {object} A v1.0.0 "upgraded" view config.
*/
export function upgradeFrom0_1_0(config, datasetUid = null) {
const coordinationSpace = {
embeddingType: {},
embeddingZoom: {},
embeddingTargetX: {},
embeddingTargetY: {},
spatialZoom: {},
spatialTargetX: {},
spatialTargetY: {},
};
const layout = [];
config.staticLayout.forEach((componentDef) => {
let newComponentDef = {
...componentDef,
coordinationScopes: {},
};
if (componentDef.component === 'scatterplot') {
// Need to set up the coordinationSpace
// with embeddingType to replace scatterplot
// component prop "mapping".
if (componentDef.props.mapping) {
coordinationSpace.embeddingType[componentDef.props.mapping] = componentDef.props.mapping;
newComponentDef = {
...newComponentDef,
coordinationScopes: {
...newComponentDef.coordinationScopes,
embeddingType: componentDef.props.mapping,
},
};
}
// Need to set up the coordinationSpace
// with embeddingZoom / embeddingTargetX/Y to replace scatterplot
// component prop "view" ({ zoom, target }).
if (componentDef.props.view) {
// Note that the below function does mutate the coordinationSpace param.
const newScopeValues = upgradeReplaceViewProp(
'embedding', componentDef.props.view, coordinationSpace,
);
newComponentDef = {
...newComponentDef,
coordinationScopes: {
...newComponentDef.coordinationScopes,
...newScopeValues,
},
};
}
}
if (componentDef.component === 'spatial') {
// Need to set up the coordinationSpace
// with spatialZoom / spatialTargetX/Y to replace spatial
// component prop "view" ({ zoom, target }).
if (componentDef?.props?.view) {
// Note that the below function does mutate the coordinationSpace param.
const newScopeValues = upgradeReplaceViewProp(
'spatial', componentDef.props.view, coordinationSpace,
);
newComponentDef = {
...newComponentDef,
coordinationScopes: {
...newComponentDef.coordinationScopes,
...newScopeValues,
},
};
}
}
layout.push(newComponentDef);
});
// Use a random dataset ID when initializing automatically,
// so that it changes with each new v0.1.0 view config.
// However, check if the `datasetUid` parameter was passed,
// which allows for unit testing.
const newDatasetUid = datasetUid || uuidv4();
return {
version: '1.0.1',
name: config.name,
description: config.description,
public: config.public,
datasets: [
{
uid: newDatasetUid,
name: newDatasetUid,
files: config.layers.map(layer => ({
type: layer.type.toLowerCase(),
fileType: layer.fileType,
url: layer.url,
})),
},
],
initStrategy: 'auto',
coordinationSpace,
layout,
};
}
export function upgradeFrom1_0_0(config) {
const coordinationSpace = { ...config.coordinationSpace };
function replaceLayerType(layerType) {
// Layer type could be one of a few things, bitmask or raster at the moment.
const isRaster = layerType === 'raster';
coordinationSpace[`spatial${capitalize(layerType)}Layer${isRaster ? 's' : ''}`] = {};
Object.entries(coordinationSpace.spatialLayers).forEach(([scope, layers]) => {
if (Array.isArray(layers) && layers.find(layer => layer.type === layerType)) {
const typedLayers = layers
.filter(layer => layer.type === layerType)
.map((layer) => {
const newLayer = { ...layer };
delete newLayer.type;
return newLayer;
});
coordinationSpace[`spatial${capitalize(layerType)}Layer${isRaster ? 's' : ''}`][scope] = isRaster ? typedLayers : typedLayers[0];
} else {
coordinationSpace[`spatial${capitalize(layerType)}Layer${isRaster ? 's' : ''}`][scope] = null;
}
});
}
if (coordinationSpace.spatialLayers) {
replaceLayerType('raster');
replaceLayerType('cells');
replaceLayerType('molecules');
replaceLayerType('neighborhoods');
delete coordinationSpace.spatialLayers;
}
const layout = config.layout.map((component) => {
const newComponent = { ...component };
function replaceCoordinationScope(layerType) {
const isRaster = layerType === 'raster';
if (
['spatial', 'layerController'].includes(newComponent.component)
|| (newComponent.component === 'description' && isRaster)
) {
newComponent.coordinationScopes[`spatial${capitalize(layerType)}Layer${isRaster ? 's' : ''}`] = newComponent
.coordinationScopes.spatialLayers;
}
}
if (newComponent.coordinationScopes && newComponent.coordinationScopes.spatialLayers) {
replaceCoordinationScope('raster');
replaceCoordinationScope('cells');
replaceCoordinationScope('molecules');
replaceCoordinationScope('neighborhoods');
delete newComponent.coordinationScopes.spatialLayers;
}
return newComponent;
});
return {
...config,
coordinationSpace,
layout,
version: '1.0.1',
};
}
export function upgradeFrom1_0_1(config) {
// Need to add the globalDisable3d prop to any layer controller views,
// to match the previous lack of 3D auto-detection behavior.
const layout = config.layout.map((component) => {
const newComponent = { ...component };
if (newComponent.component === 'layerController') {
newComponent.props = {
...newComponent.props,
globalDisable3d: true,
};
}
return newComponent;
});
// Enforce bitmask or raster as spatial raster layer type, defaulting
// to raster layer if it is not one of bitmask or raster from the old config.
const newConfig = cloneDeep(config);
Object.keys((newConfig?.coordinationSpace?.spatialRasterLayers || {})).forEach((key) => {
if (newConfig.coordinationSpace.spatialRasterLayers[key]) {
newConfig.coordinationSpace.spatialRasterLayers[key].forEach((layer, index) => {
newConfig.coordinationSpace.spatialRasterLayers[key][index].type = ['bitmask', 'raster'].includes(layer.type) ? layer.type : 'raster';
});
}
});
return {
...newConfig,
layout,
version: '1.0.2',
};
}
export function upgradeFrom1_0_2(config) {
// Need to add the globalDisable3d prop to any layer controller views,
// to match the previous lack of 3D auto-detection behavior.
const layout = config.layout.map((component) => {
const newComponent = { ...component };
if (newComponent.component === 'layerController') {
newComponent.props = {
...newComponent.props,
disableChannelsIfRgbDetected: true,
};
}
return newComponent;
});
// Enforce bitmask or raster as spatial raster layer type, defaulting
// to raster layer if it is not one of bitmask or raster from the old config.
const newConfig = cloneDeep(config);
return {
...newConfig,
layout,
version: '1.0.3',
};
}
export function upgradeFrom1_0_3(config) {
const newConfig = cloneDeep(config);
return {
...newConfig,
version: '1.0.4',
};
}
// Added in version 1.0.5:
// - Support for an array of strings in the setName property within options array items
// for the anndata-cell-sets.zarr file type.
export function upgradeFrom1_0_4(config) {
const newConfig = cloneDeep(config);
return {
...newConfig,
version: '1.0.5',
};
}
// Added in version 1.0.6:
// - Support for the scoreName property within options array items
// for the anndata-cell-sets.zarr file type.
export function upgradeFrom1_0_5(config) {
const newConfig = cloneDeep(config);
return {
...newConfig,
version: '1.0.6',
};
}
// Added in version 1.0.7:
// - Support for aliasing the gene identifiers using a different var dataframe column
// via a new `geneAlias` option for the `anndata-expression-matrix.zarr` fileType.
export function upgradeFrom1_0_6(config) {
const newConfig = cloneDeep(config);
return {
...newConfig,
version: '1.0.7',
};
}
// Added in version 1.0.8:
// - Support for multiple `dataset` coordination scopes and
// dataset-specific coordination scope mappings for all
// other coordination types.
export function upgradeFrom1_0_7(config) {
const newConfig = cloneDeep(config);
return {
...newConfig,
version: '1.0.8',
};
}
// Added in version 1.0.9:
// - Support for plugin coordination types.
export function upgradeFrom1_0_8(config) {
const newConfig = cloneDeep(config);
return {
...newConfig,
version: '1.0.9',
};
}
// Added in version 1.0.10:
// - Support for the optional 'uid' property for views.
export function upgradeFrom1_0_9(config) {
const newConfig = cloneDeep(config);
return {
...newConfig,
version: '1.0.10',
};
}
// Added in version 1.0.11:
// - Changes to spatial layer coordination type names.
// - Cell -> Obs, Gene -> Feature in coordination type names.
export function upgradeFrom1_0_10(config) {
const coordinationSpace = { ...config.coordinationSpace };
const scopeAnalogies = {
// Spatial layer types
spatialRasterLayers: 'spatialImageLayer',
spatialCellsLayer: 'spatialSegmentationLayer',
spatialMoleculesLayer: 'spatialPointLayer',
spatialNeighborhoodsLayer: 'spatialNeighborhoodLayer',
// Other types
cellFilter: 'obsFilter',
cellHighlight: 'obsHighlight',
cellSelection: 'obsSelection',
cellSetSelection: 'obsSetSelection',
cellSetHighlight: 'obsSetHighlight',
cellSetColor: 'obsSetColor',
geneFilter: 'featureFilter',
geneHighlight: 'featureHighlight',
geneSelection: 'featureSelection',
geneExpressionColormap: 'featureValueColormap',
geneExpressionColormapRange: 'featureValueColormapRange',
cellColorEncoding: 'obsColorEncoding',
additionalCellSets: 'additionalObsSets',
embeddingCellSetPolygonsVisible: 'embeddingObsSetPolygonsVisible',
embeddingCellSetLabelsVisible: 'embeddingObsSetLabelsVisible',
embeddingCellSetLabelSize: 'embeddingObsSetLabelSize',
embeddingCellRadius: 'embeddingObsRadius',
embeddingCellRadiusMode: 'embeddingObsRadiusMode',
embeddingCellOpacity: 'embeddingObsOpacity',
embeddingCellOpacityMode: 'embeddingObsOpacityMode',
};
Object.entries(scopeAnalogies).forEach(([oldKey, newKey]) => {
if (coordinationSpace[oldKey]) {
coordinationSpace[newKey] = coordinationSpace[oldKey];
delete coordinationSpace[oldKey];
}
});
const layout = config.layout.map((component) => {
const newComponent = { ...component };
const { coordinationScopes = {} } = newComponent;
Object.entries(scopeAnalogies).forEach(([oldKey, newKey]) => {
if (coordinationScopes[oldKey]) {
coordinationScopes[newKey] = coordinationScopes[oldKey];
delete coordinationScopes[oldKey];
}
});
return {
...newComponent,
coordinationScopes,
};
});
return {
...config,
coordinationSpace,
layout,
version: '1.0.11',
};
}
// Added in version 1.0.12:
// - Added a fileType-to-dataType mapping
// so that datasets[].files[].type is no longer required.
export function upgradeFrom1_0_11(config) {
const newConfig = cloneDeep(config);
return {
...newConfig,
version: '1.0.12',
};
}
// Added in version 1.0.13:
// - Adds the property `coordinationValues` for
// view config file definitions but is not yet
// used to do file matching/lookups.
export function upgradeFrom1_0_12(config) {
const newConfig = cloneDeep(config);
return {
...newConfig,
version: '1.0.13',
};
}
// Added in version 1.0.14:
// - Adds the coordination types
// gatingFeatureSelectionX,
// gatingFeatureSelectionY,
// featureValueTransformCoefficient.
export function upgradeFrom1_0_13(config) {
const newConfig = cloneDeep(config);
return {
...newConfig,
version: '1.0.14',
};
}