array-kit 0.2.10 → 0.2.11
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 +109 -25
- package/package.json +1 -1
package/lib/NDArray.js
CHANGED
|
@@ -43,9 +43,13 @@
|
|
|
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
45
|
reverse: syntactic sugar, like params.order = [ N , ... , 2 , 1 , 0 ]
|
|
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
47
|
stride: (default: 1) the stride of the fastest moving dimension, usually 1 except if there is more dimensions fastest
|
|
48
48
|
dimensions that are removed from the view
|
|
49
|
+
|
|
50
|
+
Not added yet:
|
|
51
|
+
offset: the offset of the first logical element of the ND-array
|
|
52
|
+
dataEnd: the index in the data storage where the ND-array ends, excluded (e.g. the end of the view in the Buffer)
|
|
49
53
|
*/
|
|
50
54
|
function NDArray( dataOrConstructor , sizes , params ) {
|
|
51
55
|
let dimensions , noInit = false ;
|
|
@@ -64,7 +68,9 @@ function NDArray( dataOrConstructor , sizes , params ) {
|
|
|
64
68
|
// Define the size in each axis
|
|
65
69
|
this.sizes = new Array( dimensions ) ;
|
|
66
70
|
|
|
67
|
-
// The full size of the ND-array, each axis-size multiplied
|
|
71
|
+
// The full logical size of the ND-array, each axis-size multiplied
|
|
72
|
+
// /!\ IT'S NOT THE PHYSICAL SIZE /!\
|
|
73
|
+
// See .dataStart and .dataEnd for that.
|
|
68
74
|
this.size = 1 ;
|
|
69
75
|
|
|
70
76
|
// Define the min for each axis, when the ND-array is not zero-based
|
|
@@ -80,9 +86,13 @@ function NDArray( dataOrConstructor , sizes , params ) {
|
|
|
80
86
|
// Define the layout of the array in memory, strides are products of all faster dimensions
|
|
81
87
|
this.strides = new Array( dimensions ) ;
|
|
82
88
|
|
|
83
|
-
//
|
|
89
|
+
// This is the index of the first logical element in the physical data storage
|
|
84
90
|
this.offset = 0 ;
|
|
85
91
|
|
|
92
|
+
// This is physical start and end of the ND-array in the data storage
|
|
93
|
+
this.dataStart = 0 ;
|
|
94
|
+
this.dataEnd = 0 ;
|
|
95
|
+
|
|
86
96
|
// The underlying data store: Array, TypedArray, Buffer... anything that is indexed
|
|
87
97
|
this.data = null ;
|
|
88
98
|
|
|
@@ -120,24 +130,28 @@ NDArray.prototype._init = function( dataOrConstructor , sizes , params = {} ) {
|
|
|
120
130
|
// Compute the stride
|
|
121
131
|
this._initStridesUsingSizes( params.stride ) ;
|
|
122
132
|
|
|
123
|
-
//
|
|
124
|
-
this.
|
|
133
|
+
// The index where the ND-array region starts
|
|
134
|
+
this.dataStart = params.dataStart || 0 ;
|
|
135
|
+
|
|
136
|
+
// Compute data store start and end
|
|
137
|
+
//this._initStartEndFromOffsetSizesStrides() ;
|
|
138
|
+
this._initOffsetEndFromStartSizesStrides() ;
|
|
125
139
|
|
|
126
140
|
// Set or create a new data store
|
|
127
141
|
if ( typeof dataOrConstructor === 'function' ) {
|
|
128
142
|
if ( typeof dataOrConstructor.allocUnsafe === 'function' ) {
|
|
129
143
|
// Detect Node.js's Buffer referencing it (avoid browser compatibility layer)
|
|
130
|
-
this.data = dataOrConstructor.allocUnsafe( this.
|
|
144
|
+
this.data = dataOrConstructor.allocUnsafe( this.dataEnd ) ;
|
|
131
145
|
}
|
|
132
146
|
else {
|
|
133
|
-
this.data = new dataOrConstructor( this.
|
|
147
|
+
this.data = new dataOrConstructor( this.dataEnd ) ;
|
|
134
148
|
}
|
|
135
149
|
}
|
|
136
150
|
else {
|
|
137
151
|
/*
|
|
138
152
|
// Not sure it's good to check the length, since Array is extensible and only rarer TypedArray is fixed
|
|
139
|
-
if ( dataOrConstructor.length < this.
|
|
140
|
-
throw new RangeError( "Provided data store is too small (expecting at least " +
|
|
153
|
+
if ( dataOrConstructor.length < this.dataEnd ) {
|
|
154
|
+
throw new RangeError( "Provided data store is too small (expecting at least " + this.dataEnd + " but got " + dataOrConstructor.length ) ;
|
|
141
155
|
}
|
|
142
156
|
*/
|
|
143
157
|
this.data = dataOrConstructor ;
|
|
@@ -193,9 +207,44 @@ NDArray.prototype._initStridesUsingSizes = function( stride = 1 ) {
|
|
|
193
207
|
}
|
|
194
208
|
} ;
|
|
195
209
|
|
|
210
|
+
// Offset, sizes and strides are not passed as argument, they should already be computed on the instance
|
|
211
|
+
NDArray.prototype._initOffsetEndFromStartSizesStrides = function() {
|
|
212
|
+
this.dataEnd = this.dataStart + 1 ;
|
|
213
|
+
this.offset = this.dataStart ;
|
|
214
|
+
|
|
215
|
+
for ( let d = 0 ; d < this.dimensions ; d ++ ) {
|
|
216
|
+
const shift = this.strides[ d ] * ( this.sizes[ d ] - 1 ) ;
|
|
217
|
+
|
|
218
|
+
if ( shift >= 0 ) {
|
|
219
|
+
this.dataEnd += shift ;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
this.dataEnd -= shift ;
|
|
223
|
+
this.offset -= shift ;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
} ;
|
|
196
227
|
|
|
228
|
+
/* Not sure if it's useful to compute based on offset
|
|
229
|
+
// Offset, sizes and strides are not passed as argument, they should already be computed on the instance
|
|
230
|
+
NDArray.prototype._initStartEndFromOffsetSizesStrides = function() {
|
|
231
|
+
this.dataStart = this.offset ;
|
|
232
|
+
this.dataEnd = this.offset + 1 ;
|
|
197
233
|
|
|
198
|
-
|
|
234
|
+
for ( const d of this.order ) {
|
|
235
|
+
if ( this.strides[ d ] >= 0 ) {
|
|
236
|
+
this.dataEnd += this.strides[ d ] * ( this.sizes[ d ] - 1 ) ;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
this.dataStart += this.strides[ d ] * ( this.sizes[ d ] - 1 ) ;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} ;
|
|
243
|
+
*/
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
// Make the array zero-based, or based on the provided mins vector
|
|
199
248
|
NDArray.prototype.rebase = function( mins ) {
|
|
200
249
|
if ( mins ) {
|
|
201
250
|
for ( let d = 0 ; d < this.dimensions ; d ++ ) {
|
|
@@ -215,8 +264,41 @@ NDArray.prototype.rebase = function( mins ) {
|
|
|
215
264
|
|
|
216
265
|
|
|
217
266
|
|
|
267
|
+
// Translate, moving the coordinates of each value
|
|
268
|
+
NDArray.prototype.translate = function( translation ) {
|
|
269
|
+
for ( let d = 0 ; d < this.dimensions ; d ++ ) {
|
|
270
|
+
this.mins[ d ] += translation[ d ] ;
|
|
271
|
+
this.maxs[ d ] += translation[ d ] ;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return this ;
|
|
275
|
+
} ;
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
// Flip coordinates along a dimension, but preserving min and max
|
|
280
|
+
NDArray.prototype.flip = function( dimension ) {
|
|
281
|
+
if ( dimension < 0 || dimension >= this.dimensions ) {
|
|
282
|
+
throw new RangeError( "Bad dimension, should be between [0," + this.dimensions + ") but got: " + dimension ) ;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
this.strides[ dimension ] *= - 1 ;
|
|
286
|
+
this._initOffsetEndFromStartSizesStrides() ;
|
|
287
|
+
|
|
288
|
+
return this ;
|
|
289
|
+
} ;
|
|
290
|
+
|
|
291
|
+
// Flip coordinates along all dimensions at once, but preserving min and max
|
|
292
|
+
NDArray.prototype.flipAll = function() {
|
|
293
|
+
for ( let d = 0 ; d < this.dimensions ; d ++ ) { this.strides[ d ] *= - 1 ; }
|
|
294
|
+
this._initOffsetEndFromStartSizesStrides() ;
|
|
295
|
+
return this ;
|
|
296
|
+
} ;
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
|
|
218
300
|
// Create a new clone of the current NDArray with the same geometry but an empty (but ready) data
|
|
219
|
-
// The new data is limited to the used size (
|
|
301
|
+
// The new data is limited to the used size (dataStart = 0).
|
|
220
302
|
NDArray.prototype._cloneGeometry = function() {
|
|
221
303
|
const newNDArray = new NDArray( this.dimensions ) ;
|
|
222
304
|
|
|
@@ -228,6 +310,10 @@ NDArray.prototype._cloneGeometry = function() {
|
|
|
228
310
|
Object.assign( newNDArray.maxs , this.maxs ) ;
|
|
229
311
|
Object.assign( newNDArray.strides , this.strides ) ;
|
|
230
312
|
|
|
313
|
+
// Start at index=0 in the physical storage
|
|
314
|
+
newNDArray.offset = this.offset - this.dataStart ;
|
|
315
|
+
newNDArray.dataEnd = this.dataEnd - this.dataStart ;
|
|
316
|
+
|
|
231
317
|
return newNDArray ;
|
|
232
318
|
} ;
|
|
233
319
|
|
|
@@ -240,6 +326,7 @@ NDArray.prototype._resizeGeometry = function( mins , maxs ) {
|
|
|
240
326
|
Object.assign( newNDArray.order , this.order ) ;
|
|
241
327
|
newNDArray._initSizesFromMinsMaxs( mins , maxs ) ;
|
|
242
328
|
newNDArray._initStridesUsingSizes() ;
|
|
329
|
+
newNDArray._initOffsetEndFromStartSizesStrides() ;
|
|
243
330
|
|
|
244
331
|
return newNDArray ;
|
|
245
332
|
} ;
|
|
@@ -247,7 +334,7 @@ NDArray.prototype._resizeGeometry = function( mins , maxs ) {
|
|
|
247
334
|
|
|
248
335
|
|
|
249
336
|
// Create a new data store of the same type
|
|
250
|
-
NDArray.prototype._newStorageOfTheSameKind = function( size = this.
|
|
337
|
+
NDArray.prototype._newStorageOfTheSameKind = function( size = this.dataEnd - this.dataStart ) {
|
|
251
338
|
if ( typeof this.data?.constructor?.allocUnsafe === 'function' ) {
|
|
252
339
|
// Detect Node.js's Buffer referencing it (avoid browser compatibility layer)
|
|
253
340
|
return this.data.constructor.allocUnsafe( size ) ;
|
|
@@ -308,8 +395,8 @@ NDArray.prototype._getCoords = function( index , coords = new Array( this.dimens
|
|
|
308
395
|
|
|
309
396
|
NDArray.prototype._getCoordsCheck =
|
|
310
397
|
NDArray.prototype.getCoords = function( index , coords ) {
|
|
311
|
-
if ( index < this.
|
|
312
|
-
throw new RangeError( "Index " + index + " out of bounds, which is [" + this.
|
|
398
|
+
if ( index < this.dataStart || index >= this.dataEnd ) {
|
|
399
|
+
throw new RangeError( "Index " + index + " out of bounds, which is [" + this.dataStart + "," + this.dataEnd + ")" ) ;
|
|
313
400
|
}
|
|
314
401
|
|
|
315
402
|
coords = coords ?? new Array( this.dimensions ) ;
|
|
@@ -453,14 +540,13 @@ NDArray.prototype._forEach = function( callback ) {
|
|
|
453
540
|
// Usually =1, but later we could group neighbours (e.g. for RGBA images => stride0 = 4)
|
|
454
541
|
const stride0 = this.strides[ this.order[ 0 ] ] ;
|
|
455
542
|
const coords = Array.from( this.mins ) ;
|
|
456
|
-
|
|
457
|
-
let index = this.offset ;
|
|
543
|
+
let index = this.dataStart ;
|
|
458
544
|
|
|
459
545
|
for ( ;; ) {
|
|
460
546
|
callback( this.data[ index ] , coords , index , this ) ;
|
|
461
547
|
|
|
462
548
|
index += stride0 ;
|
|
463
|
-
if ( index >= dataEnd ) { return ; }
|
|
549
|
+
if ( index >= this.dataEnd ) { return ; }
|
|
464
550
|
|
|
465
551
|
for ( let i = 0 ; i < this.dimensions ; i ++ ) {
|
|
466
552
|
const d = this.order[ i ] ;
|
|
@@ -845,10 +931,8 @@ NDArray.prototype._eachVectorIndexInRegionBackward = function*( strideStartCoord
|
|
|
845
931
|
|
|
846
932
|
|
|
847
933
|
NDArray.prototype.fill = function( value ) {
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
}
|
|
851
|
-
|
|
934
|
+
const stride0 = this.strides[ this.order[ 0 ] ] ;
|
|
935
|
+
for ( let i = this.dataStart ; i < this.dataEnd ; i += stride0 ) { this.data[ i ] = value ; }
|
|
852
936
|
return this ;
|
|
853
937
|
} ;
|
|
854
938
|
|
|
@@ -892,7 +976,7 @@ NDArray.prototype.map = function( callback ) {
|
|
|
892
976
|
newNDArray.data = this._newStorageOfTheSameKind() ;
|
|
893
977
|
|
|
894
978
|
this._forEach( ( value , coords , index ) => {
|
|
895
|
-
newNDArray.data[ index - this.
|
|
979
|
+
newNDArray.data[ index - this.dataStart ] = callback( value , coords , index , this ) ;
|
|
896
980
|
} ) ;
|
|
897
981
|
|
|
898
982
|
return newNDArray ;
|
|
@@ -913,7 +997,7 @@ NDArray.prototype.mapInRegion = function( mins , maxs , callback ) {
|
|
|
913
997
|
|
|
914
998
|
this._checkRegion( mins , maxs ) ;
|
|
915
999
|
const newNDArray = this._resizeGeometry( mins , maxs ) ;
|
|
916
|
-
newNDArray.data = this._newStorageOfTheSameKind( newNDArray.
|
|
1000
|
+
newNDArray.data = this._newStorageOfTheSameKind( newNDArray.dataEnd ) ;
|
|
917
1001
|
|
|
918
1002
|
this._dualStepCallback( newNDArray , mins , maxs , mins , maxs , callback ) ;
|
|
919
1003
|
|
|
@@ -944,7 +1028,7 @@ NDArray.prototype.mapVectorInRegion = function( mins , maxs , callback ) {
|
|
|
944
1028
|
|
|
945
1029
|
this._checkRegion( strideStartCoords , strideEndCoords , vectorDimension ) ;
|
|
946
1030
|
const newNDArray = this._resizeGeometry( strideStartCoords , strideEndCoords ) ;
|
|
947
|
-
newNDArray.data = this._newStorageOfTheSameKind( newNDArray.
|
|
1031
|
+
newNDArray.data = this._newStorageOfTheSameKind( newNDArray.dataEnd ) ;
|
|
948
1032
|
|
|
949
1033
|
this._dualStepVectorCallback( newNDArray , strideStartCoords , strideEndCoords , strideStartCoords , strideEndCoords , vectorDimension , callback ) ;
|
|
950
1034
|
|
|
@@ -1029,7 +1113,7 @@ NDArray.prototype.extractRegion = function( mins , maxs ) {
|
|
|
1029
1113
|
|
|
1030
1114
|
this._checkRegion( mins , maxs ) ;
|
|
1031
1115
|
const newNDArray = this._resizeGeometry( mins , maxs ) ;
|
|
1032
|
-
newNDArray.data = this._newStorageOfTheSameKind( newNDArray.
|
|
1116
|
+
newNDArray.data = this._newStorageOfTheSameKind( newNDArray.dataEnd ) ;
|
|
1033
1117
|
|
|
1034
1118
|
this._dualStepCopy( newNDArray , mins , maxs , mins , maxs ) ;
|
|
1035
1119
|
|