mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-24 06:18:13 +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
|
||||
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
|
||||
fetches each cell's coordinates grouping by category value.
|
||||
/*
|
||||
Generates a mapping of categorical values to data needed to calculate centroids
|
||||
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 = (
|
||||
@@ -21,85 +100,39 @@ const calcMedianCentroid = (
|
||||
categoricalSelection,
|
||||
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();
|
||||
const layoutYArray = obsLayout.col(layoutDimNames[1]).asArray();
|
||||
// categoricalValue => [medianXCoordinate, medianYCoordinate]
|
||||
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
|
||||
coordinates.forEach((value, key) => {
|
||||
// If there are coordinates for this cateogrical value,
|
||||
dataMap.forEach((value, key) => {
|
||||
// If there are coordinates for this categorical value,
|
||||
// and there is a finite coordinate for the category value
|
||||
if (value[2].length > 0 && value[3].length > 0 && value[0]) {
|
||||
// Find the median x and y coordinate
|
||||
// and insert them into the first two indices
|
||||
value[0] = quantile([0.5], value[2])[0];
|
||||
value[1] = quantile([0.5], value[3])[0];
|
||||
// Remove the last two elements (where the arrays of coordinates were)
|
||||
value.pop();
|
||||
value.pop();
|
||||
} else {
|
||||
// remove the entry if not
|
||||
coordinates.delete(key);
|
||||
if (value.length > 0 && value.hasFinite) {
|
||||
const calculatedCoordinates = [];
|
||||
|
||||
// Find and store the median x and y coordinate
|
||||
calculatedCoordinates[0] = quantile([0.5], value.xCoordinates)[0];
|
||||
calculatedCoordinates[1] = quantile([0.5], value.yCoordinates)[0];
|
||||
|
||||
coordinates.set(key, calculatedCoordinates);
|
||||
}
|
||||
});
|
||||
|
||||
// return the map: categoricalValue -> [medianXCoordinate, medianYCoordinate]
|
||||
return coordinates;
|
||||
};
|
||||
|
||||
// A simple function to hash the parameters
|
||||
// (not 100% on world hash, Bruce will have to check this one out)
|
||||
const hashMedianCentroid = (
|
||||
obsAnnotations,
|
||||
obsLayout,
|
||||
|
||||
Reference in New Issue
Block a user