mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-15 12:47:56 +08:00
* icons, partway * redux for values * onChange * cancel * annotations lifecycle for category names * copy categorical * edit category * add Dataframe.withColsFrom * render user annotations; default add/delete annotation category * add label name to actions * category name edit * error checking improvements * change schema field isUserAnnotation to writable * always have an unassigned label; implement delete label * implement add new label and edit label name * label current cell selection * fix select exact bug in crossfilter * clean up categorical reducer * fix tests * remove debugging printf * implement subset/reset for user annotations * undo redo support for user annotations * remove duplicate button from categories * add modal * remove obsolete duplicate annotation reducers * remove old debugging printf * connect modal to annotation create and dup * initial full-stack wiring * finish up end-to-end wiring * fix existing unit tests * fix pytests to match new schema API * remove debugging printfs * add label file rotation * remove obsolete comment * add fbs encode/decode tests * add tests for writable annotations * simplify code * fix hashing bug with FBS encoding * lint * fix smoke tests * improve error checking in Dataframe.withColsFrom * add unit test for Dataframe.withColsFrom * add unit test for Dataframe.columns and Dataframe.renameCol * fix bug in FBS encode, add better error checks, refactor * add FBS encode/decode test * add clarifying comment * clean up action type names; fix state inconsistency in crossfilter update * change autosave timer to 2.5sec * sort categorical metadata render order so it remains consistent * add temporary autogenerated label for add-new-label operation * fix hover-over label menu interference with cell highlighting * remove debugging code * add missing reducer cases & fix typo * make dataframe memoize more general purpose * add dev mode for annos * fix error on select duplicate * handle zero occupancy categories * correctly maintain unclipped AND clipped world * correctly handle zero length FBS matrix and label files * ensure all writable categorical schema contains an unassigned category * handle case where building occupancy stack for category with no members * dialog for creating label, disable button if duplicate or empty * visually separate writeable * edit category * fix edit category name * remove debugging code * fix edit annotation label * visually define unassigned, change options * Pull in requirements.txt from `master` * label currently selected cells * duplicate label * lint * fix pytest merge issues * rename --label-file to --experimental-label-file * remove debugging console log * spelling error fix; fix bug found in PR review. * lint
202 lines
5.5 KiB
JavaScript
202 lines
5.5 KiB
JavaScript
/* eslint no-bitwise: "off" */
|
|
import _ from "lodash";
|
|
import { flatbuffers } from "flatbuffers";
|
|
import { NetEncoding } from "../../../src/util/stateManager/matrix_generated";
|
|
|
|
/*
|
|
test data mocking REST 0.2 API responses. Used in several tests.
|
|
*/
|
|
const nObs = 10;
|
|
const nVar = 32;
|
|
const field4Categories = [83, true, "foo", 2.222222];
|
|
const fieldDCategories = [99, false, "mumble", 3.1415];
|
|
|
|
const aConfigResponse = {
|
|
config: {
|
|
features: [
|
|
{ method: "POST", path: "/cluster/", available: false },
|
|
{ method: "POST", path: "/layout/", available: false },
|
|
{ method: "POST", path: "/diffexp/", available: false },
|
|
{ method: "POST", path: "/saveLocal/", available: false }
|
|
],
|
|
displayNames: {
|
|
engine: "the little engine that could",
|
|
dataset: "all your zeros are mine"
|
|
}
|
|
}
|
|
};
|
|
|
|
const aSchemaResponse = {
|
|
schema: {
|
|
dataframe: {
|
|
nObs,
|
|
nVar,
|
|
type: "float32"
|
|
},
|
|
annotations: {
|
|
obs: {
|
|
index: "name",
|
|
columns: [
|
|
{ name: "name", type: "string" },
|
|
{ name: "field1", type: "int32" },
|
|
{ name: "field2", type: "float32" },
|
|
{ name: "field3", type: "boolean" },
|
|
{
|
|
name: "field4",
|
|
type: "categorical",
|
|
categories: field4Categories
|
|
}
|
|
]
|
|
},
|
|
var: {
|
|
index: "name",
|
|
columns: [
|
|
{ name: "name", type: "string" },
|
|
{ name: "fieldA", type: "int32" },
|
|
{ name: "fieldB", type: "float32" },
|
|
{ name: "fieldC", type: "boolean" },
|
|
{
|
|
name: "fieldD",
|
|
type: "categorical",
|
|
categories: fieldDCategories
|
|
}
|
|
]
|
|
}
|
|
},
|
|
layout: {
|
|
obs: [{ name: "umap", type: "float32", dims: ["umap_0", "umap_1"] }],
|
|
var: []
|
|
}
|
|
}
|
|
};
|
|
|
|
const anAnnotationsObsJSONResponse = {
|
|
names: ["name", "field1", "field2", "field3", "field4"],
|
|
data: _()
|
|
.range(nObs)
|
|
.map(idx => [
|
|
idx,
|
|
`obs${idx}`,
|
|
2 * idx,
|
|
idx + 0.0133,
|
|
!!(idx & 1),
|
|
field4Categories[idx % field4Categories.length]
|
|
])
|
|
.value()
|
|
};
|
|
|
|
const anAnnotationsVarJSONResponse = {
|
|
names: ["fieldA", "fieldB", "fieldC", "fieldD", "name"],
|
|
data: _()
|
|
.range(nVar)
|
|
.map(idx => [
|
|
idx,
|
|
10 * idx,
|
|
idx + 2.90143,
|
|
!!(idx & 1),
|
|
fieldDCategories[idx % fieldDCategories.length],
|
|
`var${idx}`
|
|
])
|
|
.value()
|
|
};
|
|
|
|
function encodeTypedArray(builder, uType, uData) {
|
|
const uTypeName = NetEncoding.TypedArray[uType];
|
|
const ArrayType = NetEncoding[uTypeName];
|
|
const dv = ArrayType.createDataVector(builder, uData);
|
|
builder.startObject(1);
|
|
builder.addFieldOffset(0, dv, 0);
|
|
return builder.endObject();
|
|
}
|
|
|
|
function encodeMatrix(columns, colIndex = undefined) {
|
|
/*
|
|
IMPORTANT: this is not a general purpose encoder. in particular,
|
|
it doesn't correctly handle all column index types, nor does it
|
|
handle all column typedarray types.
|
|
|
|
encodeMatrixFBS in matrix.py is more general. This is used only
|
|
as a testing santity check (alt implementation).
|
|
*/
|
|
const utf8Encoder = new TextEncoder("utf-8");
|
|
const builder = new flatbuffers.Builder(1024);
|
|
const cols = _.map(columns, carr => {
|
|
let uType;
|
|
let tarr;
|
|
if (_.every(carr, _.isNumber)) {
|
|
uType = NetEncoding.TypedArray.Float32Array;
|
|
tarr = encodeTypedArray(builder, uType, new Float32Array(carr));
|
|
} else {
|
|
uType = NetEncoding.TypedArray.JSONEncodedArray;
|
|
const json = JSON.stringify(carr);
|
|
const jsonUTF8 = utf8Encoder.encode(json);
|
|
tarr = encodeTypedArray(builder, uType, jsonUTF8);
|
|
}
|
|
NetEncoding.Column.startColumn(builder);
|
|
NetEncoding.Column.addUType(builder, uType);
|
|
NetEncoding.Column.addU(builder, tarr);
|
|
return NetEncoding.Column.endColumn(builder);
|
|
});
|
|
|
|
const encColumns = NetEncoding.Matrix.createColumnsVector(builder, cols);
|
|
|
|
let encColIndex;
|
|
if (colIndex) {
|
|
encColIndex = encodeTypedArray(
|
|
builder,
|
|
NetEncoding.TypedArray.JSONEncodedArray,
|
|
utf8Encoder.encode(JSON.stringify(colIndex))
|
|
);
|
|
}
|
|
|
|
NetEncoding.Matrix.startMatrix(builder);
|
|
NetEncoding.Matrix.addNRows(builder, columns[0].length);
|
|
NetEncoding.Matrix.addNCols(builder, columns.length);
|
|
NetEncoding.Matrix.addColumns(builder, encColumns);
|
|
if (colIndex) {
|
|
NetEncoding.Matrix.addColIndexType(
|
|
builder,
|
|
NetEncoding.TypedArray.JSONEncodedArray
|
|
);
|
|
NetEncoding.Matrix.addColIndex(builder, encColIndex);
|
|
}
|
|
const root = NetEncoding.Matrix.endMatrix(builder);
|
|
builder.finish(root);
|
|
return builder.asUint8Array();
|
|
}
|
|
|
|
const anAnnotationsObsFBSResponse = (() => {
|
|
const columns = _.zip(...anAnnotationsObsJSONResponse.data).slice(1);
|
|
return encodeMatrix(columns, anAnnotationsObsJSONResponse.names);
|
|
})();
|
|
|
|
const anAnnotationsVarFBSResponse = (() => {
|
|
const columns = _.zip(...anAnnotationsVarJSONResponse.data).slice(1);
|
|
return encodeMatrix(columns, anAnnotationsVarJSONResponse.names);
|
|
})();
|
|
|
|
const aLayoutFBSResponse = (() => {
|
|
const coords = [
|
|
new Float32Array(nObs).fill(Math.random()),
|
|
new Float32Array(nObs).fill(Math.random())
|
|
];
|
|
return encodeMatrix(coords, ["umap_0", "umap_1"]);
|
|
})();
|
|
|
|
const aDataObsResponse = {
|
|
var: [2, 4, 29],
|
|
obs: _()
|
|
.range(nObs)
|
|
.map(idx => [idx, Math.random(), Math.random(), Math.random()])
|
|
.value()
|
|
};
|
|
|
|
export {
|
|
aLayoutFBSResponse as layoutObs,
|
|
aDataObsResponse as dataObs,
|
|
anAnnotationsVarFBSResponse as annotationsVar,
|
|
anAnnotationsObsFBSResponse as annotationsObs,
|
|
aSchemaResponse as schema,
|
|
aConfigResponse as config
|
|
};
|