mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-07 23:58:11 +08:00
optimize centroid util (#1147)
* move unvarying evaluations outside of loop * refactoring * remove perf checks * commenting * minor fix + renaming * small fix * benchmarking * merge master * Revert pref checks * renaming and comment * remove redundant sets in Map
This commit is contained in:
+104
-71
@@ -4,13 +4,92 @@ import { unassignedCategoryLabel } from "../globals";
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
Centroid coordinate calculation
|
Centroid coordinate calculation
|
||||||
|
Calculates centroids for displaying
|
||||||
|
In the case that a category is truncated, the truncated labels will not have
|
||||||
|
centroids calculated
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
calcMedianCentroid goes through a given metadata category
|
Generates a mapping of categorical values to data needed to calculate centroids
|
||||||
fetches each cell's coordinates grouping by category value.
|
categoricalValue -> {
|
||||||
|
length: int,
|
||||||
|
holdsFinite: Boolean,
|
||||||
|
xCoordinates: Float32Array,
|
||||||
|
yCoordinates: Float32Array
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
const getCoordinatesByCategoricalValues = (
|
||||||
|
obsAnnotations,
|
||||||
|
obsLayout,
|
||||||
|
categoryName,
|
||||||
|
layoutDimNames,
|
||||||
|
categoricalSelection,
|
||||||
|
schemaObsByName
|
||||||
|
) => {
|
||||||
|
const coordsByCategoryLabel = new Map();
|
||||||
|
|
||||||
It then calculates the median value and puts that in the array
|
const categoryArray = obsAnnotations.col(categoryName).asArray();
|
||||||
|
|
||||||
|
const layoutXArray = obsLayout.col(layoutDimNames[0]).asArray();
|
||||||
|
const layoutYArray = obsLayout.col(layoutDimNames[1]).asArray();
|
||||||
|
|
||||||
|
const { categoryValueIndices, categoryValueCounts } = categoricalSelection[
|
||||||
|
categoryName
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check to see if the current category is a user created annotation
|
||||||
|
const isUserAnno = schemaObsByName[categoryName].writable;
|
||||||
|
|
||||||
|
// Iterate over all cells
|
||||||
|
for (let i = 0, len = categoryArray.length; i < len; i += 1) {
|
||||||
|
// Fetch the categorical value of the current cell
|
||||||
|
const categoryValue = categoryArray[i];
|
||||||
|
|
||||||
|
// Get the index of the categoryValue within the category
|
||||||
|
const categoryValueIndex = categoryValueIndices.get(categoryValue);
|
||||||
|
|
||||||
|
// If the category is truncated and this value is removed,
|
||||||
|
// it will not be assigned a category value and will not be
|
||||||
|
// labeled on the graph
|
||||||
|
// If the user created this category,
|
||||||
|
// do not create a label for the `unassigned` value
|
||||||
|
if (
|
||||||
|
categoryValueIndex !== undefined &&
|
||||||
|
!(isUserAnno && categoryValue === unassignedCategoryLabel)
|
||||||
|
) {
|
||||||
|
// Create/fetch the scratchpad value
|
||||||
|
let coords = coordsByCategoryLabel.get(categoryValue);
|
||||||
|
if (coords === undefined) {
|
||||||
|
// Get the number of cells which are in the categorical value
|
||||||
|
const numInCategoricalValue = categoryValueCounts[categoryValueIndex];
|
||||||
|
coords = {
|
||||||
|
hasFinite: false,
|
||||||
|
xCoordinates: new Float32Array(numInCategoricalValue),
|
||||||
|
yCoordinates: new Float32Array(numInCategoricalValue),
|
||||||
|
length: 0
|
||||||
|
};
|
||||||
|
coordsByCategoryLabel.set(categoryValue, coords);
|
||||||
|
}
|
||||||
|
|
||||||
|
coords.hasFinite =
|
||||||
|
coords.hasFinite ||
|
||||||
|
(Number.isFinite(layoutXArray[i]) && Number.isFinite(layoutYArray[i]));
|
||||||
|
|
||||||
|
const coordinatesLength = coords.length;
|
||||||
|
|
||||||
|
coords.xCoordinates[coordinatesLength] = layoutXArray[i];
|
||||||
|
coords.yCoordinates[coordinatesLength] = layoutYArray[i];
|
||||||
|
|
||||||
|
coords.length = coordinatesLength + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return coordsByCategoryLabel;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
calcMedianCentroid calculates the median coordinates for categorical values in a given metadata field
|
||||||
|
|
||||||
|
categoricalValue -> [x-Coordinate, y-Coordinate]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const calcMedianCentroid = (
|
const calcMedianCentroid = (
|
||||||
@@ -21,85 +100,39 @@ const calcMedianCentroid = (
|
|||||||
categoricalSelection,
|
categoricalSelection,
|
||||||
schemaObsByName
|
schemaObsByName
|
||||||
) => {
|
) => {
|
||||||
const categoryArray = obsAnnotations.col(categoryName).asArray();
|
// generate a map describing the coordinates for each value within the given category
|
||||||
|
const dataMap = getCoordinatesByCategoricalValues(
|
||||||
|
obsAnnotations,
|
||||||
|
obsLayout,
|
||||||
|
categoryName,
|
||||||
|
layoutDimNames,
|
||||||
|
categoricalSelection,
|
||||||
|
schemaObsByName
|
||||||
|
);
|
||||||
|
|
||||||
const layoutXArray = obsLayout.col(layoutDimNames[0]).asArray();
|
// categoricalValue => [medianXCoordinate, medianYCoordinate]
|
||||||
const layoutYArray = obsLayout.col(layoutDimNames[1]).asArray();
|
|
||||||
const coordinates = new Map();
|
const coordinates = new Map();
|
||||||
|
|
||||||
// Iterate over all the cells in the category
|
|
||||||
for (let i = 0, len = categoryArray.length; i < len; i += 1) {
|
|
||||||
const categoryValue = categoryArray[i];
|
|
||||||
|
|
||||||
// Get the index of the categoryValue within the category
|
|
||||||
// If the category is truncated and this value is removed,
|
|
||||||
// it will not be assigned a category value and will not be
|
|
||||||
// labeled on the graph
|
|
||||||
const categoryValueIndex = categoricalSelection[
|
|
||||||
categoryName
|
|
||||||
].categoryValueIndices.get(categoryValue);
|
|
||||||
|
|
||||||
// Check to see if the current category is a user created annotation
|
|
||||||
// if the user created this category, do not create a label for the `unassigned` value
|
|
||||||
const isUserAnno = schemaObsByName[categoryName].writable;
|
|
||||||
|
|
||||||
if (
|
|
||||||
categoryValueIndex !== undefined &&
|
|
||||||
!(isUserAnno && categoryValue === unassignedCategoryLabel)
|
|
||||||
) {
|
|
||||||
// Get the number of cells which are in the category value
|
|
||||||
const numInCategoryValue =
|
|
||||||
categoricalSelection[categoryName].categoryValueCounts[
|
|
||||||
categoryValueIndex
|
|
||||||
];
|
|
||||||
|
|
||||||
// Create/fetch the valueArray,
|
|
||||||
// which is what the key points to in the `coordinates` hashmap
|
|
||||||
const valueArray = coordinates.get(categoryValue) || [
|
|
||||||
false, // hasFinite
|
|
||||||
0, // index
|
|
||||||
new Float32Array(numInCategoryValue), // x coordinates
|
|
||||||
new Float32Array(numInCategoryValue) // y coordinates
|
|
||||||
];
|
|
||||||
const index = valueArray[1];
|
|
||||||
let hasFinite = valueArray[0];
|
|
||||||
|
|
||||||
hasFinite =
|
|
||||||
hasFinite ||
|
|
||||||
(Number.isFinite(layoutXArray[i]) && Number.isFinite(layoutYArray[i]));
|
|
||||||
|
|
||||||
valueArray[0] = hasFinite;
|
|
||||||
valueArray[1] = index + 1;
|
|
||||||
valueArray[2][index] = layoutXArray[i];
|
|
||||||
valueArray[3][index] = layoutYArray[i];
|
|
||||||
|
|
||||||
coordinates.set(categoryValue, valueArray);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over the recently created map
|
// Iterate over the recently created map
|
||||||
coordinates.forEach((value, key) => {
|
dataMap.forEach((value, key) => {
|
||||||
// If there are coordinates for this cateogrical value,
|
// If there are coordinates for this categorical value,
|
||||||
// and there is a finite coordinate for the category value
|
// and there is a finite coordinate for the category value
|
||||||
if (value[2].length > 0 && value[3].length > 0 && value[0]) {
|
if (value.length > 0 && value.hasFinite) {
|
||||||
// Find the median x and y coordinate
|
const calculatedCoordinates = [];
|
||||||
// and insert them into the first two indices
|
|
||||||
value[0] = quantile([0.5], value[2])[0];
|
// Find and store the median x and y coordinate
|
||||||
value[1] = quantile([0.5], value[3])[0];
|
calculatedCoordinates[0] = quantile([0.5], value.xCoordinates)[0];
|
||||||
// Remove the last two elements (where the arrays of coordinates were)
|
calculatedCoordinates[1] = quantile([0.5], value.yCoordinates)[0];
|
||||||
value.pop();
|
|
||||||
value.pop();
|
coordinates.set(key, calculatedCoordinates);
|
||||||
} else {
|
|
||||||
// remove the entry if not
|
|
||||||
coordinates.delete(key);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// return the map: categoricalValue -> [medianXCoordinate, medianYCoordinate]
|
// return the map: categoricalValue -> [medianXCoordinate, medianYCoordinate]
|
||||||
return coordinates;
|
return coordinates;
|
||||||
};
|
};
|
||||||
|
|
||||||
// A simple function to hash the parameters
|
// A simple function to hash the parameters
|
||||||
// (not 100% on world hash, Bruce will have to check this one out)
|
|
||||||
const hashMedianCentroid = (
|
const hashMedianCentroid = (
|
||||||
obsAnnotations,
|
obsAnnotations,
|
||||||
obsLayout,
|
obsLayout,
|
||||||
|
|||||||
Reference in New Issue
Block a user