mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-21 05:38:12 +08:00
* first flatbuffer schema * do not lint auto-generated files * add flatbuffers package * add flatbuffer module * wire up /data/X/T route * use flatbuffers for matrix data fetc * clarity and comments * add flatbuffer layout route * clean up obsolete code * fix tests * move flake8 config to setup.cfg * add comments * lint * rework layout routes for fbs * add more type support to fbs * lint * add flatbuffer support for annotations * function name improvements * fix botched merge with master * remove unused import * route cleanup for flatbuffers * rename function for clarity * add missing globals to Jest tests * fix client JS tests * fix routes for Python tests * comments for clarity * non-finite floating point hardening * more non-finite number handling * lint * fix tests for summarizeAnnotations * harden diffexp calculation against FP errors * cleanup unused code * lint * add encoding tests for flatbuffers * application type specified as strings * fix spelling error * improve variable names * add note about documentation gap * rename FBS DataFrame to Matrix
259 lines
7.7 KiB
JavaScript
259 lines
7.7 KiB
JavaScript
import _ from "lodash";
|
|
import * as Universe from "../../../src/util/stateManager/universe";
|
|
import * as World from "../../../src/util/stateManager/world";
|
|
import Crossfilter from "../../../src/util/typedCrossfilter";
|
|
import * as REST from "./sampleResponses";
|
|
import {
|
|
obsAnnoDimensionName,
|
|
layoutDimensionName
|
|
} from "../../../src/util/nameCreators";
|
|
import * as kvCache from "../../../src/util/stateManager/keyvalcache";
|
|
|
|
/*
|
|
Helper - creates universe, world, corssfilter and dimensionMap from
|
|
the default REST test response.
|
|
*/
|
|
const defaultBigBang = () => {
|
|
/* create unverse, world, crossfilter and dimensionMap */
|
|
/* create universe */
|
|
const universe = Universe.createUniverseFromRestV02Response(
|
|
REST.config,
|
|
REST.schema,
|
|
REST.annotationsObs,
|
|
REST.annotationsVar,
|
|
REST.layoutObs
|
|
);
|
|
/* create world */
|
|
const world = World.createWorldFromEntireUniverse(universe);
|
|
/* create crossfilter */
|
|
const crossfilter = Crossfilter(world.obsAnnotations);
|
|
/* create dimension map */
|
|
const dimensionMap = World.createObsDimensionMap(crossfilter, world);
|
|
|
|
return {
|
|
universe,
|
|
world,
|
|
crossfilter,
|
|
dimensionMap
|
|
};
|
|
};
|
|
|
|
describe("createWorldFromEntireUniverse", () => {
|
|
test("create from REST sample", () => {
|
|
const universe = Universe.createUniverseFromRestV02Response(
|
|
REST.config,
|
|
REST.schema,
|
|
REST.annotationsObs,
|
|
REST.annotationsVar,
|
|
REST.layoutObs
|
|
);
|
|
expect(universe).toBeDefined();
|
|
|
|
const world = World.createWorldFromEntireUniverse(universe);
|
|
expect(world).toBeDefined();
|
|
|
|
expect(world).toMatchObject(
|
|
expect.objectContaining({
|
|
api: "0.2",
|
|
nObs: universe.nObs,
|
|
nVar: universe.nVar,
|
|
schema: universe.schema,
|
|
obsAnnotations: universe.obsAnnotations,
|
|
varAnnotations: universe.varAnnotations,
|
|
obsLayout: universe.obsLayout,
|
|
|
|
summary: expect.objectContaining({
|
|
obs: _(REST.schema.schema.annotations.obs)
|
|
.filter(v => v.name !== "name")
|
|
.keyBy("name")
|
|
.mapValues(() => expect.any(Object))
|
|
.value(),
|
|
var: _(REST.schema.schema.annotations.var)
|
|
.filter(v => v.name !== "name")
|
|
.keyBy("name")
|
|
.mapValues(() => expect.any(Object))
|
|
.value()
|
|
}),
|
|
|
|
varDataCache: expect.any(Object),
|
|
|
|
obsIndex: null, // null indicating full universe
|
|
obsBackIndex: null
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("createWorldFromCurrentSelection", () => {
|
|
test("create from REST sample", () => {
|
|
const {
|
|
universe,
|
|
world: originalWorld,
|
|
crossfilter,
|
|
dimensionMap
|
|
} = defaultBigBang();
|
|
|
|
/* mock a selection */
|
|
dimensionMap[obsAnnoDimensionName("field1")].filterRange([0, 5]);
|
|
dimensionMap[obsAnnoDimensionName("field3")].filterExact(false);
|
|
|
|
/* create the world from the selection */
|
|
const world = World.createWorldFromCurrentSelection(
|
|
universe,
|
|
originalWorld,
|
|
crossfilter
|
|
);
|
|
expect(world).toBeDefined();
|
|
expect(world.nObs).toEqual(crossfilter.countFiltered());
|
|
|
|
/*
|
|
calculate expected values and match against result
|
|
*/
|
|
|
|
/* matchFilter must match the dimension filters above */
|
|
const matchFilter = val => val.field1 >= 0 && val.field1 < 5 && !val.field3;
|
|
const universeIndices = _()
|
|
.range(universe.nObs)
|
|
.filter(idx => matchFilter(universe.obsAnnotations[idx]))
|
|
.value();
|
|
|
|
const expected = {
|
|
nObs: universeIndices.length,
|
|
obsAnnotations: _.map(universeIndices, i => universe.obsAnnotations[i]),
|
|
obsLayout: {
|
|
X: new Float32Array(
|
|
_.map(universeIndices, i => universe.obsLayout.X[i])
|
|
),
|
|
Y: new Float32Array(
|
|
_.map(universeIndices, i => universe.obsLayout.Y[i])
|
|
)
|
|
},
|
|
obsBackIndex: _.transform(
|
|
universeIndices,
|
|
(result, univIdx, worldIdx) => {
|
|
result[univIdx] = worldIdx;
|
|
},
|
|
new Uint32Array(universe.nObs).fill(-1)
|
|
),
|
|
obsIndex: new Uint32Array(universeIndices)
|
|
};
|
|
|
|
expect(world).toMatchObject(
|
|
expect.objectContaining({
|
|
api: "0.2",
|
|
nObs: expected.nObs,
|
|
nVar: universe.nVar,
|
|
schema: universe.schema,
|
|
obsAnnotations: expected.obsAnnotations,
|
|
varAnnotations: universe.varAnnotations,
|
|
obsLayout: expected.obsLayout,
|
|
summary: {
|
|
obs: expect.any(Object) /* we could do better! */,
|
|
var: expect.any(Object) /* we could do better! */
|
|
},
|
|
varDataCache: expect.any(Object),
|
|
obsIndex: expected.obsIndex,
|
|
obsBackIndex: expected.obsBackIndex
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("createObsDimensionMap", () => {
|
|
test("when universe eq world", () => {
|
|
/*
|
|
check for:
|
|
- creates a dimension for all obsAnnotations, PLUS X/Y layout
|
|
- check that dimension typing is sane
|
|
*/
|
|
|
|
const { dimensionMap } = defaultBigBang();
|
|
const annotationNames = _.map(
|
|
REST.schema.schema.annotations.obs,
|
|
c => c.name
|
|
);
|
|
const schemaByObsName = _.keyBy(REST.schema.schema.annotations.obs, "name");
|
|
expect(dimensionMap).toBeDefined();
|
|
annotationNames.forEach(name => {
|
|
const dim = dimensionMap[obsAnnoDimensionName(name)];
|
|
if (name === "name") {
|
|
expect(dim).toBeUndefined();
|
|
} else {
|
|
const { type } = schemaByObsName[name];
|
|
if (type === "string" || type === "boolean" || type === "categorical") {
|
|
expect(dim).toBeInstanceOf(Crossfilter.EnumDimension);
|
|
} else {
|
|
expect(dim).toBeInstanceOf(Crossfilter.ScalarDimension);
|
|
}
|
|
}
|
|
});
|
|
expect(dimensionMap[layoutDimensionName("X")]).toBeInstanceOf(
|
|
Crossfilter.ScalarDimension
|
|
);
|
|
expect(dimensionMap[layoutDimensionName("Y")]).toBeInstanceOf(
|
|
Crossfilter.ScalarDimension
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("subsetVarData", () => {
|
|
test("when world eq universe", () => {
|
|
const { universe, world } = defaultBigBang();
|
|
/* create a mock varData array for subsetting */
|
|
const sourceVarData = new Float32Array(universe.nObs);
|
|
|
|
/* expect literally the same object back */
|
|
const result = World.subsetVarData(world, universe, sourceVarData);
|
|
expect(result).toBe(sourceVarData);
|
|
});
|
|
|
|
test("when world neq universe", () => {
|
|
const { universe, world, crossfilter, dimensionMap } = defaultBigBang();
|
|
/* create a mock varData array for subsetting */
|
|
const sourceVarData = Float32Array.from(_.range(universe.nObs));
|
|
|
|
/* mock a selection */
|
|
dimensionMap[obsAnnoDimensionName("field1")].filterRange([0, 5]);
|
|
dimensionMap[obsAnnoDimensionName("field3")].filterExact(false);
|
|
|
|
/* create the world from the selection */
|
|
const newWorld = World.createWorldFromCurrentSelection(
|
|
universe,
|
|
world,
|
|
crossfilter
|
|
);
|
|
expect(newWorld.obsIndex).toMatchObject(new Uint32Array([0, 2]));
|
|
|
|
/* expect a subset */
|
|
const result = World.subsetVarData(newWorld, universe, sourceVarData);
|
|
expect(result).not.toBe(sourceVarData);
|
|
expect(result).toHaveLength(newWorld.nObs);
|
|
/* check that we have expected source var content */
|
|
expect(result).toMatchObject(new Float32Array([0, 2]));
|
|
});
|
|
});
|
|
|
|
describe("createVarDimension", () => {
|
|
/* create default universe */
|
|
const { world, crossfilter } = defaultBigBang();
|
|
/* create a mock var data cache */
|
|
const varDataCache = kvCache.set(
|
|
kvCache.create(),
|
|
"GENE",
|
|
Float32Array.from(_.range(world.nObs))
|
|
);
|
|
const result = World.createVarDimension(
|
|
world,
|
|
varDataCache,
|
|
crossfilter,
|
|
"GENE"
|
|
);
|
|
expect(result).toBeInstanceOf(Crossfilter.ScalarDimension);
|
|
});
|
|
|
|
describe("worldEqUniverse", () => {
|
|
const { universe, world } = defaultBigBang();
|
|
const result = World.worldEqUniverse(world, universe);
|
|
expect(result).toBe(true);
|
|
});
|