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:
Severiano Badajoz
2020-03-11 12:35:42 -07:00
committed by GitHub
parent 9089fc98f2
commit 7cd9a0032a
+104 -71
View File
@@ -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,