binary wire format with flatbuffers (#509)

* 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
This commit is contained in:
Bruce Martin
2019-01-09 14:26:05 -08:00
committed by GitHub
parent 42e25a1a1f
commit b90447c387
41 changed files with 2317 additions and 266 deletions
+22 -5
View File
@@ -25,17 +25,18 @@ export function catchErrorsWrap(fn, dispatchToUser = false) {
}
/*
Wrapper to perform an async fetch and JSON decode response.
Wrapper to perform async fetch with some modest error handling
and decoding.
*/
export const doJsonRequest = async url => {
const doFetch = async (url, acceptType) => {
const res = await fetch(url, {
method: "get",
headers: new Headers({
"Content-Type": "application/json"
Accept: acceptType
})
});
if (res.ok && res.headers.get("Content-Type") === "application/json") {
return res.json();
if (res.ok && res.headers.get("Content-Type") === acceptType) {
return res;
}
// else an error
let msg = `Unexpected HTTP response ${res.status}, ${res.statusText}`;
@@ -47,6 +48,22 @@ export const doJsonRequest = async url => {
throw new Error(msg);
};
/*
Wrapper to perform an async fetch and JSON decode response.
*/
export const doJsonRequest = async url => {
const res = await doFetch(url, "application/json");
return res.json();
};
/*
Wrapper to perform an async fetch for binary data.
*/
export const doBinaryRequest = async url => {
const res = await doFetch(url, "application/octet-stream");
return res.arrayBuffer();
};
/*
This function "packs" filter index lists into the more efficient
"range" form specified in the REST 0.2 spec.
+33
View File
@@ -0,0 +1,33 @@
/*
Return the [minimum, maximum] extent, of the given typed array, ignoring
non-finite values (ie, +Infinity, -Infinity).
If undefined or empty array, or array contains only non-finite numbers,
will return [undefined, undefined]
*/
function finiteExtent(tarr) {
let min;
let max;
let i;
for (i = 0; i < tarr.length; i += 1) {
const val = tarr[i];
if (Number.isFinite(val)) {
min = val;
max = val;
i += 1;
break;
}
}
for (; i < tarr.length; i += 1) {
const val = tarr[i];
if (Number.isFinite(val)) {
if (min > val) min = val;
if (max < val) max = val;
}
}
return [min, max];
}
export default finiteExtent;
+1 -1
View File
@@ -3,7 +3,7 @@
/*
Model manager providing an abstraction for the use of the reducer code.
This module provides several buckets of functionality:
- schema and config driven tranformation of the dataframe wire protocol
- schema and config driven tranformation of the wire protocol
into a format that is easy for the UI code to use.
- manage the universe/world abstraction:
+ universe: all of the server-provided, read-only data
+75
View File
@@ -0,0 +1,75 @@
import { flatbuffers } from "flatbuffers";
import { NetEncoding } from "./matrix_generated";
const utf8Decoder = new TextDecoder("utf-8");
/*
Matrix flatbuffer decoding support. See fbs/matrix.fbs
*/
/*
Decode NetEncoding.TypedArray
*/
function decodeTypedArray(uType, uValF, inplace = false) {
if (uType === NetEncoding.TypedArray.NONE) {
return null;
}
// Convert to a JS class that supports this type
const TypeClass = NetEncoding[NetEncoding.TypedArray[uType]];
// Create a TypedArray that references the underlying buffer
let arr = uValF(new TypeClass()).dataArray();
if (uType === NetEncoding.TypedArray.JSONEncodedArray) {
const json = utf8Decoder.decode(arr);
arr = JSON.parse(json);
} else if (!inplace) {
/* force copy to release underlying FBS buffer */
arr = new arr.constructor(arr);
}
return arr;
}
/*
Parameter: Uint8Array or ArrayBuffer containing raw flatbuffer Matrix
Returns: object containing decoded Matrix:
{
nRows: num,
nCols: num,
columns: [
each column, which will be a TypedArray or Array
]
colIdx: []|null
}
*/
function decodeMatrixFBS(arrayBuffer, inplace = false) {
const bb = new flatbuffers.ByteBuffer(new Uint8Array(arrayBuffer));
const df = NetEncoding.Matrix.getRootAsMatrix(bb);
const nRows = df.nRows();
const nCols = df.nCols();
/* decode columns */
const columnsLength = df.columnsLength();
const columns = Array(columnsLength).fill(null);
for (let c = 0; c < columnsLength; c += 1) {
const col = df.columns(c);
columns[c] = decodeTypedArray(col.uType(), col.u.bind(col), inplace);
}
/* decode col_idx */
const colIdx = decodeTypedArray(
df.colIndexType(),
df.colIndex.bind(df),
inplace
);
return {
nRows,
nCols,
columns,
colIdx,
rowIdx: null
};
}
export default decodeMatrixFBS;
@@ -0,0 +1,835 @@
// automatically generated by the FlatBuffers compiler, do not modify
/**
* @const
* @namespace
*/
var NetEncoding = NetEncoding || {};
/**
* @enum
*/
NetEncoding.TypedArray = {
NONE: 0, 0: 'NONE',
Float32Array: 1, 1: 'Float32Array',
Int32Array: 2, 2: 'Int32Array',
Uint32Array: 3, 3: 'Uint32Array',
Float64Array: 4, 4: 'Float64Array',
JSONEncodedArray: 5, 5: 'JSONEncodedArray'
};
/**
* @constructor
*/
NetEncoding.Float32Array = function() {
/**
* @type {flatbuffers.ByteBuffer}
*/
this.bb = null;
/**
* @type {number}
*/
this.bb_pos = 0;
};
/**
* @param {number} i
* @param {flatbuffers.ByteBuffer} bb
* @returns {NetEncoding.Float32Array}
*/
NetEncoding.Float32Array.prototype.__init = function(i, bb) {
this.bb_pos = i;
this.bb = bb;
return this;
};
/**
* @param {flatbuffers.ByteBuffer} bb
* @param {NetEncoding.Float32Array=} obj
* @returns {NetEncoding.Float32Array}
*/
NetEncoding.Float32Array.getRootAsFloat32Array = function(bb, obj) {
return (obj || new NetEncoding.Float32Array).__init(bb.readInt32(bb.position()) + bb.position(), bb);
};
/**
* @param {number} index
* @returns {number}
*/
NetEncoding.Float32Array.prototype.data = function(index) {
var offset = this.bb.__offset(this.bb_pos, 4);
return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0;
};
/**
* @returns {number}
*/
NetEncoding.Float32Array.prototype.dataLength = function() {
var offset = this.bb.__offset(this.bb_pos, 4);
return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0;
};
/**
* @returns {Float32Array}
*/
NetEncoding.Float32Array.prototype.dataArray = function() {
var offset = this.bb.__offset(this.bb_pos, 4);
return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null;
};
/**
* @param {flatbuffers.Builder} builder
*/
NetEncoding.Float32Array.startFloat32Array = function(builder) {
builder.startObject(1);
};
/**
* @param {flatbuffers.Builder} builder
* @param {flatbuffers.Offset} dataOffset
*/
NetEncoding.Float32Array.addData = function(builder, dataOffset) {
builder.addFieldOffset(0, dataOffset, 0);
};
/**
* @param {flatbuffers.Builder} builder
* @param {Array.<number>} data
* @returns {flatbuffers.Offset}
*/
NetEncoding.Float32Array.createDataVector = function(builder, data) {
builder.startVector(4, data.length, 4);
for (var i = data.length - 1; i >= 0; i--) {
builder.addFloat32(data[i]);
}
return builder.endVector();
};
/**
* @param {flatbuffers.Builder} builder
* @param {number} numElems
*/
NetEncoding.Float32Array.startDataVector = function(builder, numElems) {
builder.startVector(4, numElems, 4);
};
/**
* @param {flatbuffers.Builder} builder
* @returns {flatbuffers.Offset}
*/
NetEncoding.Float32Array.endFloat32Array = function(builder) {
var offset = builder.endObject();
return offset;
};
/**
* @constructor
*/
NetEncoding.Uint32Array = function() {
/**
* @type {flatbuffers.ByteBuffer}
*/
this.bb = null;
/**
* @type {number}
*/
this.bb_pos = 0;
};
/**
* @param {number} i
* @param {flatbuffers.ByteBuffer} bb
* @returns {NetEncoding.Uint32Array}
*/
NetEncoding.Uint32Array.prototype.__init = function(i, bb) {
this.bb_pos = i;
this.bb = bb;
return this;
};
/**
* @param {flatbuffers.ByteBuffer} bb
* @param {NetEncoding.Uint32Array=} obj
* @returns {NetEncoding.Uint32Array}
*/
NetEncoding.Uint32Array.getRootAsUint32Array = function(bb, obj) {
return (obj || new NetEncoding.Uint32Array).__init(bb.readInt32(bb.position()) + bb.position(), bb);
};
/**
* @param {number} index
* @returns {number}
*/
NetEncoding.Uint32Array.prototype.data = function(index) {
var offset = this.bb.__offset(this.bb_pos, 4);
return offset ? this.bb.readUint32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0;
};
/**
* @returns {number}
*/
NetEncoding.Uint32Array.prototype.dataLength = function() {
var offset = this.bb.__offset(this.bb_pos, 4);
return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0;
};
/**
* @returns {Uint32Array}
*/
NetEncoding.Uint32Array.prototype.dataArray = function() {
var offset = this.bb.__offset(this.bb_pos, 4);
return offset ? new Uint32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null;
};
/**
* @param {flatbuffers.Builder} builder
*/
NetEncoding.Uint32Array.startUint32Array = function(builder) {
builder.startObject(1);
};
/**
* @param {flatbuffers.Builder} builder
* @param {flatbuffers.Offset} dataOffset
*/
NetEncoding.Uint32Array.addData = function(builder, dataOffset) {
builder.addFieldOffset(0, dataOffset, 0);
};
/**
* @param {flatbuffers.Builder} builder
* @param {Array.<number>} data
* @returns {flatbuffers.Offset}
*/
NetEncoding.Uint32Array.createDataVector = function(builder, data) {
builder.startVector(4, data.length, 4);
for (var i = data.length - 1; i >= 0; i--) {
builder.addInt32(data[i]);
}
return builder.endVector();
};
/**
* @param {flatbuffers.Builder} builder
* @param {number} numElems
*/
NetEncoding.Uint32Array.startDataVector = function(builder, numElems) {
builder.startVector(4, numElems, 4);
};
/**
* @param {flatbuffers.Builder} builder
* @returns {flatbuffers.Offset}
*/
NetEncoding.Uint32Array.endUint32Array = function(builder) {
var offset = builder.endObject();
return offset;
};
/**
* @constructor
*/
NetEncoding.Int32Array = function() {
/**
* @type {flatbuffers.ByteBuffer}
*/
this.bb = null;
/**
* @type {number}
*/
this.bb_pos = 0;
};
/**
* @param {number} i
* @param {flatbuffers.ByteBuffer} bb
* @returns {NetEncoding.Int32Array}
*/
NetEncoding.Int32Array.prototype.__init = function(i, bb) {
this.bb_pos = i;
this.bb = bb;
return this;
};
/**
* @param {flatbuffers.ByteBuffer} bb
* @param {NetEncoding.Int32Array=} obj
* @returns {NetEncoding.Int32Array}
*/
NetEncoding.Int32Array.getRootAsInt32Array = function(bb, obj) {
return (obj || new NetEncoding.Int32Array).__init(bb.readInt32(bb.position()) + bb.position(), bb);
};
/**
* @param {number} index
* @returns {number}
*/
NetEncoding.Int32Array.prototype.data = function(index) {
var offset = this.bb.__offset(this.bb_pos, 4);
return offset ? this.bb.readInt32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0;
};
/**
* @returns {number}
*/
NetEncoding.Int32Array.prototype.dataLength = function() {
var offset = this.bb.__offset(this.bb_pos, 4);
return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0;
};
/**
* @returns {Int32Array}
*/
NetEncoding.Int32Array.prototype.dataArray = function() {
var offset = this.bb.__offset(this.bb_pos, 4);
return offset ? new Int32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null;
};
/**
* @param {flatbuffers.Builder} builder
*/
NetEncoding.Int32Array.startInt32Array = function(builder) {
builder.startObject(1);
};
/**
* @param {flatbuffers.Builder} builder
* @param {flatbuffers.Offset} dataOffset
*/
NetEncoding.Int32Array.addData = function(builder, dataOffset) {
builder.addFieldOffset(0, dataOffset, 0);
};
/**
* @param {flatbuffers.Builder} builder
* @param {Array.<number>} data
* @returns {flatbuffers.Offset}
*/
NetEncoding.Int32Array.createDataVector = function(builder, data) {
builder.startVector(4, data.length, 4);
for (var i = data.length - 1; i >= 0; i--) {
builder.addInt32(data[i]);
}
return builder.endVector();
};
/**
* @param {flatbuffers.Builder} builder
* @param {number} numElems
*/
NetEncoding.Int32Array.startDataVector = function(builder, numElems) {
builder.startVector(4, numElems, 4);
};
/**
* @param {flatbuffers.Builder} builder
* @returns {flatbuffers.Offset}
*/
NetEncoding.Int32Array.endInt32Array = function(builder) {
var offset = builder.endObject();
return offset;
};
/**
* @constructor
*/
NetEncoding.Float64Array = function() {
/**
* @type {flatbuffers.ByteBuffer}
*/
this.bb = null;
/**
* @type {number}
*/
this.bb_pos = 0;
};
/**
* @param {number} i
* @param {flatbuffers.ByteBuffer} bb
* @returns {NetEncoding.Float64Array}
*/
NetEncoding.Float64Array.prototype.__init = function(i, bb) {
this.bb_pos = i;
this.bb = bb;
return this;
};
/**
* @param {flatbuffers.ByteBuffer} bb
* @param {NetEncoding.Float64Array=} obj
* @returns {NetEncoding.Float64Array}
*/
NetEncoding.Float64Array.getRootAsFloat64Array = function(bb, obj) {
return (obj || new NetEncoding.Float64Array).__init(bb.readInt32(bb.position()) + bb.position(), bb);
};
/**
* @param {number} index
* @returns {number}
*/
NetEncoding.Float64Array.prototype.data = function(index) {
var offset = this.bb.__offset(this.bb_pos, 4);
return offset ? this.bb.readFloat64(this.bb.__vector(this.bb_pos + offset) + index * 8) : 0;
};
/**
* @returns {number}
*/
NetEncoding.Float64Array.prototype.dataLength = function() {
var offset = this.bb.__offset(this.bb_pos, 4);
return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0;
};
/**
* @returns {Float64Array}
*/
NetEncoding.Float64Array.prototype.dataArray = function() {
var offset = this.bb.__offset(this.bb_pos, 4);
return offset ? new Float64Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null;
};
/**
* @param {flatbuffers.Builder} builder
*/
NetEncoding.Float64Array.startFloat64Array = function(builder) {
builder.startObject(1);
};
/**
* @param {flatbuffers.Builder} builder
* @param {flatbuffers.Offset} dataOffset
*/
NetEncoding.Float64Array.addData = function(builder, dataOffset) {
builder.addFieldOffset(0, dataOffset, 0);
};
/**
* @param {flatbuffers.Builder} builder
* @param {Array.<number>} data
* @returns {flatbuffers.Offset}
*/
NetEncoding.Float64Array.createDataVector = function(builder, data) {
builder.startVector(8, data.length, 8);
for (var i = data.length - 1; i >= 0; i--) {
builder.addFloat64(data[i]);
}
return builder.endVector();
};
/**
* @param {flatbuffers.Builder} builder
* @param {number} numElems
*/
NetEncoding.Float64Array.startDataVector = function(builder, numElems) {
builder.startVector(8, numElems, 8);
};
/**
* @param {flatbuffers.Builder} builder
* @returns {flatbuffers.Offset}
*/
NetEncoding.Float64Array.endFloat64Array = function(builder) {
var offset = builder.endObject();
return offset;
};
/**
* @constructor
*/
NetEncoding.JSONEncodedArray = function() {
/**
* @type {flatbuffers.ByteBuffer}
*/
this.bb = null;
/**
* @type {number}
*/
this.bb_pos = 0;
};
/**
* @param {number} i
* @param {flatbuffers.ByteBuffer} bb
* @returns {NetEncoding.JSONEncodedArray}
*/
NetEncoding.JSONEncodedArray.prototype.__init = function(i, bb) {
this.bb_pos = i;
this.bb = bb;
return this;
};
/**
* @param {flatbuffers.ByteBuffer} bb
* @param {NetEncoding.JSONEncodedArray=} obj
* @returns {NetEncoding.JSONEncodedArray}
*/
NetEncoding.JSONEncodedArray.getRootAsJSONEncodedArray = function(bb, obj) {
return (obj || new NetEncoding.JSONEncodedArray).__init(bb.readInt32(bb.position()) + bb.position(), bb);
};
/**
* @param {number} index
* @returns {number}
*/
NetEncoding.JSONEncodedArray.prototype.data = function(index) {
var offset = this.bb.__offset(this.bb_pos, 4);
return offset ? this.bb.readUint8(this.bb.__vector(this.bb_pos + offset) + index) : 0;
};
/**
* @returns {number}
*/
NetEncoding.JSONEncodedArray.prototype.dataLength = function() {
var offset = this.bb.__offset(this.bb_pos, 4);
return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0;
};
/**
* @returns {Uint8Array}
*/
NetEncoding.JSONEncodedArray.prototype.dataArray = function() {
var offset = this.bb.__offset(this.bb_pos, 4);
return offset ? new Uint8Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null;
};
/**
* @param {flatbuffers.Builder} builder
*/
NetEncoding.JSONEncodedArray.startJSONEncodedArray = function(builder) {
builder.startObject(1);
};
/**
* @param {flatbuffers.Builder} builder
* @param {flatbuffers.Offset} dataOffset
*/
NetEncoding.JSONEncodedArray.addData = function(builder, dataOffset) {
builder.addFieldOffset(0, dataOffset, 0);
};
/**
* @param {flatbuffers.Builder} builder
* @param {Array.<number>} data
* @returns {flatbuffers.Offset}
*/
NetEncoding.JSONEncodedArray.createDataVector = function(builder, data) {
builder.startVector(1, data.length, 1);
for (var i = data.length - 1; i >= 0; i--) {
builder.addInt8(data[i]);
}
return builder.endVector();
};
/**
* @param {flatbuffers.Builder} builder
* @param {number} numElems
*/
NetEncoding.JSONEncodedArray.startDataVector = function(builder, numElems) {
builder.startVector(1, numElems, 1);
};
/**
* @param {flatbuffers.Builder} builder
* @returns {flatbuffers.Offset}
*/
NetEncoding.JSONEncodedArray.endJSONEncodedArray = function(builder) {
var offset = builder.endObject();
return offset;
};
/**
* @constructor
*/
NetEncoding.Column = function() {
/**
* @type {flatbuffers.ByteBuffer}
*/
this.bb = null;
/**
* @type {number}
*/
this.bb_pos = 0;
};
/**
* @param {number} i
* @param {flatbuffers.ByteBuffer} bb
* @returns {NetEncoding.Column}
*/
NetEncoding.Column.prototype.__init = function(i, bb) {
this.bb_pos = i;
this.bb = bb;
return this;
};
/**
* @param {flatbuffers.ByteBuffer} bb
* @param {NetEncoding.Column=} obj
* @returns {NetEncoding.Column}
*/
NetEncoding.Column.getRootAsColumn = function(bb, obj) {
return (obj || new NetEncoding.Column).__init(bb.readInt32(bb.position()) + bb.position(), bb);
};
/**
* @returns {NetEncoding.TypedArray}
*/
NetEncoding.Column.prototype.uType = function() {
var offset = this.bb.__offset(this.bb_pos, 4);
return offset ? /** @type {NetEncoding.TypedArray} */ (this.bb.readUint8(this.bb_pos + offset)) : NetEncoding.TypedArray.NONE;
};
/**
* @param {flatbuffers.Table} obj
* @returns {?flatbuffers.Table}
*/
NetEncoding.Column.prototype.u = function(obj) {
var offset = this.bb.__offset(this.bb_pos, 6);
return offset ? this.bb.__union(obj, this.bb_pos + offset) : null;
};
/**
* @param {flatbuffers.Builder} builder
*/
NetEncoding.Column.startColumn = function(builder) {
builder.startObject(2);
};
/**
* @param {flatbuffers.Builder} builder
* @param {NetEncoding.TypedArray} uType
*/
NetEncoding.Column.addUType = function(builder, uType) {
builder.addFieldInt8(0, uType, NetEncoding.TypedArray.NONE);
};
/**
* @param {flatbuffers.Builder} builder
* @param {flatbuffers.Offset} uOffset
*/
NetEncoding.Column.addU = function(builder, uOffset) {
builder.addFieldOffset(1, uOffset, 0);
};
/**
* @param {flatbuffers.Builder} builder
* @returns {flatbuffers.Offset}
*/
NetEncoding.Column.endColumn = function(builder) {
var offset = builder.endObject();
return offset;
};
/**
* @constructor
*/
NetEncoding.Matrix = function() {
/**
* @type {flatbuffers.ByteBuffer}
*/
this.bb = null;
/**
* @type {number}
*/
this.bb_pos = 0;
};
/**
* @param {number} i
* @param {flatbuffers.ByteBuffer} bb
* @returns {NetEncoding.Matrix}
*/
NetEncoding.Matrix.prototype.__init = function(i, bb) {
this.bb_pos = i;
this.bb = bb;
return this;
};
/**
* @param {flatbuffers.ByteBuffer} bb
* @param {NetEncoding.Matrix=} obj
* @returns {NetEncoding.Matrix}
*/
NetEncoding.Matrix.getRootAsMatrix = function(bb, obj) {
return (obj || new NetEncoding.Matrix).__init(bb.readInt32(bb.position()) + bb.position(), bb);
};
/**
* @returns {number}
*/
NetEncoding.Matrix.prototype.nRows = function() {
var offset = this.bb.__offset(this.bb_pos, 4);
return offset ? this.bb.readUint32(this.bb_pos + offset) : 0;
};
/**
* @returns {number}
*/
NetEncoding.Matrix.prototype.nCols = function() {
var offset = this.bb.__offset(this.bb_pos, 6);
return offset ? this.bb.readUint32(this.bb_pos + offset) : 0;
};
/**
* @param {number} index
* @param {NetEncoding.Column=} obj
* @returns {NetEncoding.Column}
*/
NetEncoding.Matrix.prototype.columns = function(index, obj) {
var offset = this.bb.__offset(this.bb_pos, 8);
return offset ? (obj || new NetEncoding.Column).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos + offset) + index * 4), this.bb) : null;
};
/**
* @returns {number}
*/
NetEncoding.Matrix.prototype.columnsLength = function() {
var offset = this.bb.__offset(this.bb_pos, 8);
return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0;
};
/**
* @returns {NetEncoding.TypedArray}
*/
NetEncoding.Matrix.prototype.colIndexType = function() {
var offset = this.bb.__offset(this.bb_pos, 10);
return offset ? /** @type {NetEncoding.TypedArray} */ (this.bb.readUint8(this.bb_pos + offset)) : NetEncoding.TypedArray.NONE;
};
/**
* @param {flatbuffers.Table} obj
* @returns {?flatbuffers.Table}
*/
NetEncoding.Matrix.prototype.colIndex = function(obj) {
var offset = this.bb.__offset(this.bb_pos, 12);
return offset ? this.bb.__union(obj, this.bb_pos + offset) : null;
};
/**
* @returns {NetEncoding.TypedArray}
*/
NetEncoding.Matrix.prototype.rowIndexType = function() {
var offset = this.bb.__offset(this.bb_pos, 14);
return offset ? /** @type {NetEncoding.TypedArray} */ (this.bb.readUint8(this.bb_pos + offset)) : NetEncoding.TypedArray.NONE;
};
/**
* @param {flatbuffers.Table} obj
* @returns {?flatbuffers.Table}
*/
NetEncoding.Matrix.prototype.rowIndex = function(obj) {
var offset = this.bb.__offset(this.bb_pos, 16);
return offset ? this.bb.__union(obj, this.bb_pos + offset) : null;
};
/**
* @param {flatbuffers.Builder} builder
*/
NetEncoding.Matrix.startMatrix = function(builder) {
builder.startObject(7);
};
/**
* @param {flatbuffers.Builder} builder
* @param {number} nRows
*/
NetEncoding.Matrix.addNRows = function(builder, nRows) {
builder.addFieldInt32(0, nRows, 0);
};
/**
* @param {flatbuffers.Builder} builder
* @param {number} nCols
*/
NetEncoding.Matrix.addNCols = function(builder, nCols) {
builder.addFieldInt32(1, nCols, 0);
};
/**
* @param {flatbuffers.Builder} builder
* @param {flatbuffers.Offset} columnsOffset
*/
NetEncoding.Matrix.addColumns = function(builder, columnsOffset) {
builder.addFieldOffset(2, columnsOffset, 0);
};
/**
* @param {flatbuffers.Builder} builder
* @param {Array.<flatbuffers.Offset>} data
* @returns {flatbuffers.Offset}
*/
NetEncoding.Matrix.createColumnsVector = function(builder, data) {
builder.startVector(4, data.length, 4);
for (var i = data.length - 1; i >= 0; i--) {
builder.addOffset(data[i]);
}
return builder.endVector();
};
/**
* @param {flatbuffers.Builder} builder
* @param {number} numElems
*/
NetEncoding.Matrix.startColumnsVector = function(builder, numElems) {
builder.startVector(4, numElems, 4);
};
/**
* @param {flatbuffers.Builder} builder
* @param {NetEncoding.TypedArray} colIndexType
*/
NetEncoding.Matrix.addColIndexType = function(builder, colIndexType) {
builder.addFieldInt8(3, colIndexType, NetEncoding.TypedArray.NONE);
};
/**
* @param {flatbuffers.Builder} builder
* @param {flatbuffers.Offset} colIndexOffset
*/
NetEncoding.Matrix.addColIndex = function(builder, colIndexOffset) {
builder.addFieldOffset(4, colIndexOffset, 0);
};
/**
* @param {flatbuffers.Builder} builder
* @param {NetEncoding.TypedArray} rowIndexType
*/
NetEncoding.Matrix.addRowIndexType = function(builder, rowIndexType) {
builder.addFieldInt8(5, rowIndexType, NetEncoding.TypedArray.NONE);
};
/**
* @param {flatbuffers.Builder} builder
* @param {flatbuffers.Offset} rowIndexOffset
*/
NetEncoding.Matrix.addRowIndex = function(builder, rowIndexOffset) {
builder.addFieldOffset(6, rowIndexOffset, 0);
};
/**
* @param {flatbuffers.Builder} builder
* @returns {flatbuffers.Offset}
*/
NetEncoding.Matrix.endMatrix = function(builder) {
var offset = builder.endObject();
return offset;
};
/**
* @param {flatbuffers.Builder} builder
* @param {flatbuffers.Offset} offset
*/
NetEncoding.Matrix.finishMatrixBuffer = function(builder, offset) {
builder.finish(offset);
};
// Exports for ECMAScript6 Modules
export {NetEncoding};
@@ -1,4 +1,5 @@
import _ from "lodash";
import finiteExtent from "../finiteExtent";
/*
Build and return obs/var summary using any annotation in the schema
@@ -61,16 +62,32 @@ function _summarizeAnnotations(_schema, annotations) {
const continuous = type === "int32" || type === "float32";
if (continuous) {
let min = Number.POSITIVE_INFINITY;
let max = Number.NEGATIVE_INFINITY;
let min;
let max;
let nan = 0;
let pinf = 0;
let ninf = 0;
for (let r = 0; r < annotations.length; r += 1) {
const val = Number(annotations[r][name]);
min = val < min ? val : min;
max = val > max ? val : max;
if (Number.isFinite(val)) {
if (min === undefined) {
min = val;
max = val;
} else {
min = val < min ? val : min;
max = val > max ? val : max;
}
} else if (Number.isNaN(val)) {
nan += 1;
} else if (val > 0) {
pinf += 1;
} else {
ninf += 1;
}
}
return {
categorical: false,
range: { min, max }
range: { min, max, nan, pinf, ninf }
};
}
+44 -83
View File
@@ -1,8 +1,10 @@
// jshint esversion: 6
import _ from "lodash";
import * as kvCache from "./keyvalcache";
import summarizeAnnotations from "./summarizeAnnotations";
import decodeMatrixFBS from "./matrix";
/*
Private helper function - create and return a template Universe
@@ -92,73 +94,40 @@ function finalize(universe) {
return universe;
}
function RESTv02AnnotationsResponseToInternal(response) {
function RESTv02AnotationsFBSResponseToInternal(arrayBuffer) {
/*
Source per the spec:
{
names: [
'tissue_type', 'sex', 'num_reads', 'clusters'
],
data: [
[ 0, 'lung', 'F', 39844, 99 ],
[ 1, 'heart', 'M', 83, 1 ],
[ 49, 'spleen', null, 2, "unknown cluster" ],
// [ obsOrVarIndex, value, value, value, value ],
// ...
]
}
Convert a Matrix FBS to our internal format -- row-major array of
observations/cells, stored as an object. Each obs has a key for each
annotation, plus __index__ containing its obsIndex.
Internal (target) format:
Example:
[
{ __index__: 0, tissue_type: "lung", sex: "F", ... },
...
]
XXX TODO: we could make use of the columns in building crossfilter
dimensions (they have to be recreated). Future optimization.
*/
const { names, data } = response;
const keys = ["__index__", ...names];
return _(data)
.map(obs => _.zipObject(keys, obs))
.value();
const fbs = decodeMatrixFBS(arrayBuffer);
const keys = fbs.colIdx;
const result = Array(fbs.nRows);
for (let row = 0; row < fbs.nRows; row += 1) {
const rec = { __index__: row };
for (let col = 0; col < fbs.nCols; col += 1) {
rec[keys[col]] = fbs.columns[col][row];
}
result[row] = rec;
}
return result;
}
function RESTv02LayoutResponseToInternal(response) {
/*
Source per the spec:
{
layout: {
ndims: 2,
coordinates: [
[ 0, 0.284483, 0.983744 ],
[ 1, 0.038844, 0.739444 ],
// [ obsOrVarIndex, X_coord, Y_coord ],
// ...
]
}
}
Target (internal) format:
{
X: Float32Array(numObs),
Y: Float32Array(numObs)
}
In the same order as obsAnnotations
*/
const { ndims, coordinates } = response.layout;
if (ndims !== 2) {
throw new Error("Unsupported layout dimensionality");
}
const layout = {
X: new Float32Array(coordinates.length),
Y: new Float32Array(coordinates.length)
function RESTv02LayoutFBSResponseToInternal(arrayBuffer) {
const fbs = decodeMatrixFBS(arrayBuffer, true);
return {
X: fbs.columns[0],
Y: fbs.columns[1]
};
for (let i = 0; i < coordinates.length; i += 1) {
const [idx, x, y] = coordinates[i];
layout.X[idx] = x;
layout.Y[idx] = y;
}
return layout;
}
function reconcileSchemaCategoriesWithSummary(universe) {
@@ -192,7 +161,7 @@ export function createUniverseFromRestV02Response(
schemaResponse,
annotationsObsResponse,
annotationsVarResponse,
layoutObsResponse
layoutFBSResponse
) {
/*
build & return universe from a REST 0.2 /config, /schema and /annotations/obs response
@@ -209,15 +178,15 @@ export function createUniverseFromRestV02Response(
universe.nVar = schema.dataframe.nVar;
/* annotations */
universe.obsAnnotations = RESTv02AnnotationsResponseToInternal(
universe.obsAnnotations = RESTv02AnotationsFBSResponseToInternal(
annotationsObsResponse
);
universe.varAnnotations = RESTv02AnnotationsResponseToInternal(
universe.varAnnotations = RESTv02AnotationsFBSResponseToInternal(
annotationsVarResponse
);
/* layout */
universe.obsLayout = RESTv02LayoutResponseToInternal(layoutObsResponse);
universe.obsLayout = RESTv02LayoutFBSResponseToInternal(layoutFBSResponse);
universe.summary = summarizeAnnotations(
universe.schema,
@@ -229,32 +198,24 @@ export function createUniverseFromRestV02Response(
return finalize(universe);
}
export function convertExpressionRESTv02ToObject(universe, response) {
export function convertDataFBStoObject(universe, arrayBuffer) {
/*
/data/obs response looks like:
{
var: [ varIndices fetched ],
obs: [
[ obsIndex, evalue, ... ],
...
]
}
/data/var returns a flatbuffer (FBS) as described by cellxgene/fbs/matrix.fbs
convert expression toa simple Float32Array, and return
{ geneName: array, geneName: array, ... }
NOTE: geneName, not varIndex
This routine converts the binary wire encoding into a JS object:
{
gene: Float32Array,
...
}
*/
const vars = response.var;
const { obs } = response;
const fbs = decodeMatrixFBS(arrayBuffer);
const { colIdx, columns } = fbs;
const result = {};
// XXX TODO: could this use _.unzip and have less code?
for (let varIdx = 0; varIdx < vars.length; varIdx += 1) {
const gene = universe.varAnnotations[vars[varIdx]].name;
const data = new Float32Array(universe.nObs);
for (let obsIdx = 0; obsIdx < obs.length; obsIdx += 1) {
data[obsIdx] = obs[obsIdx][varIdx + 1];
}
result[gene] = data;
for (let c = 0; c < colIdx.length; c += 1) {
const gene = universe.varAnnotations[colIdx[c]].name;
result[gene] = columns[c];
}
return result;
}