vitessce
Version:
Vitessce app and React component library
115 lines (105 loc) • 3.69 kB
JavaScript
/* eslint-disable no-plusplus */
// Adapted from https://github.com/feross/fromentries/blob/29b52a850bb3a47c390937631c2638edf3443942/index.js
export function fromEntries(iterable) {
return [...iterable]
.reduce((obj, { 0: key, 1: val }) => Object.assign(obj, { [key]: val }), {});
}
/**
* Select between a singular and plural version of a word,
* based on an item count.
* @param {string} singular The singular version of the word.
* @param {string} plural The plural version of the word.
* @param {number} count The number of items.
* @returns {string} Singular if count is one, else plural.
*/
export function pluralize(singular, plural, count) {
return (count === 1 ? singular : plural);
}
/**
* Capitalize a the first letter of a string.
* @param {string} word A string to capitalize.
* @returns {string} The word parameter with the first letter capitalized.
*/
export function capitalize(word) {
return word.charAt(0).toUpperCase() + word.slice(1);
}
/**
* Get the longest string in the array of strings.
* @param {string[]} strings The array of strings.
* @returns The longest string.
*/
export function getLongestString(strings) {
return strings.reduce(
(prevLongest, currentValue) => (
prevLongest.length > currentValue.length ? prevLongest : currentValue
),
);
}
/**
* Generate a new scope name which does not
* conflict / overlap with a previous scope name.
* Really these just need to be unique within the coordination object.
* So in theory they could be String(Math.random()) or uuidv4() or something.
* However it may be good to make them more human-readable and memorable
* since eventually we will want to expose a UI to update the coordination.
* @param {string[]} prevScopes Previous scope names.
* @returns {string} The new scope name.
*/
export function getNextScope(prevScopes) {
// Keep an ordered list of valid characters.
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
// Store the value of the next character for each position
// in the new string.
// For example, [0] -> "A", [1] -> "B", [0, 1] -> "AB"
const nextCharIndices = [0];
// Generate a new scope name,
// potentially conflicting with an existing name.
// Reference: https://stackoverflow.com/a/12504061
function next() {
const r = [];
nextCharIndices.forEach((charIndex) => {
r.unshift(chars[charIndex]);
});
let increment = true;
for (let i = 0; i < nextCharIndices.length; i++) {
const val = ++nextCharIndices[i];
if (val >= chars.length) {
nextCharIndices[i] = 0;
} else {
increment = false;
break;
}
}
if (increment) {
nextCharIndices.push(0);
}
return r.join('');
}
let nextScope;
do {
nextScope = next();
} while (prevScopes.includes(nextScope));
return nextScope;
}
/**
* Get a representative PixelSource from a loader object returned from
* the Vitessce imaging loaders
* @param {object} loader { data: (PixelSource[]|PixelSource), metadata, channels } object
* @param {number=} level Level of the multiscale loader from which to get a PixelSource
* @returns {object} PixelSource object
*/
export function getSourceFromLoader(loader, level) {
const { data } = loader;
const source = Array.isArray(data) ? data[(level || data.length - 1)] : data;
return source;
}
/*
* Helper method to determine whether pixel data is interleaved and rgb or not.
* @param {object} loader
*/
export function isRgb(loader) {
const source = getSourceFromLoader(loader);
const { shape, dtype, labels } = source;
const channelSize = shape[labels.indexOf('c')];
return (channelSize === 3) && dtype === 'Uint8';
}