array-kit 0.2.12 → 0.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/NDArray.js +202 -38
- package/package.json +1 -1
package/lib/NDArray.js
CHANGED
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
params: an object of optional parameters, where:
|
|
43
43
|
order: array of coord order stored in the storage, by default [ 0 , 1 , 2 , ..., N ], for 2D it's row-first,
|
|
44
44
|
if you want column-first, use [ 1 , 0 ], or params.reverse = true
|
|
45
|
-
reverse: syntactic sugar, like params.order = [ N , ... , 2 , 1 , 0 ]
|
|
45
|
+
reverse: syntactic sugar, like params.order = [ N , ... , 2 , 1 , 0 ], means that the slowest moving dimension comes first
|
|
46
46
|
dataStart: the index in the data storage where the ND-array starts (e.g. the start of the view in the Buffer)
|
|
47
|
-
stride: (default: 1) the stride of the fastest moving dimension, usually 1 except if there is
|
|
48
|
-
|
|
47
|
+
stride: (default: 1) the stride of the fastest moving dimension, usually 1 except if there is faster dimensions
|
|
48
|
+
that are removed from the view
|
|
49
49
|
|
|
50
50
|
Not added yet:
|
|
51
51
|
offset: the offset of the first logical element of the ND-array
|
|
@@ -92,11 +92,15 @@ function NDArray( dataOrConstructor , sizes , params ) {
|
|
|
92
92
|
// This is the index of the first logical element in the physical data storage
|
|
93
93
|
this.offset = 0 ;
|
|
94
94
|
|
|
95
|
+
// True if the physical backend is contiguous, that can speed up some operation like .fill()
|
|
96
|
+
this.isContiguous = true ;
|
|
97
|
+
|
|
95
98
|
// This is physical start and end of the ND-array in the data storage
|
|
96
99
|
this.dataStart = 0 ;
|
|
97
100
|
this.dataEnd = 0 ;
|
|
98
101
|
|
|
99
|
-
// The underlying data store
|
|
102
|
+
// The underlying physical data store backend.
|
|
103
|
+
// Can be: Array, TypedArray, Buffer... anything that is indexed.
|
|
100
104
|
this.data = null ;
|
|
101
105
|
|
|
102
106
|
if ( ! noInit ) {
|
|
@@ -131,7 +135,7 @@ NDArray.prototype._init = function( dataOrConstructor , sizes , params = {} ) {
|
|
|
131
135
|
}
|
|
132
136
|
|
|
133
137
|
// Compute the stride
|
|
134
|
-
this.
|
|
138
|
+
this._initStridesFromSizes( params.stride ) ;
|
|
135
139
|
|
|
136
140
|
// The index where the ND-array region starts
|
|
137
141
|
this.dataStart = params.dataStart || 0 ;
|
|
@@ -143,7 +147,7 @@ NDArray.prototype._init = function( dataOrConstructor , sizes , params = {} ) {
|
|
|
143
147
|
// Set or create a new data store
|
|
144
148
|
if ( typeof dataOrConstructor === 'function' ) {
|
|
145
149
|
if ( typeof dataOrConstructor.allocUnsafe === 'function' ) {
|
|
146
|
-
// Detect Node.js's Buffer referencing it (avoid browser compatibility layer)
|
|
150
|
+
// Detect Node.js's Buffer without referencing it (avoid browser compatibility layer)
|
|
147
151
|
this.data = dataOrConstructor.allocUnsafe( this.dataEnd ) ;
|
|
148
152
|
}
|
|
149
153
|
else {
|
|
@@ -164,6 +168,8 @@ NDArray.prototype._init = function( dataOrConstructor , sizes , params = {} ) {
|
|
|
164
168
|
// Fragmented init, because it can be re-use by various cloning things
|
|
165
169
|
|
|
166
170
|
NDArray.prototype._initMinsMaxsSizesFromRegion = function( region ) {
|
|
171
|
+
this.size = 1 ;
|
|
172
|
+
|
|
167
173
|
for ( let d = 0 ; d < this.dimensions ; d ++ ) {
|
|
168
174
|
this.mins[ d ] = region[ d ][ 0 ] ;
|
|
169
175
|
this.maxs[ d ] = region[ d ][ 1 ] ;
|
|
@@ -203,14 +209,23 @@ NDArray.prototype._initMinsMaxsFromSizes = function( sizes ) {
|
|
|
203
209
|
} ;
|
|
204
210
|
|
|
205
211
|
// Sizes is not passed as argument, it should already be computed on the instance
|
|
206
|
-
NDArray.prototype.
|
|
212
|
+
NDArray.prototype._initStridesFromSizes = function( nextStride = 1 ) {
|
|
213
|
+
// It's always contiguous when strides start at 1
|
|
214
|
+
this.isContiguous = nextStride === 1 ;
|
|
215
|
+
|
|
207
216
|
for ( const d of this.order ) {
|
|
208
|
-
this.strides[ d ] =
|
|
209
|
-
this.backStrides[ d ] = ( 1 - this.sizes[ d ] ) *
|
|
210
|
-
|
|
217
|
+
this.strides[ d ] = nextStride ;
|
|
218
|
+
this.backStrides[ d ] = ( 1 - this.sizes[ d ] ) * nextStride ;
|
|
219
|
+
nextStride *= this.sizes[ d ] ;
|
|
211
220
|
}
|
|
212
221
|
} ;
|
|
213
222
|
|
|
223
|
+
// Strides is not passed as argument, it should already be computed on the instance
|
|
224
|
+
NDArray.prototype._initOrderFromStrides = function() {
|
|
225
|
+
this.order = Array.from( { length: this.strides.length } , ( v , i ) => i ) ;
|
|
226
|
+
this.order.sort( ( a , b ) => this.strides[ a ] - this.strides[ b ] ) ;
|
|
227
|
+
} ;
|
|
228
|
+
|
|
214
229
|
// Offset, sizes and strides are not passed as argument, they should already be computed on the instance
|
|
215
230
|
NDArray.prototype._initOffsetEndFromStartSizesStrides = function() {
|
|
216
231
|
this.dataEnd = this.dataStart + 1 ;
|
|
@@ -229,7 +244,6 @@ NDArray.prototype._initOffsetEndFromStartSizesStrides = function() {
|
|
|
229
244
|
}
|
|
230
245
|
} ;
|
|
231
246
|
|
|
232
|
-
/* Not sure if it's useful to compute based on offset
|
|
233
247
|
// Offset, sizes and strides are not passed as argument, they should already be computed on the instance
|
|
234
248
|
NDArray.prototype._initStartEndFromOffsetSizesStrides = function() {
|
|
235
249
|
this.dataStart = this.offset ;
|
|
@@ -244,7 +258,62 @@ NDArray.prototype._initStartEndFromOffsetSizesStrides = function() {
|
|
|
244
258
|
}
|
|
245
259
|
}
|
|
246
260
|
} ;
|
|
247
|
-
|
|
261
|
+
|
|
262
|
+
// Init .isContiguous based on strides and sizes.
|
|
263
|
+
// Usually only called for views, because if ._initStridesFromSizes() is called it already set it to the right value.
|
|
264
|
+
NDArray.prototype._initIsContiguousFromStridesSizes = function() {
|
|
265
|
+
let nextStride = 1 ;
|
|
266
|
+
|
|
267
|
+
for ( const d of this.order ) {
|
|
268
|
+
const absStride = Math.abs( this.strides[ d ] ) ;
|
|
269
|
+
|
|
270
|
+
if ( absStride !== nextStride ) {
|
|
271
|
+
this.isContiguous = false ;
|
|
272
|
+
return ;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
nextStride *= absStride ;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
this.isContiguous = true ;
|
|
279
|
+
} ;
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
// Create a new data back-end from another one
|
|
284
|
+
// sliceStart and sliceEnd are optional
|
|
285
|
+
NDArray.prototype._createDataFrom = function( data , sliceStart = 0 , sliceEnd = data.length ) {
|
|
286
|
+
if ( Array.isArray( data ) ) {
|
|
287
|
+
this.data = data.slice( sliceStart , sliceEnd ) ;
|
|
288
|
+
}
|
|
289
|
+
else if ( typeof data.constructor.allocUnsafe === 'function' ) {
|
|
290
|
+
// Detect Node.js's Buffer without referencing it (avoid browser compatibility layer)
|
|
291
|
+
this.data = data.constructor.allocUnsafe( sliceEnd - sliceStart ) ;
|
|
292
|
+
data.copy( this.data , 0 , sliceStart , sliceEnd ) ;
|
|
293
|
+
}
|
|
294
|
+
else if ( typeof data.slice === 'function' ) {
|
|
295
|
+
this.data = data.slice( sliceStart , sliceEnd ) ;
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
this.data = new data.constructor( data.length ) ;
|
|
299
|
+
|
|
300
|
+
for ( let src = sliceStart , dst = 0 ; src < sliceEnd ; src ++ , dst ++ ) {
|
|
301
|
+
this.data[ dst ] = data[ src ] ;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
} ;
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
// Create a new data store of the same type
|
|
309
|
+
NDArray.prototype._newStorageOfTheSameKind = function( size = this.dataEnd - this.dataStart ) {
|
|
310
|
+
if ( typeof this.data?.constructor?.allocUnsafe === 'function' ) {
|
|
311
|
+
// Detect Node.js's Buffer referencing it (avoid browser compatibility layer)
|
|
312
|
+
return this.data.constructor.allocUnsafe( size ) ;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return new this.data.constructor( size ) ;
|
|
316
|
+
} ;
|
|
248
317
|
|
|
249
318
|
|
|
250
319
|
|
|
@@ -306,9 +375,100 @@ NDArray.prototype.flipAll = function() {
|
|
|
306
375
|
|
|
307
376
|
|
|
308
377
|
|
|
378
|
+
// Transpose: swap coordinates orders
|
|
379
|
+
NDArray.prototype.transpose = function( ... dimensionList ) {
|
|
380
|
+
// It's not possible to swap because each dimension look up for the source at its original place
|
|
381
|
+
const dMax = Math.min( dimensionList.length , this.dimensions ) ,
|
|
382
|
+
sizes = new Array( this.dimensions ) ,
|
|
383
|
+
mins = new Array( this.dimensions ) ,
|
|
384
|
+
maxs = new Array( this.dimensions ) ,
|
|
385
|
+
order = new Array( this.dimensions ) ,
|
|
386
|
+
strides = new Array( this.dimensions ) ,
|
|
387
|
+
backStrides = new Array( this.dimensions ) ;
|
|
388
|
+
|
|
389
|
+
for ( let d = 0 ; d < dMax ; d ++ ) {
|
|
390
|
+
const fromD = dimensionList[ d ] ;
|
|
391
|
+
sizes[ d ] = this.sizes[ fromD ] ;
|
|
392
|
+
mins[ d ] = this.mins[ fromD ] ;
|
|
393
|
+
maxs[ d ] = this.maxs[ fromD ] ;
|
|
394
|
+
order[ d ] = this.order[ fromD ] ;
|
|
395
|
+
strides[ d ] = this.strides[ fromD ] ;
|
|
396
|
+
backStrides[ d ] = this.backStrides[ fromD ] ;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
this.sizes = sizes ;
|
|
400
|
+
this.mins = mins ;
|
|
401
|
+
this.maxs = maxs ;
|
|
402
|
+
this.order = order ;
|
|
403
|
+
this.strides = strides ;
|
|
404
|
+
this.backStrides = backStrides ;
|
|
405
|
+
|
|
406
|
+
return this ;
|
|
407
|
+
} ;
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
/*
|
|
412
|
+
Fix some axis and reduce the number of dimensions.
|
|
413
|
+
Like .pick() in the "ndarray" package.
|
|
414
|
+
|
|
415
|
+
Examples:
|
|
416
|
+
.select( null , 3 , null ) fix the second coordinate to 3, the ND-array will now have 2 dimensions instead of 3
|
|
417
|
+
.select( 1 , 3 , null ) fix the first and second coordinates respectively to 1 and 3, the ND-array will now have 1 dimensions instead of 3
|
|
418
|
+
*/
|
|
419
|
+
NDArray.prototype.select = function( ... coords ) {
|
|
420
|
+
this.size = 1 ;
|
|
421
|
+
|
|
422
|
+
// dimensions should be cached because it's modified along the way
|
|
423
|
+
for ( let d = 0 , newD = 0 , dimensions = this.dimensions ; d < dimensions ; d ++ ) {
|
|
424
|
+
const coord = coords[ d ] ;
|
|
425
|
+
|
|
426
|
+
if ( coord === null || coord === undefined ) {
|
|
427
|
+
this.size *= this.sizes[ newD ] ;
|
|
428
|
+
newD ++ ;
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
this.offset += ( coord - this.mins[ newD ] ) * this.strides[ newD ] ;
|
|
432
|
+
this.sizes.splice( newD , 1 ) ;
|
|
433
|
+
this.mins.splice( newD , 1 ) ;
|
|
434
|
+
this.maxs.splice( newD , 1 ) ;
|
|
435
|
+
this.strides.splice( newD , 1 ) ;
|
|
436
|
+
this.backStrides.splice( newD , 1 ) ;
|
|
437
|
+
this.dimensions -- ;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
this._initOrderFromStrides() ;
|
|
442
|
+
this._initStartEndFromOffsetSizesStrides() ;
|
|
443
|
+
this._initIsContiguousFromStridesSizes() ;
|
|
444
|
+
|
|
445
|
+
return this ;
|
|
446
|
+
} ;
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
// Clone only the view, the data storage is the same
|
|
451
|
+
NDArray.prototype.cloneView =
|
|
452
|
+
NDArray.prototype.view = function() {
|
|
453
|
+
const newNDArray = this._cloneLogical( true ) ;
|
|
454
|
+
newNDArray.data = this.data ;
|
|
455
|
+
return newNDArray ;
|
|
456
|
+
} ;
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
// Full clone
|
|
461
|
+
NDArray.prototype.clone = function() {
|
|
462
|
+
const newNDArray = this._cloneLogical() ;
|
|
463
|
+
newNDArray._createDataFrom( this.data , this.dataStart , this.dataEnd ) ;
|
|
464
|
+
return newNDArray ;
|
|
465
|
+
} ;
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
|
|
309
469
|
// Create a new clone of the current NDArray with the same geometry but an empty (but ready) data
|
|
310
|
-
// The new data is limited to the used size (dataStart = 0).
|
|
311
|
-
NDArray.prototype.
|
|
470
|
+
// The new data is limited to the used size (dataStart = 0) except if keepDataStart is set to true.
|
|
471
|
+
NDArray.prototype._cloneLogical = function( keepDataStart = false ) {
|
|
312
472
|
const newNDArray = new NDArray( this.dimensions ) ;
|
|
313
473
|
|
|
314
474
|
// Copy geometry
|
|
@@ -320,9 +480,16 @@ NDArray.prototype._cloneGeometry = function() {
|
|
|
320
480
|
Object.assign( newNDArray.strides , this.strides ) ;
|
|
321
481
|
Object.assign( newNDArray.backStrides , this.backStrides ) ;
|
|
322
482
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
483
|
+
if ( keepDataStart ) {
|
|
484
|
+
newNDArray.dataStart = this.dataStart ;
|
|
485
|
+
newNDArray.dataEnd = this.dataEnd ;
|
|
486
|
+
newNDArray.offset = this.offset ;
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
// Start at index=0 in the physical storage
|
|
490
|
+
newNDArray.offset = this.offset - this.dataStart ;
|
|
491
|
+
newNDArray.dataEnd = this.dataEnd - this.dataStart ;
|
|
492
|
+
}
|
|
326
493
|
|
|
327
494
|
return newNDArray ;
|
|
328
495
|
} ;
|
|
@@ -330,12 +497,12 @@ NDArray.prototype._cloneGeometry = function() {
|
|
|
330
497
|
|
|
331
498
|
|
|
332
499
|
// Clone with a modified geometry
|
|
333
|
-
NDArray.prototype.
|
|
500
|
+
NDArray.prototype._resizeLogical = function( mins , maxs ) {
|
|
334
501
|
const newNDArray = new NDArray( this.dimensions ) ;
|
|
335
502
|
|
|
336
503
|
Object.assign( newNDArray.order , this.order ) ;
|
|
337
504
|
newNDArray._initSizesFromMinsMaxs( mins , maxs ) ;
|
|
338
|
-
newNDArray.
|
|
505
|
+
newNDArray._initStridesFromSizes() ;
|
|
339
506
|
newNDArray._initOffsetEndFromStartSizesStrides() ;
|
|
340
507
|
|
|
341
508
|
return newNDArray ;
|
|
@@ -343,18 +510,6 @@ NDArray.prototype._resizeGeometry = function( mins , maxs ) {
|
|
|
343
510
|
|
|
344
511
|
|
|
345
512
|
|
|
346
|
-
// Create a new data store of the same type
|
|
347
|
-
NDArray.prototype._newStorageOfTheSameKind = function( size = this.dataEnd - this.dataStart ) {
|
|
348
|
-
if ( typeof this.data?.constructor?.allocUnsafe === 'function' ) {
|
|
349
|
-
// Detect Node.js's Buffer referencing it (avoid browser compatibility layer)
|
|
350
|
-
return this.data.constructor.allocUnsafe( size ) ;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
return new this.data.constructor( size ) ;
|
|
354
|
-
} ;
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
513
|
// Internal version without any unnessessary test
|
|
359
514
|
// Classic strided array access is:
|
|
360
515
|
// index = coords[ 0 ] * strides[ 0 ] + coords[ 1 ] * strides[ 1 ] + ... + coords[ n ] * strides[ n ]
|
|
@@ -367,7 +522,6 @@ NDArray.prototype._getIndex = function( coords ) {
|
|
|
367
522
|
NDArray.prototype._getIndexCheck = function( coords ) {
|
|
368
523
|
let index = this.offset ;
|
|
369
524
|
|
|
370
|
-
//for ( const d of this.order ) {
|
|
371
525
|
for ( let d = 0 ; d < this.dimensions ; d ++ ) {
|
|
372
526
|
const c = coords[ d ] ;
|
|
373
527
|
|
|
@@ -942,8 +1096,18 @@ NDArray.prototype._eachVectorIndexInRegionBackward = function*( strideStartCoord
|
|
|
942
1096
|
|
|
943
1097
|
|
|
944
1098
|
NDArray.prototype.fill = function( value ) {
|
|
945
|
-
|
|
946
|
-
|
|
1099
|
+
if ( this.isContiguous ) {
|
|
1100
|
+
// Fast path: iterate directly on the physical data
|
|
1101
|
+
for ( let i = this.dataStart ; i < this.dataEnd ; i ++ ) {
|
|
1102
|
+
this.data[ i ] = value ;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
else {
|
|
1106
|
+
for ( const { index } of this._each() ) {
|
|
1107
|
+
this.data[ index ] = value ;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
947
1111
|
return this ;
|
|
948
1112
|
} ;
|
|
949
1113
|
|
|
@@ -983,7 +1147,7 @@ NDArray.prototype.fillVectorInRegion = function( mins , maxs , vector ) {
|
|
|
983
1147
|
The classic .map(), returning a new ND-Array with its own data storage.
|
|
984
1148
|
*/
|
|
985
1149
|
NDArray.prototype.map = function( callback ) {
|
|
986
|
-
const newNDArray = this.
|
|
1150
|
+
const newNDArray = this._cloneLogical() ;
|
|
987
1151
|
newNDArray.data = this._newStorageOfTheSameKind() ;
|
|
988
1152
|
|
|
989
1153
|
this._forEach( ( value , coords , index ) => {
|
|
@@ -1007,7 +1171,7 @@ NDArray.prototype.mapInRegion = function( mins , maxs , callback ) {
|
|
|
1007
1171
|
}
|
|
1008
1172
|
|
|
1009
1173
|
this._checkRegion( mins , maxs ) ;
|
|
1010
|
-
const newNDArray = this.
|
|
1174
|
+
const newNDArray = this._resizeLogical( mins , maxs ) ;
|
|
1011
1175
|
newNDArray.data = this._newStorageOfTheSameKind( newNDArray.dataEnd ) ;
|
|
1012
1176
|
|
|
1013
1177
|
this._dualStepCallback( newNDArray , mins , maxs , mins , maxs , callback ) ;
|
|
@@ -1038,7 +1202,7 @@ NDArray.prototype.mapVectorInRegion = function( mins , maxs , callback ) {
|
|
|
1038
1202
|
strideEndCoords[ vectorDimension ] = this.maxs[ vectorDimension ] ;
|
|
1039
1203
|
|
|
1040
1204
|
this._checkRegion( strideStartCoords , strideEndCoords , vectorDimension ) ;
|
|
1041
|
-
const newNDArray = this.
|
|
1205
|
+
const newNDArray = this._resizeLogical( strideStartCoords , strideEndCoords ) ;
|
|
1042
1206
|
newNDArray.data = this._newStorageOfTheSameKind( newNDArray.dataEnd ) ;
|
|
1043
1207
|
|
|
1044
1208
|
this._dualStepVectorCallback( newNDArray , strideStartCoords , strideEndCoords , strideStartCoords , strideEndCoords , vectorDimension , callback ) ;
|
|
@@ -1123,7 +1287,7 @@ NDArray.prototype.extractRegion = function( mins , maxs ) {
|
|
|
1123
1287
|
}
|
|
1124
1288
|
|
|
1125
1289
|
this._checkRegion( mins , maxs ) ;
|
|
1126
|
-
const newNDArray = this.
|
|
1290
|
+
const newNDArray = this._resizeLogical( mins , maxs ) ;
|
|
1127
1291
|
newNDArray.data = this._newStorageOfTheSameKind( newNDArray.dataEnd ) ;
|
|
1128
1292
|
|
|
1129
1293
|
this._dualStepCopy( newNDArray , mins , maxs , mins , maxs ) ;
|