@az0uz/zarr
Version:
Javascript implementation of Zarr
132 lines • 5.54 kB
JavaScript
import { getTypedArrayCtr, getTypedArrayDtypeString } from './types';
import { slice } from '../core/slice';
import { ValueError } from '../errors';
import { normalizeShape, IS_NODE, isArrayBufferLike } from '../util';
import { setNestedArray, setNestedArrayToScalar, flattenNestedArray, sliceNestedArray } from './ops';
export class NestedArray {
constructor(data, shape, dtype) {
const dataIsTypedArray = data !== null && !!data.BYTES_PER_ELEMENT;
if (shape === undefined) {
if (!dataIsTypedArray) {
throw new ValueError("Shape argument is required unless you pass in a TypedArray");
}
shape = [data.length];
}
if (dtype === undefined) {
if (!dataIsTypedArray) {
throw new ValueError("Dtype argument is required unless you pass in a TypedArray");
}
dtype = getTypedArrayDtypeString(data);
}
shape = normalizeShape(shape);
this.shape = shape;
this.dtype = dtype;
if (dataIsTypedArray && shape.length !== 1) {
data = data.buffer;
}
// Zero dimension array.. they are a bit weirdly represented now, they will only ever occur internally
if (this.shape.length === 0) {
this.data = new (getTypedArrayCtr(dtype))(1);
}
else if (
// tslint:disable-next-line: strict-type-predicates
(IS_NODE && Buffer.isBuffer(data))
|| isArrayBufferLike(data)
|| data === null) {
// Create from ArrayBuffer or Buffer
const numShapeElements = shape.reduce((x, y) => x * y, 1);
if (data === null) {
data = new ArrayBuffer(numShapeElements * parseInt(dtype[dtype.length - 1], 10));
}
const numDataElements = data.byteLength / parseInt(dtype[dtype.length - 1], 10);
if (numShapeElements !== numDataElements) {
throw new Error(`Buffer has ${numDataElements} of dtype ${dtype}, shape is too large or small ${shape} (flat=${numShapeElements})`);
}
const typeConstructor = getTypedArrayCtr(dtype);
this.data = createNestedArray(data, typeConstructor, shape);
}
else {
this.data = data;
}
}
get(selection) {
const [sliceResult, outShape] = sliceNestedArray(this.data, this.shape, selection);
if (outShape.length === 0) {
return sliceResult;
}
else {
return new NestedArray(sliceResult, outShape, this.dtype);
}
}
set(selection = null, value) {
if (selection === null) {
selection = [slice(null)];
}
if (typeof value === "number") {
if (this.shape.length === 0) {
// Zero dimension array..
this.data[0] = value;
}
else {
setNestedArrayToScalar(this.data, value, this.shape, selection);
}
}
else {
setNestedArray(this.data, value.data, this.shape, value.shape, selection);
}
}
flatten() {
if (this.shape.length === 1) {
return this.data;
}
return flattenNestedArray(this.data, this.shape, getTypedArrayCtr(this.dtype));
}
/**
* Currently only supports a single integer as the size, TODO: support start, stop, step.
*/
static arange(size, dtype = "<i4") {
const constr = getTypedArrayCtr(dtype);
const data = rangeTypedArray([size], constr);
return new NestedArray(data, [size], dtype);
}
}
/**
* Creates a TypedArray with values 0 through N where N is the product of the shape.
*/
export function rangeTypedArray(shape, tContructor) {
const size = shape.reduce((x, y) => x * y, 1);
const data = new tContructor(size);
data.set([...Array(size).keys()]); // Sets range 0,1,2,3,4,5
return data;
}
/**
* Creates multi-dimensional (rank > 1) array given input data and shape recursively.
* What it does is create a Array<Array<...<Array<Uint8Array>>> or some other typed array.
* This is for internal use, there should be no need to call this from user code.
* @param data a buffer containing the data for this array.
* @param t constructor for the datatype of choice
* @param shape list of numbers describing the size in each dimension
* @param offset in bytes for this dimension
*/
export function createNestedArray(data, t, shape, offset = 0) {
if (shape.length === 1) {
// This is only ever reached if called with rank 1 shape, never reached through recursion.
// We just slice set the array directly from one level above to save some function calls.
return new t(data.slice(offset, offset + shape[0] * t.BYTES_PER_ELEMENT));
}
const arr = new Array(shape[0]);
if (shape.length === 2) {
for (let i = 0; i < shape[0]; i++) {
arr[i] = new t(data.slice(offset + shape[1] * i * t.BYTES_PER_ELEMENT, offset + shape[1] * (i + 1) * t.BYTES_PER_ELEMENT));
}
return arr;
}
const nextShape = shape.slice(1);
// Small optimization possible here: this can be precomputed for different levels of depth and passed on.
const mult = nextShape.reduce((x, y) => x * y, 1);
for (let i = 0; i < shape[0]; i++) {
arr[i] = createNestedArray(data, t, nextShape, offset + mult * i * t.BYTES_PER_ELEMENT);
}
return arr;
}
//# sourceMappingURL=index.js.map