array-kit 0.2.10 → 0.2.12

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.
Files changed (2) hide show
  1. package/lib/NDArray.js +150 -55
  2. 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
- offset: the offset if the ND-array does not start at the begining of the data storage (e.g. create a view of a Buffer)
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,16 @@ 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
- // When the store is only partially used, this is the offset in the store natural index
89
+ // Use to speed up the whole array iteration
90
+ this.backStrides = new Array( dimensions ) ;
91
+
92
+ // This is the index of the first logical element in the physical data storage
84
93
  this.offset = 0 ;
85
94
 
95
+ // This is physical start and end of the ND-array in the data storage
96
+ this.dataStart = 0 ;
97
+ this.dataEnd = 0 ;
98
+
86
99
  // The underlying data store: Array, TypedArray, Buffer... anything that is indexed
87
100
  this.data = null ;
88
101
 
@@ -120,24 +133,28 @@ NDArray.prototype._init = function( dataOrConstructor , sizes , params = {} ) {
120
133
  // Compute the stride
121
134
  this._initStridesUsingSizes( params.stride ) ;
122
135
 
123
- // Data store start and end
124
- this.offset = params.offset || 0 ;
136
+ // The index where the ND-array region starts
137
+ this.dataStart = params.dataStart || 0 ;
138
+
139
+ // Compute data store start and end
140
+ //this._initStartEndFromOffsetSizesStrides() ;
141
+ this._initOffsetEndFromStartSizesStrides() ;
125
142
 
126
143
  // Set or create a new data store
127
144
  if ( typeof dataOrConstructor === 'function' ) {
128
145
  if ( typeof dataOrConstructor.allocUnsafe === 'function' ) {
129
146
  // Detect Node.js's Buffer referencing it (avoid browser compatibility layer)
130
- this.data = dataOrConstructor.allocUnsafe( this.offset + this.size ) ;
147
+ this.data = dataOrConstructor.allocUnsafe( this.dataEnd ) ;
131
148
  }
132
149
  else {
133
- this.data = new dataOrConstructor( this.offset + this.size ) ;
150
+ this.data = new dataOrConstructor( this.dataEnd ) ;
134
151
  }
135
152
  }
136
153
  else {
137
154
  /*
138
155
  // Not sure it's good to check the length, since Array is extensible and only rarer TypedArray is fixed
139
- if ( dataOrConstructor.length < this.offset + this.size ) {
140
- throw new RangeError( "Provided data store is too small (expecting at least " + ( this.offset + this.size ) + " but got " + dataOrConstructor.length ) ;
156
+ if ( dataOrConstructor.length < this.dataEnd ) {
157
+ throw new RangeError( "Provided data store is too small (expecting at least " + this.dataEnd + " but got " + dataOrConstructor.length ) ;
141
158
  }
142
159
  */
143
160
  this.data = dataOrConstructor ;
@@ -189,13 +206,49 @@ NDArray.prototype._initMinsMaxsFromSizes = function( sizes ) {
189
206
  NDArray.prototype._initStridesUsingSizes = function( stride = 1 ) {
190
207
  for ( const d of this.order ) {
191
208
  this.strides[ d ] = stride ;
209
+ this.backStrides[ d ] = ( 1 - this.sizes[ d ] ) * stride ;
192
210
  stride *= this.sizes[ d ] ;
193
211
  }
194
212
  } ;
195
213
 
214
+ // Offset, sizes and strides are not passed as argument, they should already be computed on the instance
215
+ NDArray.prototype._initOffsetEndFromStartSizesStrides = function() {
216
+ this.dataEnd = this.dataStart + 1 ;
217
+ this.offset = this.dataStart ;
218
+
219
+ for ( let d = 0 ; d < this.dimensions ; d ++ ) {
220
+ const shift = this.strides[ d ] * ( this.sizes[ d ] - 1 ) ;
221
+
222
+ if ( shift >= 0 ) {
223
+ this.dataEnd += shift ;
224
+ }
225
+ else {
226
+ this.dataEnd -= shift ;
227
+ this.offset -= shift ;
228
+ }
229
+ }
230
+ } ;
231
+
232
+ /* Not sure if it's useful to compute based on offset
233
+ // Offset, sizes and strides are not passed as argument, they should already be computed on the instance
234
+ NDArray.prototype._initStartEndFromOffsetSizesStrides = function() {
235
+ this.dataStart = this.offset ;
236
+ this.dataEnd = this.offset + 1 ;
237
+
238
+ for ( const d of this.order ) {
239
+ if ( this.strides[ d ] >= 0 ) {
240
+ this.dataEnd += this.strides[ d ] * ( this.sizes[ d ] - 1 ) ;
241
+ }
242
+ else {
243
+ this.dataStart += this.strides[ d ] * ( this.sizes[ d ] - 1 ) ;
244
+ }
245
+ }
246
+ } ;
247
+ */
248
+
196
249
 
197
250
 
198
- // Make the array zero-based, or based to the provided mins vector
251
+ // Make the array zero-based, or based on the provided mins vector
199
252
  NDArray.prototype.rebase = function( mins ) {
200
253
  if ( mins ) {
201
254
  for ( let d = 0 ; d < this.dimensions ; d ++ ) {
@@ -215,8 +268,46 @@ NDArray.prototype.rebase = function( mins ) {
215
268
 
216
269
 
217
270
 
271
+ // Translate, moving the coordinates of each value
272
+ NDArray.prototype.translate = function( translation ) {
273
+ for ( let d = 0 ; d < this.dimensions ; d ++ ) {
274
+ this.mins[ d ] += translation[ d ] ;
275
+ this.maxs[ d ] += translation[ d ] ;
276
+ }
277
+
278
+ return this ;
279
+ } ;
280
+
281
+
282
+
283
+ // Flip coordinates along a dimension, but preserving min and max
284
+ NDArray.prototype.flip = function( dimension ) {
285
+ if ( dimension < 0 || dimension >= this.dimensions ) {
286
+ throw new RangeError( "Bad dimension, should be between [0," + this.dimensions + ") but got: " + dimension ) ;
287
+ }
288
+
289
+ this.strides[ dimension ] *= - 1 ;
290
+ this.backStrides[ dimension ] *= - 1 ;
291
+ this._initOffsetEndFromStartSizesStrides() ;
292
+
293
+ return this ;
294
+ } ;
295
+
296
+ // Flip coordinates along all dimensions at once, but preserving min and max
297
+ NDArray.prototype.flipAll = function() {
298
+ for ( let d = 0 ; d < this.dimensions ; d ++ ) {
299
+ this.strides[ d ] *= - 1 ;
300
+ this.backStrides[ d ] *= - 1 ;
301
+ }
302
+
303
+ this._initOffsetEndFromStartSizesStrides() ;
304
+ return this ;
305
+ } ;
306
+
307
+
308
+
218
309
  // 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 (offset = 0).
310
+ // The new data is limited to the used size (dataStart = 0).
220
311
  NDArray.prototype._cloneGeometry = function() {
221
312
  const newNDArray = new NDArray( this.dimensions ) ;
222
313
 
@@ -227,6 +318,11 @@ NDArray.prototype._cloneGeometry = function() {
227
318
  Object.assign( newNDArray.mins , this.mins ) ;
228
319
  Object.assign( newNDArray.maxs , this.maxs ) ;
229
320
  Object.assign( newNDArray.strides , this.strides ) ;
321
+ Object.assign( newNDArray.backStrides , this.backStrides ) ;
322
+
323
+ // Start at index=0 in the physical storage
324
+ newNDArray.offset = this.offset - this.dataStart ;
325
+ newNDArray.dataEnd = this.dataEnd - this.dataStart ;
230
326
 
231
327
  return newNDArray ;
232
328
  } ;
@@ -240,6 +336,7 @@ NDArray.prototype._resizeGeometry = function( mins , maxs ) {
240
336
  Object.assign( newNDArray.order , this.order ) ;
241
337
  newNDArray._initSizesFromMinsMaxs( mins , maxs ) ;
242
338
  newNDArray._initStridesUsingSizes() ;
339
+ newNDArray._initOffsetEndFromStartSizesStrides() ;
243
340
 
244
341
  return newNDArray ;
245
342
  } ;
@@ -247,7 +344,7 @@ NDArray.prototype._resizeGeometry = function( mins , maxs ) {
247
344
 
248
345
 
249
346
  // Create a new data store of the same type
250
- NDArray.prototype._newStorageOfTheSameKind = function( size = this.size ) {
347
+ NDArray.prototype._newStorageOfTheSameKind = function( size = this.dataEnd - this.dataStart ) {
251
348
  if ( typeof this.data?.constructor?.allocUnsafe === 'function' ) {
252
349
  // Detect Node.js's Buffer referencing it (avoid browser compatibility layer)
253
350
  return this.data.constructor.allocUnsafe( size ) ;
@@ -263,7 +360,6 @@ NDArray.prototype._newStorageOfTheSameKind = function( size = this.size ) {
263
360
  // index = coords[ 0 ] * strides[ 0 ] + coords[ 1 ] * strides[ 1 ] + ... + coords[ n ] * strides[ n ]
264
361
  NDArray.prototype._getIndex = function( coords ) {
265
362
  let index = this.offset ;
266
- //for ( const d of this.order ) { index += ( coords[ d ] - this.mins[ d ] ) * this.strides[ d ] ; }
267
363
  for ( let d = 0 ; d < this.dimensions ; d ++ ) { index += ( coords[ d ] - this.mins[ d ] ) * this.strides[ d ] ; }
268
364
  return index ;
269
365
  } ;
@@ -308,8 +404,8 @@ NDArray.prototype._getCoords = function( index , coords = new Array( this.dimens
308
404
 
309
405
  NDArray.prototype._getCoordsCheck =
310
406
  NDArray.prototype.getCoords = function( index , coords ) {
311
- if ( index < this.offset || index >= this.offset + this.size ) {
312
- throw new RangeError( "Index " + index + " out of bounds, which is [" + this.offset + "," + ( this.offset + this.size ) + ")" ) ;
407
+ if ( index < this.dataStart || index >= this.dataEnd ) {
408
+ throw new RangeError( "Index " + index + " out of bounds, which is [" + this.dataStart + "," + this.dataEnd + ")" ) ;
313
409
  }
314
410
 
315
411
  coords = coords ?? new Array( this.dimensions ) ;
@@ -450,32 +546,7 @@ NDArray.prototype._getStrideStartAndVectorDimension = function( coords ) {
450
546
  */
451
547
  NDArray.prototype.forEach =
452
548
  NDArray.prototype._forEach = function( callback ) {
453
- // Usually =1, but later we could group neighbours (e.g. for RGBA images => stride0 = 4)
454
- const stride0 = this.strides[ this.order[ 0 ] ] ;
455
- const coords = Array.from( this.mins ) ;
456
- const dataEnd = this.offset + this.size ;
457
- let index = this.offset ;
458
-
459
- for ( ;; ) {
460
- callback( this.data[ index ] , coords , index , this ) ;
461
-
462
- index += stride0 ;
463
- if ( index >= dataEnd ) { return ; }
464
-
465
- for ( let i = 0 ; i < this.dimensions ; i ++ ) {
466
- const d = this.order[ i ] ;
467
- coords[ d ] ++ ;
468
- if (
469
- i < this.dimensions - 1
470
- && coords[ d ] - this.mins[ d ] >= this.strides[ this.order[ i + 1 ] ]
471
- ) {
472
- coords[ d ] = this.mins[ d ] ;
473
- }
474
- else {
475
- break ;
476
- }
477
- }
478
- }
549
+ return this._forEachInRegion( this.mins , this.maxs , callback , true ) ;
479
550
  } ;
480
551
 
481
552
 
@@ -511,10 +582,19 @@ NDArray.prototype._forEachInRegionCheck = function( mins , maxs , callback ) {
511
582
  this._forEachInRegion( mins , maxs , callback ) ;
512
583
  } ;
513
584
 
514
- NDArray.prototype._forEachInRegion = function( mins , maxs , callback ) {
515
- const backStrides = mins.map( ( min , d ) => ( min - maxs[ d ] ) * this.strides[ d ] ) ;
585
+ // wholeArray=true when coming from ._forEach()
586
+ NDArray.prototype._forEachInRegion = function( mins , maxs , callback , wholeArray = false ) {
587
+ let backStrides , index ;
516
588
  const coords = Array.from( mins ) ;
517
- let index = this._getIndex( coords ) ;
589
+
590
+ if ( wholeArray ) {
591
+ backStrides = this.backStrides ;
592
+ index = this.offset ;
593
+ }
594
+ else {
595
+ backStrides = mins.map( ( min , d ) => ( min - maxs[ d ] ) * this.strides[ d ] ) ;
596
+ index = this._getIndex( coords ) ;
597
+ }
518
598
 
519
599
  for ( ;; ) {
520
600
  callback( this.data[ index ] , coords , index , this ) ;
@@ -591,6 +671,13 @@ NDArray.prototype._forEachVectorInRegion = function( strideStartCoords , maxs ,
591
671
 
592
672
 
593
673
 
674
+ // Return a Generator
675
+ // .each()
676
+ NDArray.prototype.each =
677
+ NDArray.prototype._each = function() {
678
+ return this._eachInRegion( this.mins , this.maxs , true ) ;
679
+ } ;
680
+
594
681
  // Return a Generator
595
682
  // .eachInRegion( region )
596
683
  // .eachInRegion( mins , maxs )
@@ -610,10 +697,20 @@ NDArray.prototype._eachInRegionCheck = function( mins , maxs ) {
610
697
 
611
698
  // Generator
612
699
  // The yielded entry { coords , index , value } should be cloned, as well as entry.coords, if userland want to modify it
613
- NDArray.prototype._eachInRegion = function*( mins , maxs ) {
614
- const backStrides = mins.map( ( min , d ) => ( min - maxs[ d ] ) * this.strides[ d ] ) ;
700
+ // wholeArray=true when coming from ._each()
701
+ NDArray.prototype._eachInRegion = function*( mins , maxs , wholeArray = false ) {
702
+ let backStrides , index ;
615
703
  const coords = Array.from( mins ) ;
616
- let index = this._getIndex( coords ) ;
704
+
705
+ if ( wholeArray ) {
706
+ backStrides = this.backStrides ;
707
+ index = this.offset ;
708
+ }
709
+ else {
710
+ backStrides = mins.map( ( min , d ) => ( min - maxs[ d ] ) * this.strides[ d ] ) ;
711
+ index = this._getIndex( coords ) ;
712
+ }
713
+
617
714
  const entry = { coords , index , value: undefined } ;
618
715
 
619
716
  for ( ;; ) {
@@ -845,10 +942,8 @@ NDArray.prototype._eachVectorIndexInRegionBackward = function*( strideStartCoord
845
942
 
846
943
 
847
944
  NDArray.prototype.fill = function( value ) {
848
- for ( let i = this.offset , end = this.offset + this.size ; i < end ; i ++ ) {
849
- this.data[ i ] = value ;
850
- }
851
-
945
+ const stride0 = this.strides[ this.order[ 0 ] ] ;
946
+ for ( let i = this.dataStart ; i < this.dataEnd ; i += stride0 ) { this.data[ i ] = value ; }
852
947
  return this ;
853
948
  } ;
854
949
 
@@ -892,7 +987,7 @@ NDArray.prototype.map = function( callback ) {
892
987
  newNDArray.data = this._newStorageOfTheSameKind() ;
893
988
 
894
989
  this._forEach( ( value , coords , index ) => {
895
- newNDArray.data[ index - this.offset ] = callback( value , coords , index , this ) ;
990
+ newNDArray.data[ index - this.dataStart ] = callback( value , coords , index , this ) ;
896
991
  } ) ;
897
992
 
898
993
  return newNDArray ;
@@ -913,7 +1008,7 @@ NDArray.prototype.mapInRegion = function( mins , maxs , callback ) {
913
1008
 
914
1009
  this._checkRegion( mins , maxs ) ;
915
1010
  const newNDArray = this._resizeGeometry( mins , maxs ) ;
916
- newNDArray.data = this._newStorageOfTheSameKind( newNDArray.size ) ;
1011
+ newNDArray.data = this._newStorageOfTheSameKind( newNDArray.dataEnd ) ;
917
1012
 
918
1013
  this._dualStepCallback( newNDArray , mins , maxs , mins , maxs , callback ) ;
919
1014
 
@@ -944,7 +1039,7 @@ NDArray.prototype.mapVectorInRegion = function( mins , maxs , callback ) {
944
1039
 
945
1040
  this._checkRegion( strideStartCoords , strideEndCoords , vectorDimension ) ;
946
1041
  const newNDArray = this._resizeGeometry( strideStartCoords , strideEndCoords ) ;
947
- newNDArray.data = this._newStorageOfTheSameKind( newNDArray.size ) ;
1042
+ newNDArray.data = this._newStorageOfTheSameKind( newNDArray.dataEnd ) ;
948
1043
 
949
1044
  this._dualStepVectorCallback( newNDArray , strideStartCoords , strideEndCoords , strideStartCoords , strideEndCoords , vectorDimension , callback ) ;
950
1045
 
@@ -1029,7 +1124,7 @@ NDArray.prototype.extractRegion = function( mins , maxs ) {
1029
1124
 
1030
1125
  this._checkRegion( mins , maxs ) ;
1031
1126
  const newNDArray = this._resizeGeometry( mins , maxs ) ;
1032
- newNDArray.data = this._newStorageOfTheSameKind( newNDArray.size ) ;
1127
+ newNDArray.data = this._newStorageOfTheSameKind( newNDArray.dataEnd ) ;
1033
1128
 
1034
1129
  this._dualStepCopy( newNDArray , mins , maxs , mins , maxs ) ;
1035
1130
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "array-kit",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "description": "An array manipulation toolbox.",
5
5
  "main": "lib/array-kit.js",
6
6
  "directories": {