array-kit 0.2.9 → 0.2.10

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 ADDED
@@ -0,0 +1,1515 @@
1
+ /*
2
+ Array Kit
3
+
4
+ Copyright (c) 2014 - 2020 Cédric Ronvel
5
+
6
+ The MIT License (MIT)
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in all
16
+ copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ SOFTWARE.
25
+ */
26
+
27
+ "use strict" ;
28
+
29
+
30
+
31
+ /*
32
+ Contrary to most NDArray implementation, this one allow negative indexes or any non-zero-based array.
33
+
34
+ NDArray( dimensions ) Internal, special case where nothing is init, it's the bare minium for cloning
35
+ NDArray( [ dataStorage | Constructor ] , [ sizes | region ] , [ params ] )
36
+
37
+ Arguments:
38
+ storage: an array-like (Array, TypedArray, Buffer, or any indexable object) that will be used as the storage backend
39
+ Constructor: constructor to create the storage backend with the appropriate size.
40
+ sizes: array of sizes, whose length is also used as the number of dimensions for the NDArray
41
+ region: an array of [ min , max ] (max being INCLUDED), for creating a non-zero-based ND-array
42
+ params: an object of optional parameters, where:
43
+ order: array of coord order stored in the storage, by default [ 0 , 1 , 2 , ..., N ], for 2D it's row-first,
44
+ if you want column-first, use [ 1 , 0 ], or params.reverse = true
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)
47
+ stride: (default: 1) the stride of the fastest moving dimension, usually 1 except if there is more dimensions fastest
48
+ dimensions that are removed from the view
49
+ */
50
+ function NDArray( dataOrConstructor , sizes , params ) {
51
+ let dimensions , noInit = false ;
52
+
53
+ if ( typeof dataOrConstructor === 'number' ) {
54
+ dimensions = dataOrConstructor ;
55
+ noInit = true ; // Fast mode, for cloning and similar things
56
+ }
57
+ else {
58
+ dimensions = sizes.length ;
59
+ }
60
+
61
+ // The number of dimension of the ND-array, e.g. 2D, 3D, ...
62
+ this.dimensions = dimensions ;
63
+
64
+ // Define the size in each axis
65
+ this.sizes = new Array( dimensions ) ;
66
+
67
+ // The full size of the ND-array, each axis-size multiplied
68
+ this.size = 1 ;
69
+
70
+ // Define the min for each axis, when the ND-array is not zero-based
71
+ this.mins = new Array( dimensions ) ;
72
+
73
+ // Define the INCLUSIVE max for each axis
74
+ this.maxs = new Array( dimensions ) ;
75
+
76
+ // Define the coordinate order (e.g. row-first, column-first), the fastest moving dimensions in the memory first,
77
+ // so it will order strides starting from the smallest one first...
78
+ this.order = new Array( dimensions ) ;
79
+
80
+ // Define the layout of the array in memory, strides are products of all faster dimensions
81
+ this.strides = new Array( dimensions ) ;
82
+
83
+ // When the store is only partially used, this is the offset in the store natural index
84
+ this.offset = 0 ;
85
+
86
+ // The underlying data store: Array, TypedArray, Buffer... anything that is indexed
87
+ this.data = null ;
88
+
89
+ if ( ! noInit ) {
90
+ this._init( dataOrConstructor , sizes , params ) ;
91
+ }
92
+ }
93
+
94
+ module.exports = NDArray ;
95
+
96
+
97
+
98
+ NDArray.prototype._init = function( dataOrConstructor , sizes , params = {} ) {
99
+ // Compute order
100
+ if ( params.order ) {
101
+ for ( let d = 0 ; d < this.dimensions ; d ++ ) { this.order[ d ] = params.order[ d ] ; }
102
+ }
103
+ else if ( params.reverse ) {
104
+ for ( let d = 0 ; d < this.dimensions ; d ++ ) { this.order[ d ] = this.dimensions - d - 1 ; }
105
+ }
106
+ else {
107
+ for ( let d = 0 ; d < this.dimensions ; d ++ ) { this.order[ d ] = d ; }
108
+ }
109
+
110
+ // Compute mins/maxs/sizes
111
+ if ( Array.isArray( sizes[ 0 ] ) ) {
112
+ // This is the region variant
113
+ this._initMinsMaxsSizesFromRegion( sizes ) ;
114
+ }
115
+ else {
116
+ // This is a zero-based array
117
+ this._initMinsMaxsFromSizes( sizes ) ;
118
+ }
119
+
120
+ // Compute the stride
121
+ this._initStridesUsingSizes( params.stride ) ;
122
+
123
+ // Data store start and end
124
+ this.offset = params.offset || 0 ;
125
+
126
+ // Set or create a new data store
127
+ if ( typeof dataOrConstructor === 'function' ) {
128
+ if ( typeof dataOrConstructor.allocUnsafe === 'function' ) {
129
+ // Detect Node.js's Buffer referencing it (avoid browser compatibility layer)
130
+ this.data = dataOrConstructor.allocUnsafe( this.offset + this.size ) ;
131
+ }
132
+ else {
133
+ this.data = new dataOrConstructor( this.offset + this.size ) ;
134
+ }
135
+ }
136
+ else {
137
+ /*
138
+ // 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 ) ;
141
+ }
142
+ */
143
+ this.data = dataOrConstructor ;
144
+ }
145
+ } ;
146
+
147
+ // Fragmented init, because it can be re-use by various cloning things
148
+
149
+ NDArray.prototype._initMinsMaxsSizesFromRegion = function( region ) {
150
+ for ( let d = 0 ; d < this.dimensions ; d ++ ) {
151
+ this.mins[ d ] = region[ d ][ 0 ] ;
152
+ this.maxs[ d ] = region[ d ][ 1 ] ;
153
+ this.sizes[ d ] = this.maxs[ d ] - this.mins[ d ] + 1 ;
154
+ this.size *= this.sizes[ d ] ;
155
+
156
+ if ( this.sizes[ d ] <= 0 || ! Number.isFinite( this.sizes[ d ] ) ) {
157
+ throw new RangeError( "Bad size (min-max: [" + this.mins[ d ] + "," + this.maxs[ d ] + "]) for dimension #" + d ) ;
158
+ }
159
+ }
160
+ } ;
161
+
162
+ NDArray.prototype._initSizesFromMinsMaxs = function( mins , maxs ) {
163
+ for ( let d = 0 ; d < this.dimensions ; d ++ ) {
164
+ this.mins[ d ] = mins[ d ] ;
165
+ this.maxs[ d ] = maxs[ d ] ;
166
+ this.sizes[ d ] = this.maxs[ d ] - this.mins[ d ] + 1 ;
167
+ this.size *= this.sizes[ d ] ;
168
+
169
+ if ( this.sizes[ d ] <= 0 || ! Number.isFinite( this.sizes[ d ] ) ) {
170
+ throw new RangeError( "Bad size (min-max: [" + this.mins[ d ] + "," + this.maxs[ d ] + "]) for dimension #" + d ) ;
171
+ }
172
+ }
173
+ } ;
174
+
175
+ NDArray.prototype._initMinsMaxsFromSizes = function( sizes ) {
176
+ for ( let d = 0 ; d < this.dimensions ; d ++ ) {
177
+ this.sizes[ d ] = sizes[ d ] ;
178
+ this.mins[ d ] = 0 ;
179
+ this.maxs[ d ] = this.sizes[ d ] - 1 ;
180
+ this.size *= this.sizes[ d ] ;
181
+
182
+ if ( this.sizes[ d ] <= 0 || ! Number.isFinite( this.sizes[ d ] ) ) {
183
+ throw new RangeError( "Bad size (" + this.sizes[ d ] + ") for dimension #" + d ) ;
184
+ }
185
+ }
186
+ } ;
187
+
188
+ // Sizes is not passed as argument, it should already be computed on the instance
189
+ NDArray.prototype._initStridesUsingSizes = function( stride = 1 ) {
190
+ for ( const d of this.order ) {
191
+ this.strides[ d ] = stride ;
192
+ stride *= this.sizes[ d ] ;
193
+ }
194
+ } ;
195
+
196
+
197
+
198
+ // Make the array zero-based, or based to the provided mins vector
199
+ NDArray.prototype.rebase = function( mins ) {
200
+ if ( mins ) {
201
+ for ( let d = 0 ; d < this.dimensions ; d ++ ) {
202
+ this.mins[ d ] = mins[ d ] ;
203
+ this.maxs[ d ] = mins[ d ] + this.sizes[ d ] - 1 ;
204
+ }
205
+ }
206
+ else {
207
+ for ( let d = 0 ; d < this.dimensions ; d ++ ) {
208
+ this.mins[ d ] = 0 ;
209
+ this.maxs[ d ] = this.sizes[ d ] - 1 ;
210
+ }
211
+ }
212
+
213
+ return this ;
214
+ } ;
215
+
216
+
217
+
218
+ // 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).
220
+ NDArray.prototype._cloneGeometry = function() {
221
+ const newNDArray = new NDArray( this.dimensions ) ;
222
+
223
+ // Copy geometry
224
+ Object.assign( newNDArray.order , this.order ) ;
225
+ Object.assign( newNDArray.sizes , this.sizes ) ;
226
+ newNDArray.size = this.size ;
227
+ Object.assign( newNDArray.mins , this.mins ) ;
228
+ Object.assign( newNDArray.maxs , this.maxs ) ;
229
+ Object.assign( newNDArray.strides , this.strides ) ;
230
+
231
+ return newNDArray ;
232
+ } ;
233
+
234
+
235
+
236
+ // Clone with a modified geometry
237
+ NDArray.prototype._resizeGeometry = function( mins , maxs ) {
238
+ const newNDArray = new NDArray( this.dimensions ) ;
239
+
240
+ Object.assign( newNDArray.order , this.order ) ;
241
+ newNDArray._initSizesFromMinsMaxs( mins , maxs ) ;
242
+ newNDArray._initStridesUsingSizes() ;
243
+
244
+ return newNDArray ;
245
+ } ;
246
+
247
+
248
+
249
+ // Create a new data store of the same type
250
+ NDArray.prototype._newStorageOfTheSameKind = function( size = this.size ) {
251
+ if ( typeof this.data?.constructor?.allocUnsafe === 'function' ) {
252
+ // Detect Node.js's Buffer referencing it (avoid browser compatibility layer)
253
+ return this.data.constructor.allocUnsafe( size ) ;
254
+ }
255
+
256
+ return new this.data.constructor( size ) ;
257
+ } ;
258
+
259
+
260
+
261
+ // Internal version without any unnessessary test
262
+ // Classic strided array access is:
263
+ // index = coords[ 0 ] * strides[ 0 ] + coords[ 1 ] * strides[ 1 ] + ... + coords[ n ] * strides[ n ]
264
+ NDArray.prototype._getIndex = function( coords ) {
265
+ let index = this.offset ;
266
+ //for ( const d of this.order ) { index += ( coords[ d ] - this.mins[ d ] ) * this.strides[ d ] ; }
267
+ for ( let d = 0 ; d < this.dimensions ; d ++ ) { index += ( coords[ d ] - this.mins[ d ] ) * this.strides[ d ] ; }
268
+ return index ;
269
+ } ;
270
+
271
+ NDArray.prototype._getIndexCheck = function( coords ) {
272
+ let index = this.offset ;
273
+
274
+ //for ( const d of this.order ) {
275
+ for ( let d = 0 ; d < this.dimensions ; d ++ ) {
276
+ const c = coords[ d ] ;
277
+
278
+ if ( c < this.mins[ d ] || c > this.maxs[ d ] ) {
279
+ throw new RangeError( "Coordinate " + c + " out of bounds for dimension #" + d + " which is [" + this.mins[ d ] + "," + this.maxs[ d ] + "]" ) ;
280
+ }
281
+
282
+ index += ( c - this.mins[ d ] ) * this.strides[ d ] ;
283
+ }
284
+
285
+ return index ;
286
+ } ;
287
+
288
+ NDArray.prototype.getIndex = function( ... coords ) {
289
+ if ( Array.isArray( coords[ 0 ] ) ) { coords = coords[ 0 ] ; }
290
+ return this._getIndexCheck( coords ) ;
291
+ } ;
292
+
293
+
294
+
295
+ NDArray.prototype._getCoords = function( index , coords = new Array( this.dimensions ) ) {
296
+ // Rebase the index
297
+ index -= this.offset ;
298
+
299
+ // Because of the division and modulo, this have to be done in the reverse order, from the biggest stride to the smallest.
300
+ for ( let i = this.dimensions - 1 ; i >= 0 ; i -- ) {
301
+ const d = this.order[ i ] ;
302
+ coords[ d ] = Math.floor( index / this.strides[ d ] ) + this.mins[ d ] ;
303
+ index = index % this.strides[ d ] ;
304
+ }
305
+
306
+ return coords ;
307
+ } ;
308
+
309
+ NDArray.prototype._getCoordsCheck =
310
+ 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 ) + ")" ) ;
313
+ }
314
+
315
+ coords = coords ?? new Array( this.dimensions ) ;
316
+
317
+ // Rebase the index
318
+ index -= this.offset ;
319
+
320
+ // Because of the division and modulo, this have to be done in the reverse order, from the biggest stride to the smallest.
321
+ for ( let i = this.dimensions - 1 ; i >= 0 ; i -- ) {
322
+ const d = this.order[ i ] ;
323
+ coords[ d ] = Math.floor( index / this.strides[ d ] ) + this.mins[ d ] ;
324
+ index = index % this.strides[ d ] ;
325
+ }
326
+
327
+ return coords ;
328
+ } ;
329
+
330
+
331
+
332
+ NDArray.prototype._get = function( coords ) { return this.data[ this._getIndex( coords ) ] ; } ;
333
+ NDArray.prototype._getCheck = function( coords ) { return this.data[ this._getIndexCheck( coords ) ] ; } ;
334
+ NDArray.prototype.get = function( ... coords ) {
335
+ if ( Array.isArray( coords[ 0 ] ) ) { coords = coords[ 0 ] ; }
336
+ return this.data[ this._getIndexCheck( coords ) ] ;
337
+ } ;
338
+
339
+
340
+
341
+ NDArray.prototype._set = function( coords , value ) { this.data[ this._getIndex( coords ) ] = value ; } ;
342
+ NDArray.prototype._setCheck = function( coords , value ) { this.data[ this._getIndexCheck( coords ) ] = value ; } ;
343
+ NDArray.prototype.set = function( ... args ) {
344
+ if ( Array.isArray( args[ 0 ] ) ) {
345
+ this.data[ this._getIndexCheck( args[ 0 ] ) ] = args[ 1 ] ;
346
+ }
347
+ else {
348
+ // We don't care for the extra element in ._getIndexCheck()
349
+ this.data[ this._getIndexCheck( args ) ] = args[ args.length - 1 ] ;
350
+ }
351
+ } ;
352
+
353
+
354
+
355
+ NDArray.prototype.getVector = function( ... coords ) {
356
+ if ( Array.isArray( coords[ 0 ] ) ) { coords = coords[ 0 ] ; }
357
+ const [ strideStartCoords , vectorDimension ] = this._getStrideStartAndVectorDimension( coords ) ;
358
+ const index = this._getIndexCheck( strideStartCoords ) ;
359
+ return this._getVectorAtIndex( index , vectorDimension ) ;
360
+ } ;
361
+
362
+ NDArray.prototype._getVector = function( strideStartCoords , vectorDimension ) {
363
+ const index = this._getIndex( strideStartCoords ) ;
364
+ return this._getVectorAtIndex( index , vectorDimension ) ;
365
+ } ;
366
+
367
+ NDArray.prototype._getVectorAtIndex = function( index , vectorDimension , vector = new Array( this.sizes[ vectorDimension ] ) ) {
368
+ const iMax = this.sizes[ vectorDimension ] ;
369
+ const stride = this.strides[ vectorDimension ] ;
370
+
371
+ for ( let i = 0 ; i < iMax ; i ++ , index += stride ) {
372
+ vector[ i ] = this.data[ index ] ;
373
+ }
374
+
375
+ return vector ;
376
+ } ;
377
+
378
+
379
+
380
+ NDArray.prototype.setVector = function( ... args ) {
381
+ let strideStartCoords , vectorDimension , vector ;
382
+
383
+ if ( Array.isArray( args[ 0 ] ) ) {
384
+ [ strideStartCoords , vectorDimension ] = this._getStrideStartAndVectorDimension( args[ 0 ] ) ;
385
+ vector = args[ 1 ] ;
386
+ }
387
+ else {
388
+ // We don't care for the extra element in ._getStrideStartAndVectorDimension()
389
+ [ strideStartCoords , vectorDimension ] = this._getStrideStartAndVectorDimension( args ) ;
390
+ vector = args[ args.length - 1 ] ;
391
+ }
392
+
393
+ const index = this._getIndexCheck( strideStartCoords ) ;
394
+ this._setVectorAtIndex( index , vectorDimension , vector ) ;
395
+ } ;
396
+
397
+ NDArray.prototype._setVector = function( strideStartCoords , vectorDimension , vector ) {
398
+ const index = this._getIndex( strideStartCoords ) ;
399
+ this._setVectorAtIndex( index , vectorDimension , vector ) ;
400
+ } ;
401
+
402
+ NDArray.prototype._setVectorAtIndex = function( index , vectorDimension , vector ) {
403
+ const iMax = this.sizes[ vectorDimension ] ;
404
+ const stride = this.strides[ vectorDimension ] ;
405
+
406
+ for ( let i = 0 ; i < iMax ; i ++ , index += stride ) {
407
+ this.data[ index ] = vector[ i ] ;
408
+ }
409
+ } ;
410
+
411
+
412
+ NDArray.prototype._getStrideStartAndVectorDimension = function( coords ) {
413
+ let vectorDimension = null ;
414
+ const strideStartCoords = new Array( this.dimensions ) ;
415
+
416
+ for ( let d = 0 ; d < this.dimensions ; d ++ ) {
417
+ const coord = coords[ d ] ;
418
+ if ( coord === null ) {
419
+ if ( vectorDimension !== null ) {
420
+ throw new Error( "*Vector*() methods require exactly one coord to be null" ) ;
421
+ }
422
+
423
+ vectorDimension = d ;
424
+ strideStartCoords[ d ] = this.mins[ d ] ;
425
+ }
426
+ else {
427
+ strideStartCoords[ d ] = coord ;
428
+ }
429
+ }
430
+
431
+ if ( vectorDimension === null ) {
432
+ throw new Error( "*Vector*() methods require exactly one coord to be null" ) ;
433
+ }
434
+
435
+ return [ strideStartCoords , vectorDimension ] ;
436
+ } ;
437
+
438
+
439
+
440
+ /*
441
+ /!\ .forEach*() and .each*() methods are the backbone of the lib.
442
+ Other high-level methods like .map*(), .copyTo*(), etc, all use .forEach*() or .each*().
443
+ */
444
+
445
+ /*
446
+ Iterate the whole ND-array over index (and convert to coords).
447
+
448
+ BE CAREFUL! The callback receive a coords array that is not cloned, because it would slowdown iteration
449
+ and would put a lot of work for the Garbage Collector for large ndarrays.
450
+ */
451
+ NDArray.prototype.forEach =
452
+ 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
+ }
479
+ } ;
480
+
481
+
482
+
483
+ /*
484
+ Like foreach, but scan only an region.
485
+ So it does not iterate over index (and convert to coords), but over coords (and convert to index).
486
+
487
+ BE CAREFUL! The callback receive a coords array that is not cloned, because it would slowdown iteration
488
+ and would put a lot of work for the Garbage Collector for large ndarrays.
489
+
490
+ .forEachInRegion( region , callback )
491
+ .forEachInRegion( mins , maxs , callback )
492
+
493
+ Arguments:
494
+ mins: an array of min coords
495
+ maxs: an array of max coords (they are INCLUDED)
496
+ region: an array of [ min , max ] for each coord (max is INCLUDED)
497
+ callback: the iterator callback
498
+ */
499
+ NDArray.prototype.forEachInRegion = function( mins , maxs , callback ) {
500
+ if ( typeof maxs === 'function' ) {
501
+ callback = maxs ;
502
+ maxs = mins.map( minmax => minmax[ 1 ] ) ;
503
+ mins = mins.map( minmax => minmax[ 0 ] ) ;
504
+ }
505
+
506
+ this._forEachInRegionCheck( mins , maxs , callback ) ;
507
+ } ;
508
+
509
+ NDArray.prototype._forEachInRegionCheck = function( mins , maxs , callback ) {
510
+ this._checkRegion( mins , maxs ) ;
511
+ this._forEachInRegion( mins , maxs , callback ) ;
512
+ } ;
513
+
514
+ NDArray.prototype._forEachInRegion = function( mins , maxs , callback ) {
515
+ const backStrides = mins.map( ( min , d ) => ( min - maxs[ d ] ) * this.strides[ d ] ) ;
516
+ const coords = Array.from( mins ) ;
517
+ let index = this._getIndex( coords ) ;
518
+
519
+ for ( ;; ) {
520
+ callback( this.data[ index ] , coords , index , this ) ;
521
+
522
+ for ( let i = 0 ; i < this.dimensions ; i ++ ) {
523
+ const d = this.order[ i ] ;
524
+
525
+ if ( coords[ d ] >= maxs[ d ] ) {
526
+ if ( i === this.dimensions - 1 ) { return ; }
527
+ coords[ d ] = mins[ d ] ;
528
+ // Return index back to min
529
+ index += backStrides[ d ] ;
530
+ }
531
+ else {
532
+ coords[ d ] ++ ;
533
+ index += this.strides[ d ] ;
534
+ break ;
535
+ }
536
+ }
537
+ }
538
+ } ;
539
+
540
+
541
+
542
+ NDArray.prototype.forEachVectorInRegion = function( mins , maxs , callback ) {
543
+ if ( typeof maxs === 'function' ) {
544
+ callback = maxs ;
545
+ maxs = mins.map( minmax => minmax?.[ 1 ] ?? null ) ;
546
+ mins = mins.map( minmax => minmax?.[ 0 ] ?? null ) ;
547
+ }
548
+
549
+ const [ strideStartCoords , vectorDimension ] = this._getStrideStartAndVectorDimension( mins ) ;
550
+ this._forEachVectorInRegionCheck( strideStartCoords , maxs , vectorDimension , callback ) ;
551
+ } ;
552
+
553
+ NDArray.prototype._forEachVectorInRegionCheck = function( strideStartCoords , maxs , vectorDimension , callback ) {
554
+ this._checkRegion( strideStartCoords , maxs , vectorDimension ) ;
555
+ this._forEachVectorInRegion( strideStartCoords , maxs , vectorDimension , callback ) ;
556
+ } ;
557
+
558
+ NDArray.prototype._forEachVectorInRegion = function( strideStartCoords , maxs , vectorDimension , callback ) {
559
+ const backStrides = strideStartCoords.map( ( min , d ) => ( min - maxs[ d ] ) * this.strides[ d ] ) ;
560
+ const coords = Array.from( strideStartCoords ) ;
561
+ const vector = new Array( this.sizes[ vectorDimension ] ) ;
562
+ let index = this._getIndex( coords ) ;
563
+
564
+ for ( ;; ) {
565
+ this._getVectorAtIndex( index , vectorDimension , vector ) ;
566
+ callback( vector , coords , index , this ) ;
567
+
568
+ for ( let i = 0 ; i < this.dimensions ; i ++ ) {
569
+ const d = this.order[ i ] ;
570
+
571
+ if ( d === vectorDimension ) {
572
+ if ( i === this.dimensions - 1 ) { return ; }
573
+ // Just skip it without doing anything
574
+ continue ;
575
+ }
576
+
577
+ if ( coords[ d ] >= maxs[ d ] ) {
578
+ if ( i === this.dimensions - 1 ) { return ; }
579
+ coords[ d ] = strideStartCoords[ d ] ;
580
+ // Return index back to min
581
+ index += backStrides[ d ] ;
582
+ }
583
+ else {
584
+ coords[ d ] ++ ;
585
+ index += this.strides[ d ] ;
586
+ break ;
587
+ }
588
+ }
589
+ }
590
+ } ;
591
+
592
+
593
+
594
+ // Return a Generator
595
+ // .eachInRegion( region )
596
+ // .eachInRegion( mins , maxs )
597
+ NDArray.prototype.eachInRegion = function( mins , maxs ) {
598
+ if ( ! maxs ) {
599
+ maxs = mins.map( minmax => minmax[ 1 ] ) ;
600
+ mins = mins.map( minmax => minmax[ 0 ] ) ;
601
+ }
602
+
603
+ return this._eachInRegionCheck( mins , maxs ) ;
604
+ } ;
605
+
606
+ NDArray.prototype._eachInRegionCheck = function( mins , maxs ) {
607
+ this._checkRegion( mins , maxs ) ;
608
+ return this._eachInRegion( mins , maxs ) ;
609
+ } ;
610
+
611
+ // Generator
612
+ // 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 ] ) ;
615
+ const coords = Array.from( mins ) ;
616
+ let index = this._getIndex( coords ) ;
617
+ const entry = { coords , index , value: undefined } ;
618
+
619
+ for ( ;; ) {
620
+ entry.value = this.data[ index ] ;
621
+ entry.index = index ;
622
+ yield entry ;
623
+
624
+ for ( let i = 0 ; i < this.dimensions ; i ++ ) {
625
+ const d = this.order[ i ] ;
626
+
627
+ if ( coords[ d ] >= maxs[ d ] ) {
628
+ if ( i === this.dimensions - 1 ) { return ; }
629
+ coords[ d ] = mins[ d ] ;
630
+ // Return index back to min
631
+ index += backStrides[ d ] ;
632
+ }
633
+ else {
634
+ coords[ d ] ++ ;
635
+ index += this.strides[ d ] ;
636
+ break ;
637
+ }
638
+ }
639
+ }
640
+ } ;
641
+
642
+ NDArray.prototype._eachInRegionBackward = function*( mins , maxs ) {
643
+ const backStrides = mins.map( ( min , d ) => ( min - maxs[ d ] ) * this.strides[ d ] ) ;
644
+ const coords = Array.from( maxs ) ;
645
+ let index = this._getIndex( coords ) ;
646
+ const entry = { coords , index , value: undefined } ;
647
+
648
+ for ( ;; ) {
649
+ entry.value = this.data[ index ] ;
650
+ entry.index = index ;
651
+ yield entry ;
652
+
653
+ for ( let i = 0 ; i < this.dimensions ; i ++ ) {
654
+ const d = this.order[ i ] ;
655
+
656
+ if ( coords[ d ] <= mins[ d ] ) {
657
+ if ( i === this.dimensions - 1 ) { return ; }
658
+ coords[ d ] = maxs[ d ] ;
659
+ // Return index back to min
660
+ index -= backStrides[ d ] ;
661
+ }
662
+ else {
663
+ coords[ d ] -- ;
664
+ index -= this.strides[ d ] ;
665
+ break ;
666
+ }
667
+ }
668
+ }
669
+ } ;
670
+
671
+
672
+
673
+ // Return a Generator
674
+ // .eachInRegion( region )
675
+ // .eachInRegion( mins , maxs )
676
+ NDArray.prototype.eachVectorInRegion = function( mins , maxs ) {
677
+ if ( ! maxs ) {
678
+ maxs = mins.map( minmax => minmax?.[ 1 ] ?? null ) ;
679
+ mins = mins.map( minmax => minmax?.[ 0 ] ?? null ) ;
680
+ }
681
+
682
+ const [ strideStartCoords , vectorDimension ] = this._getStrideStartAndVectorDimension( mins ) ;
683
+ return this._eachVectorInRegionCheck( strideStartCoords , maxs , vectorDimension ) ;
684
+ } ;
685
+
686
+ NDArray.prototype._eachVectorInRegionCheck = function( strideStartCoords , maxs , vectorDimension ) {
687
+ this._checkRegion( strideStartCoords , maxs , vectorDimension ) ;
688
+ return this._eachVectorInRegion( strideStartCoords , maxs , vectorDimension ) ;
689
+ } ;
690
+
691
+ // Generator
692
+ // The yielded entry { coords , index , value } should be cloned, as well as entry.coords and entry.value if userland want to modify it
693
+ NDArray.prototype._eachVectorInRegion = function*( strideStartCoords , maxs , vectorDimension ) {
694
+ const backStrides = strideStartCoords.map( ( min , d ) => ( min - maxs[ d ] ) * this.strides[ d ] ) ;
695
+ const coords = Array.from( strideStartCoords ) ;
696
+ const vector = new Array( this.sizes[ vectorDimension ] ) ;
697
+ let index = this._getIndex( coords ) ;
698
+ const entry = { coords , index , value: vector } ;
699
+
700
+ for ( ;; ) {
701
+ this._getVectorAtIndex( index , vectorDimension , vector ) ;
702
+ entry.index = index ;
703
+ yield entry ;
704
+
705
+ for ( let i = 0 ; i < this.dimensions ; i ++ ) {
706
+ const d = this.order[ i ] ;
707
+
708
+ if ( d === vectorDimension ) {
709
+ if ( i === this.dimensions - 1 ) { return ; }
710
+ // Just skip it without doing anything
711
+ continue ;
712
+ }
713
+
714
+ if ( coords[ d ] >= maxs[ d ] ) {
715
+ if ( i === this.dimensions - 1 ) { return ; }
716
+ coords[ d ] = strideStartCoords[ d ] ;
717
+ // Return index back to min
718
+ index += backStrides[ d ] ;
719
+ }
720
+ else {
721
+ coords[ d ] ++ ;
722
+ index += this.strides[ d ] ;
723
+ break ;
724
+ }
725
+ }
726
+ }
727
+ } ;
728
+
729
+ // Generator
730
+ // The yielded entry { coords , index , value } should be cloned, as well as entry.coords and entry.value if userland want to modify it
731
+ NDArray.prototype._eachVectorInRegionBackward = function*( strideStartCoords , maxs , vectorDimension ) {
732
+ const backStrides = strideStartCoords.map( ( min , d ) => ( min - maxs[ d ] ) * this.strides[ d ] ) ;
733
+ const coords = Array.from( maxs ) ;
734
+ coords[ vectorDimension ] = strideStartCoords[ vectorDimension ] ;
735
+ const vector = new Array( this.sizes[ vectorDimension ] ) ;
736
+ let index = this._getIndex( coords ) ;
737
+ const entry = { coords , index , value: vector } ;
738
+
739
+ for ( ;; ) {
740
+ this._getVectorAtIndex( index , vectorDimension , vector ) ;
741
+ entry.index = index ;
742
+ yield entry ;
743
+
744
+ for ( let i = 0 ; i < this.dimensions ; i ++ ) {
745
+ const d = this.order[ i ] ;
746
+
747
+ if ( d === vectorDimension ) {
748
+ if ( i === this.dimensions - 1 ) { return ; }
749
+ // Just skip it without doing anything
750
+ continue ;
751
+ }
752
+
753
+ if ( coords[ d ] <= strideStartCoords[ d ] ) {
754
+ if ( i === this.dimensions - 1 ) { return ; }
755
+ coords[ d ] = maxs[ d ] ;
756
+ // Return index back to min
757
+ index -= backStrides[ d ] ;
758
+ }
759
+ else {
760
+ coords[ d ] -- ;
761
+ index -= this.strides[ d ] ;
762
+ break ;
763
+ }
764
+ }
765
+ }
766
+ } ;
767
+
768
+ // Generator
769
+ // Same than ._eachVectorInRegion() except the callback does not receive of vector, just the coords and index.
770
+ // Just a bit faster when you don't need the vector (e.g. dualStepVectorCallback()), since it's not created.
771
+ // The yielded entry { coords , index } should be cloned, as well as entry.coords if userland want to modify it
772
+ NDArray.prototype._eachVectorIndexInRegion = function*( strideStartCoords , maxs , vectorDimension ) {
773
+ const backStrides = strideStartCoords.map( ( min , d ) => ( min - maxs[ d ] ) * this.strides[ d ] ) ;
774
+ const coords = Array.from( strideStartCoords ) ;
775
+ let index = this._getIndex( coords ) ;
776
+ const entry = { coords , index } ;
777
+
778
+ for ( ;; ) {
779
+ entry.index = index ;
780
+ yield entry ;
781
+
782
+ for ( let i = 0 ; i < this.dimensions ; i ++ ) {
783
+ const d = this.order[ i ] ;
784
+
785
+ if ( d === vectorDimension ) {
786
+ if ( i === this.dimensions - 1 ) { return ; }
787
+ // Just skip it without doing anything
788
+ continue ;
789
+ }
790
+
791
+ if ( coords[ d ] >= maxs[ d ] ) {
792
+ if ( i === this.dimensions - 1 ) { return ; }
793
+ coords[ d ] = strideStartCoords[ d ] ;
794
+ // Return index back to min
795
+ index += backStrides[ d ] ;
796
+ }
797
+ else {
798
+ coords[ d ] ++ ;
799
+ index += this.strides[ d ] ;
800
+ break ;
801
+ }
802
+ }
803
+ }
804
+ } ;
805
+
806
+ // Generator
807
+ // Same than ._eachVectorInRegion() except the callback does not receive of vector, just the coords and index.
808
+ // Just a bit faster when you don't need the vector (e.g. dualStepVectorCallback()), since it's not created.
809
+ // The yielded entry { coords , index } should be cloned, as well as entry.coords if userland want to modify it
810
+ NDArray.prototype._eachVectorIndexInRegionBackward = function*( strideStartCoords , maxs , vectorDimension ) {
811
+ const backStrides = strideStartCoords.map( ( min , d ) => ( min - maxs[ d ] ) * this.strides[ d ] ) ;
812
+ const coords = Array.from( maxs ) ;
813
+ coords[ vectorDimension ] = strideStartCoords[ vectorDimension ] ;
814
+ let index = this._getIndex( coords ) ;
815
+ const entry = { coords , index } ;
816
+
817
+ for ( ;; ) {
818
+ entry.index = index ;
819
+ yield entry ;
820
+
821
+ for ( let i = 0 ; i < this.dimensions ; i ++ ) {
822
+ const d = this.order[ i ] ;
823
+
824
+ if ( d === vectorDimension ) {
825
+ if ( i === this.dimensions - 1 ) { return ; }
826
+ // Just skip it without doing anything
827
+ continue ;
828
+ }
829
+
830
+ if ( coords[ d ] <= strideStartCoords[ d ] ) {
831
+ if ( i === this.dimensions - 1 ) { return ; }
832
+ coords[ d ] = maxs[ d ] ;
833
+ // Return index back to min
834
+ index -= backStrides[ d ] ;
835
+ }
836
+ else {
837
+ coords[ d ] -- ;
838
+ index -= this.strides[ d ] ;
839
+ break ;
840
+ }
841
+ }
842
+ }
843
+ } ;
844
+
845
+
846
+
847
+ 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
+
852
+ return this ;
853
+ } ;
854
+
855
+ NDArray.prototype.fillInRegion = function( mins , maxs , value ) {
856
+ if ( Array.isArray( mins[ 0 ] ) ) {
857
+ value = maxs ;
858
+ maxs = mins.map( minmax => minmax[ 1 ] ) ;
859
+ mins = mins.map( minmax => minmax[ 0 ] ) ;
860
+ }
861
+
862
+ for ( const { index } of this._eachInRegionCheck( mins , maxs ) ) {
863
+ this.data[ index ] = value ;
864
+ }
865
+
866
+ return this ;
867
+ } ;
868
+
869
+ NDArray.prototype.fillVectorInRegion = function( mins , maxs , vector ) {
870
+ if ( Array.isArray( mins[ 0 ] ) ) {
871
+ vector = maxs ;
872
+ maxs = mins.map( minmax => minmax?.[ 1 ] ?? null ) ;
873
+ mins = mins.map( minmax => minmax?.[ 0 ] ?? null ) ;
874
+ }
875
+
876
+ const [ strideStartCoords , vectorDimension ] = this._getStrideStartAndVectorDimension( mins ) ;
877
+
878
+ for ( const { index } of this._eachVectorInRegionCheck( strideStartCoords , maxs , vectorDimension ) ) {
879
+ this._setVectorAtIndex( index , vectorDimension , vector ) ;
880
+ }
881
+
882
+ return this ;
883
+ } ;
884
+
885
+
886
+
887
+ /*
888
+ The classic .map(), returning a new ND-Array with its own data storage.
889
+ */
890
+ NDArray.prototype.map = function( callback ) {
891
+ const newNDArray = this._cloneGeometry() ;
892
+ newNDArray.data = this._newStorageOfTheSameKind() ;
893
+
894
+ this._forEach( ( value , coords , index ) => {
895
+ newNDArray.data[ index - this.offset ] = callback( value , coords , index , this ) ;
896
+ } ) ;
897
+
898
+ return newNDArray ;
899
+ } ;
900
+
901
+ /*
902
+ Works like .map() but with a region, and also produce a reduced store.
903
+
904
+ .mapInRegion( region , callback )
905
+ .mapInRegion( mins , maxs , callback )
906
+ */
907
+ NDArray.prototype.mapInRegion = function( mins , maxs , callback ) {
908
+ if ( typeof maxs === 'function' ) {
909
+ callback = maxs ;
910
+ maxs = mins.map( minmax => minmax[ 1 ] ) ;
911
+ mins = mins.map( minmax => minmax[ 0 ] ) ;
912
+ }
913
+
914
+ this._checkRegion( mins , maxs ) ;
915
+ const newNDArray = this._resizeGeometry( mins , maxs ) ;
916
+ newNDArray.data = this._newStorageOfTheSameKind( newNDArray.size ) ;
917
+
918
+ this._dualStepCallback( newNDArray , mins , maxs , mins , maxs , callback ) ;
919
+
920
+ return newNDArray ;
921
+ } ;
922
+
923
+ /*
924
+ Works like .mapInRegion() but with vectors.
925
+
926
+ .mapVectorInRegion( region , callback )
927
+ .mapVectorInRegion( mins , maxs , callback )
928
+ */
929
+ NDArray.prototype.mapVectorInRegion = function( mins , maxs , callback ) {
930
+ let strideStartCoords , strideEndCoords , vectorDimension ;
931
+
932
+ if ( typeof maxs === 'function' ) {
933
+ callback = maxs ;
934
+ strideEndCoords = maxs = mins.map( minmax => minmax?.[ 1 ] ?? null ) ;
935
+ mins = mins.map( minmax => minmax?.[ 0 ] ?? null ) ;
936
+ [ strideStartCoords , vectorDimension ] = this._getStrideStartAndVectorDimension( mins ) ;
937
+ }
938
+ else {
939
+ [ strideStartCoords , vectorDimension ] = this._getStrideStartAndVectorDimension( mins ) ;
940
+ strideEndCoords = Array.from( maxs ) ;
941
+ }
942
+
943
+ strideEndCoords[ vectorDimension ] = this.maxs[ vectorDimension ] ;
944
+
945
+ this._checkRegion( strideStartCoords , strideEndCoords , vectorDimension ) ;
946
+ const newNDArray = this._resizeGeometry( strideStartCoords , strideEndCoords ) ;
947
+ newNDArray.data = this._newStorageOfTheSameKind( newNDArray.size ) ;
948
+
949
+ this._dualStepVectorCallback( newNDArray , strideStartCoords , strideEndCoords , strideStartCoords , strideEndCoords , vectorDimension , callback ) ;
950
+
951
+ return newNDArray ;
952
+ } ;
953
+
954
+
955
+
956
+ /*
957
+ Similar to .map(), but modify in-place.
958
+ */
959
+ NDArray.prototype.update = function( callback ) {
960
+ this._forEach( ( value , coords , index ) => {
961
+ this.data[ index ] = callback( value , coords , index , this ) ;
962
+ } ) ;
963
+
964
+ return this ;
965
+ } ;
966
+
967
+ /*
968
+ Modify a region in-place.
969
+
970
+ .updateInRegion( region , callback )
971
+ .updateInRegion( mins , maxs , callback )
972
+ */
973
+ NDArray.prototype.updateInRegion = function( mins , maxs , callback ) {
974
+ if ( typeof maxs === 'function' ) {
975
+ callback = maxs ;
976
+ maxs = mins.map( minmax => minmax[ 1 ] ) ;
977
+ mins = mins.map( minmax => minmax[ 0 ] ) ;
978
+ }
979
+
980
+ this._forEachInRegionCheck( mins , maxs , ( value , coords , index ) => {
981
+ this.data[ index ] = callback( value , coords , index , this ) ;
982
+ } ) ;
983
+
984
+ return this ;
985
+ } ;
986
+
987
+ /*
988
+ Works like .updateInRegion() but with vectors.
989
+
990
+ .updateVectorInRegion( region , callback )
991
+ .updateVectorInRegion( mins , maxs , callback )
992
+ */
993
+ NDArray.prototype.updateVectorInRegion = function( mins , maxs , callback ) {
994
+ let strideStartCoords , strideEndCoords , vectorDimension ;
995
+
996
+ if ( typeof maxs === 'function' ) {
997
+ callback = maxs ;
998
+ strideEndCoords = maxs = mins.map( minmax => minmax?.[ 1 ] ?? null ) ;
999
+ mins = mins.map( minmax => minmax?.[ 0 ] ?? null ) ;
1000
+ [ strideStartCoords , vectorDimension ] = this._getStrideStartAndVectorDimension( mins ) ;
1001
+ }
1002
+ else {
1003
+ [ strideStartCoords , vectorDimension ] = this._getStrideStartAndVectorDimension( mins ) ;
1004
+ strideEndCoords = Array.from( maxs ) ;
1005
+ }
1006
+
1007
+ strideEndCoords[ vectorDimension ] = this.maxs[ vectorDimension ] ;
1008
+
1009
+ this._forEachVectorInRegionCheck( strideStartCoords , strideEndCoords , vectorDimension , ( value , coords , index ) => {
1010
+ this._setVectorAtIndex( index , vectorDimension , callback( value , coords , index , this ) ) ;
1011
+ } ) ;
1012
+
1013
+ return this ;
1014
+ } ;
1015
+
1016
+
1017
+
1018
+ /*
1019
+ Extract an region into a new ND-array, it has a new data store only with the correct size.
1020
+
1021
+ .extractRegion( region )
1022
+ .extractRegion( mins , maxs )
1023
+ */
1024
+ NDArray.prototype.extractRegion = function( mins , maxs ) {
1025
+ if ( ! maxs ) {
1026
+ maxs = mins.map( minmax => minmax[ 1 ] ) ;
1027
+ mins = mins.map( minmax => minmax[ 0 ] ) ;
1028
+ }
1029
+
1030
+ this._checkRegion( mins , maxs ) ;
1031
+ const newNDArray = this._resizeGeometry( mins , maxs ) ;
1032
+ newNDArray.data = this._newStorageOfTheSameKind( newNDArray.size ) ;
1033
+
1034
+ this._dualStepCopy( newNDArray , mins , maxs , mins , maxs ) ;
1035
+
1036
+ return newNDArray ;
1037
+ } ;
1038
+
1039
+
1040
+
1041
+ /*
1042
+ Copy to another ND-array.
1043
+
1044
+ .copyTo( toNDArray )
1045
+ .copyTo( toNDArray , at )
1046
+ .copyTo( toNDArray , at , region )
1047
+ .copyTo( toNDArray , at , mins , maxs )
1048
+
1049
+ toNDArray: the destination ND-Array to copy into
1050
+ at: array vector, the position in the destination where to start copying (the mins of the copy region in the destination)
1051
+ if omitted: copy at the minimum (e.g. top-left for image, etc...)
1052
+ region: the region of the source to copy from, if omitted: the whole region
1053
+ mins: the mins of the region to copy from
1054
+ maxs: the maxs of the region to copy from
1055
+ */
1056
+ NDArray.prototype.copyTo = function( toNDArray , at , mins , maxs ) {
1057
+ if ( this.dimensions !== toNDArray.dimensions ) {
1058
+ throw new Error( ".copyTo(): uncompatible ND-arrays, the should have the same dimension (" + this.dimensions + "≠" + toNDArray.dimensions + ")" ) ;
1059
+ }
1060
+
1061
+ if ( ! at ) {
1062
+ at = Array.from( toNDArray.mins ) ;
1063
+ }
1064
+
1065
+ // mins and maxs should be copied, because that are later modified by ._clipRegion()
1066
+ if ( ! mins ) {
1067
+ mins = Array.from( this.mins ) ;
1068
+ maxs = Array.from( this.maxs ) ;
1069
+ }
1070
+ else if ( ! maxs ) {
1071
+ maxs = mins.map( minmax => minmax[ 1 ] ) ;
1072
+ mins = mins.map( minmax => minmax[ 0 ] ) ;
1073
+ }
1074
+ else {
1075
+ mins = Array.from( mins ) ;
1076
+ maxs = Array.from( maxs ) ;
1077
+ }
1078
+
1079
+ this._copyTo( toNDArray , at , mins , maxs ) ;
1080
+ } ;
1081
+
1082
+ NDArray.prototype._copyTo = function( toNDArray , at , mins , maxs ) {
1083
+ const shift = at.map( ( atCoord , d ) => atCoord - mins[ d ] ) ;
1084
+ const dstMins = Array.from( at ) ;
1085
+ const dstMaxs = maxs.map( ( max , d ) => max + shift[ d ] ) ;
1086
+
1087
+ this._clipRegion( mins , maxs ) ;
1088
+ toNDArray._clipRegion( dstMins , dstMaxs ) ;
1089
+
1090
+ if ( ! this._mutualClipRegion( mins , maxs , dstMins , dstMaxs , shift ) ) {
1091
+ // Nothing to do, everything was clipped away
1092
+ return ;
1093
+ }
1094
+
1095
+ let backward = false ;
1096
+
1097
+ if ( this.data === toNDArray.data ) {
1098
+ // This is a case of explicit or implicit copy-within
1099
+ // We can copy behind but not copy ahead with the regular ._dualStepCopy()
1100
+ const srcIndex = this._getIndex( mins ) ;
1101
+ const dstIndex = toNDArray._getIndex( dstMins ) ;
1102
+
1103
+ if ( srcIndex < dstIndex ) {
1104
+ backward = true ;
1105
+ }
1106
+ }
1107
+
1108
+ this._dualStepCopy( toNDArray , mins , maxs , dstMins , dstMaxs , backward ) ;
1109
+ } ;
1110
+
1111
+ /*
1112
+ Copy a part of the ND-array into itself
1113
+
1114
+ .copyWithin( at )
1115
+ .copyWithin( at , region )
1116
+ .copyWithin( at , mins , maxs )
1117
+
1118
+ See .copyTo() for details.
1119
+ */
1120
+ NDArray.prototype.copyWithin = function( at , mins , maxs ) { return this.copyTo( this , at , mins , maxs ) ; } ;
1121
+
1122
+
1123
+
1124
+ /*
1125
+ Combine into another ND-array.
1126
+ Like .copyTo() with a callback.
1127
+
1128
+ .combineInto( toNDArray , callback )
1129
+ .combineInto( toNDArray , at , callback )
1130
+ .combineInto( toNDArray , at , region , callback )
1131
+ .combineInto( toNDArray , at , mins , maxs , callback )
1132
+
1133
+ toNDArray: the destination ND-Array to copy into
1134
+ at: array vector, the position in the destination where to start copying (the mins of the copy region in the destination)
1135
+ if omitted: copy at the minimum (e.g. top-left for image, etc...)
1136
+ region: the region of the source to copy from, if omitted: the whole region
1137
+ mins: the mins of the region to copy from
1138
+ maxs: the maxs of the region to copy from
1139
+ callback( srcItem , dstItem ): the function to call, that should return the value for an element, where:
1140
+ srcItem: an object describing the source element of the current iteration, where:
1141
+ value: the current element value
1142
+ coords: the current element coords
1143
+ index: the current element index in the data storage
1144
+ dstItem: an object describing the destination element of the current iteration, same properties than srcItem.
1145
+ */
1146
+ NDArray.prototype.combineInto = function( toNDArray , at , mins , maxs , callback ) {
1147
+ if ( this.dimensions !== toNDArray.dimensions ) {
1148
+ throw new Error( ".combineInto(): uncompatible ND-arrays, the should have the same dimension (" + this.dimensions + "≠" + toNDArray.dimensions + ")" ) ;
1149
+ }
1150
+
1151
+ // mins and maxs should be copied, because that are later modified by ._clipRegion()
1152
+ if ( typeof at === 'function' ) {
1153
+ callback = at ;
1154
+ at = Array.from( toNDArray.mins ) ;
1155
+ mins = Array.from( this.mins ) ;
1156
+ maxs = Array.from( this.maxs ) ;
1157
+ }
1158
+ else if ( typeof mins === 'function' ) {
1159
+ callback = mins ;
1160
+ mins = Array.from( this.mins ) ;
1161
+ maxs = Array.from( this.maxs ) ;
1162
+ }
1163
+ else if ( typeof maxs === 'function' ) {
1164
+ callback = maxs ;
1165
+ maxs = mins.map( minmax => minmax[ 1 ] ) ;
1166
+ mins = mins.map( minmax => minmax[ 0 ] ) ;
1167
+ }
1168
+ else {
1169
+ mins = Array.from( mins ) ;
1170
+ maxs = Array.from( maxs ) ;
1171
+ }
1172
+
1173
+ this._combineInto( toNDArray , at , mins , maxs , callback ) ;
1174
+ } ;
1175
+
1176
+ NDArray.prototype._combineInto = function( toNDArray , at , mins , maxs , callback ) {
1177
+ const shift = at.map( ( atCoord , d ) => atCoord - mins[ d ] ) ;
1178
+ const dstMins = Array.from( at ) ;
1179
+ const dstMaxs = maxs.map( ( max , d ) => max + shift[ d ] ) ;
1180
+
1181
+ this._clipRegion( mins , maxs ) ;
1182
+ toNDArray._clipRegion( dstMins , dstMaxs ) ;
1183
+
1184
+ if ( ! this._mutualClipRegion( mins , maxs , dstMins , dstMaxs , shift ) ) {
1185
+ // Nothing to do, everything was clipped away
1186
+ return ;
1187
+ }
1188
+
1189
+ let backward = false ;
1190
+
1191
+ if ( this.data === toNDArray.data ) {
1192
+ // This is a case of explicit or implicit copy-within
1193
+ // We can copy behind but not copy ahead with the regular ._dualStepCopy()
1194
+ const srcIndex = this._getIndex( mins ) ;
1195
+ const dstIndex = toNDArray._getIndex( dstMins ) ;
1196
+
1197
+ if ( srcIndex < dstIndex ) {
1198
+ backward = true ;
1199
+ }
1200
+ }
1201
+
1202
+ this._dualStepCallbackWithDst( toNDArray , mins , maxs , dstMins , dstMaxs , callback , backward ) ;
1203
+ } ;
1204
+
1205
+ /*
1206
+ Combine a part of the ND-array with itself
1207
+
1208
+ .combineWithin( at , callback )
1209
+ .combineWithin( at , region , callback )
1210
+ .combineWithin( at , mins , maxs , callback )
1211
+
1212
+ See .combineInto() for details.
1213
+ */
1214
+ NDArray.prototype.combineWithin = function( at , mins , maxs , callback ) { return this.combineInto( this , at , mins , maxs , callback ) ; } ;
1215
+
1216
+
1217
+
1218
+ /*
1219
+ Combine vectors into another ND-array.
1220
+ Like .combineInto() but the callback receive a vector as the value.
1221
+
1222
+ .combineVectorInto( toNDArray , vectorDimension , callback )
1223
+ .combineVectorInto( toNDArray , at , vectorDimension , callback )
1224
+ .combineVectorInto( toNDArray , at , region , callback )
1225
+ .combineVectorInto( toNDArray , at , mins , maxs , callback )
1226
+
1227
+ toNDArray: the destination ND-Array to copy into
1228
+ vectorDimension : the dimension along which the vector lie
1229
+ at: array vector, the position in the destination where to start copying (the mins of the copy region in the destination)
1230
+ if omitted: copy at the minimum (e.g. top-left for image, etc...)
1231
+ region: the region of the source to copy from, if omitted: the whole region
1232
+ mins: the mins of the region to copy from
1233
+ maxs: the maxs of the region to copy from
1234
+ callback( srcItem , dstItem ): the function to call, that should return the value for an element, where:
1235
+ srcItem: an object describing the source element of the current iteration, where:
1236
+ value: the current element vector
1237
+ coords: the current element coords
1238
+ index: the current element index in the data storage
1239
+ dstItem: an object describing the destination element of the current iteration, same properties than srcItem.
1240
+ */
1241
+ NDArray.prototype.combineVectorInto = function( toNDArray , at , mins , maxs , callback ) {
1242
+ if ( this.dimensions !== toNDArray.dimensions ) {
1243
+ throw new Error( ".combineVectorInto(): uncompatible ND-arrays, they should have the same dimension (" + this.dimensions + "≠" + toNDArray.dimensions + ")" ) ;
1244
+ }
1245
+
1246
+ let strideStartCoords , strideEndCoords , vectorDimension ;
1247
+
1248
+ // at, mins and maxs should be copied, because that are later modified by ._clipRegion()
1249
+ if ( typeof mins === 'function' ) {
1250
+ // .combineVectorInto( toNDArray , vectorDimension , callback )
1251
+ callback = mins ;
1252
+ vectorDimension = at ;
1253
+ at = Array.from( toNDArray.mins ) ;
1254
+ strideStartCoords = mins = Array.from( this.mins ) ;
1255
+ strideEndCoords = maxs = Array.from( this.maxs ) ;
1256
+ }
1257
+ else if ( typeof maxs === 'function' ) {
1258
+ if ( typeof mins === 'number' ) {
1259
+ // .combineVectorInto( toNDArray , at , vectorDimension , callback )
1260
+ callback = maxs ;
1261
+ vectorDimension = mins ;
1262
+ at = Array.from( at ) ;
1263
+ strideStartCoords = mins = Array.from( this.mins ) ;
1264
+ strideEndCoords = maxs = Array.from( this.maxs ) ;
1265
+ }
1266
+ else {
1267
+ // .combineVectorInto( toNDArray , at , region , callback )
1268
+ callback = maxs ;
1269
+ at = Array.from( at ) ;
1270
+ strideEndCoords = maxs = mins.map( minmax => minmax?.[ 1 ] ?? null ) ;
1271
+ mins = mins.map( minmax => minmax?.[ 0 ] ?? null ) ;
1272
+ [ strideStartCoords , vectorDimension ] = this._getStrideStartAndVectorDimension( mins ) ;
1273
+ }
1274
+ }
1275
+ else {
1276
+ // .combineVectorInto( toNDArray , at , mins , maxs , callback )
1277
+ at = Array.from( at ) ;
1278
+ [ strideStartCoords , vectorDimension ] = this._getStrideStartAndVectorDimension( mins ) ;
1279
+ strideEndCoords = Array.from( maxs ) ;
1280
+ }
1281
+
1282
+ at[ vectorDimension ] = this.mins[ vectorDimension ] ;
1283
+ strideEndCoords[ vectorDimension ] = this.maxs[ vectorDimension ] ;
1284
+
1285
+ if ( this.sizes[ vectorDimension ] !== toNDArray.sizes[ vectorDimension ] ) {
1286
+ throw new Error( ".combineVectorInto(): uncompatible ND-arrays, they should have the same size along the vector's dimension (" + vectorDimension + ", but" + this.sizes[ vectorDimension ] + "≠" + toNDArray.sizes[ vectorDimension ] + ")" ) ;
1287
+ }
1288
+
1289
+ this._combineVectorInto( toNDArray , at , strideStartCoords , strideEndCoords , vectorDimension , callback ) ;
1290
+ } ;
1291
+
1292
+ NDArray.prototype._combineVectorInto = function( toNDArray , at , strideStartCoords , strideEndCoords , vectorDimension , callback ) {
1293
+ const shift = at.map( ( atCoord , d ) => atCoord - strideStartCoords[ d ] ) ;
1294
+ const dstStrideStartCoords = Array.from( at ) ;
1295
+ const dstStrideEndCoords = strideEndCoords.map( ( max , d ) => max + shift[ d ] ) ;
1296
+
1297
+ this._clipRegion( strideStartCoords , strideEndCoords , vectorDimension ) ;
1298
+ toNDArray._clipRegion( dstStrideStartCoords , dstStrideEndCoords , vectorDimension ) ;
1299
+
1300
+ if ( ! this._mutualClipRegion( strideStartCoords , strideEndCoords , dstStrideStartCoords , dstStrideEndCoords , shift , vectorDimension ) ) {
1301
+ // Nothing to do, everything was clipped away
1302
+ return ;
1303
+ }
1304
+
1305
+ let backward = false ;
1306
+
1307
+ if ( this.data === toNDArray.data ) {
1308
+ // This is a case of explicit or implicit copy-within
1309
+ // We can copy behind but not copy ahead with the regular ._dualStepCopy()
1310
+ const srcIndex = this._getIndex( strideStartCoords ) ;
1311
+ const dstIndex = toNDArray._getIndex( dstStrideStartCoords ) ;
1312
+
1313
+ if ( srcIndex < dstIndex ) {
1314
+ backward = true ;
1315
+ }
1316
+ }
1317
+
1318
+ this._dualStepVectorCallbackWithDst(
1319
+ toNDArray ,
1320
+ strideStartCoords , strideEndCoords ,
1321
+ dstStrideStartCoords , dstStrideEndCoords ,
1322
+ vectorDimension ,
1323
+ callback ,
1324
+ backward
1325
+ ) ;
1326
+ } ;
1327
+
1328
+ /*
1329
+ Combine a part of the ND-array with itself, vector by vector.
1330
+
1331
+ .combineVectorWithin( at , callback )
1332
+ .combineVectorWithin( at , region , callback )
1333
+ .combineVectorWithin( at , mins , maxs , callback )
1334
+
1335
+ See .combineVectorInto() for details.
1336
+ */
1337
+ NDArray.prototype.combineVectorWithin = function( at , mins , maxs , callback ) { return this.combineVectorInto( this , at , mins , maxs , callback ) ; } ;
1338
+
1339
+
1340
+
1341
+ // /!\ WARNING /!\ All _dualStep*() methods are for INTERNAL USAGE ONLY /!\
1342
+ // It only works if both src and dst minmax have the same size!
1343
+
1344
+ NDArray.prototype._dualStepCopy = function( dst , mins , maxs , dstMins , dstMaxs , backward = false ) {
1345
+ let srcGen , dstGen , srcItem , dstItem ;
1346
+
1347
+ if ( backward ) {
1348
+ srcGen = this._eachInRegionBackward( mins , maxs ) ;
1349
+ dstGen = dst._eachInRegionBackward( dstMins , dstMaxs ) ;
1350
+ }
1351
+ else {
1352
+ srcGen = this._eachInRegion( mins , maxs ) ;
1353
+ dstGen = dst._eachInRegion( dstMins , dstMaxs ) ;
1354
+ }
1355
+
1356
+ for ( ;; ) {
1357
+ srcItem = srcGen.next().value ;
1358
+ if ( ! srcItem ) { return ; }
1359
+ dstItem = dstGen.next().value ;
1360
+ dst.data[ dstItem.index ] = srcItem.value ;
1361
+ }
1362
+ } ;
1363
+
1364
+ NDArray.prototype._dualStepCallback = function( dst , mins , maxs , dstMins , dstMaxs , callback , backward = false ) {
1365
+ let srcGen , dstGen , srcItem , dstItem ;
1366
+
1367
+ if ( backward ) {
1368
+ srcGen = this._eachInRegionBackward( mins , maxs ) ;
1369
+ dstGen = dst._eachInRegionBackward( dstMins , dstMaxs ) ;
1370
+ }
1371
+ else {
1372
+ srcGen = this._eachInRegion( mins , maxs ) ;
1373
+ dstGen = dst._eachInRegion( dstMins , dstMaxs ) ;
1374
+ }
1375
+
1376
+ for ( ;; ) {
1377
+ srcItem = srcGen.next().value ;
1378
+ if ( ! srcItem ) { return ; }
1379
+ dstItem = dstGen.next().value ;
1380
+ dst.data[ dstItem.index ] = callback( srcItem.value , srcItem.coords , srcItem.index , this ) ;
1381
+ }
1382
+ } ;
1383
+
1384
+ NDArray.prototype._dualStepCallbackWithDst = function( dst , mins , maxs , dstMins , dstMaxs , callback , backward = false ) {
1385
+ let srcGen , dstGen , srcItem , dstItem ;
1386
+
1387
+ if ( backward ) {
1388
+ srcGen = this._eachInRegionBackward( mins , maxs ) ;
1389
+ dstGen = dst._eachInRegionBackward( dstMins , dstMaxs ) ;
1390
+ }
1391
+ else {
1392
+ srcGen = this._eachInRegion( mins , maxs ) ;
1393
+ dstGen = dst._eachInRegion( dstMins , dstMaxs ) ;
1394
+ }
1395
+
1396
+ for ( ;; ) {
1397
+ srcItem = srcGen.next().value ;
1398
+ if ( ! srcItem ) { return ; }
1399
+ dstItem = dstGen.next().value ;
1400
+ dst.data[ dstItem.index ] = callback( srcItem , dstItem , this ) ;
1401
+ }
1402
+ } ;
1403
+
1404
+ NDArray.prototype._dualStepVectorCallback = function(
1405
+ dst ,
1406
+ strideStartCoords , strideEndCoords ,
1407
+ dstStrideStartCoords , dstStrideEndCoords ,
1408
+ vectorDimension ,
1409
+ callback ,
1410
+ backward = false
1411
+ ) {
1412
+ let srcGen , dstGen , srcItem , dstItem ;
1413
+
1414
+ if ( backward ) {
1415
+ srcGen = this._eachVectorInRegionBackward( strideStartCoords , strideEndCoords , vectorDimension ) ;
1416
+ dstGen = dst._eachVectorIndexInRegionBackward( dstStrideStartCoords , dstStrideEndCoords , vectorDimension ) ;
1417
+ }
1418
+ else {
1419
+ srcGen = this._eachVectorInRegion( strideStartCoords , strideEndCoords , vectorDimension ) ;
1420
+ dstGen = dst._eachVectorIndexInRegion( dstStrideStartCoords , dstStrideEndCoords , vectorDimension ) ;
1421
+ }
1422
+
1423
+ for ( ;; ) {
1424
+ srcItem = srcGen.next().value ;
1425
+ if ( ! srcItem ) { return ; }
1426
+ dstItem = dstGen.next().value ;
1427
+ dst._setVectorAtIndex( dstItem.index , vectorDimension , callback( srcItem.value , srcItem.coords , srcItem.index , this ) ) ;
1428
+ }
1429
+ } ;
1430
+
1431
+ NDArray.prototype._dualStepVectorCallbackWithDst = function(
1432
+ dst ,
1433
+ strideStartCoords , strideEndCoords ,
1434
+ dstStrideStartCoords , dstStrideEndCoords ,
1435
+ vectorDimension ,
1436
+ callback ,
1437
+ backward = false
1438
+ ) {
1439
+ let srcGen , dstGen , srcItem , dstItem ;
1440
+
1441
+ if ( backward ) {
1442
+ srcGen = this._eachVectorInRegionBackward( strideStartCoords , strideEndCoords , vectorDimension ) ;
1443
+ dstGen = dst._eachVectorInRegionBackward( dstStrideStartCoords , dstStrideEndCoords , vectorDimension ) ;
1444
+ }
1445
+ else {
1446
+ srcGen = this._eachVectorInRegion( strideStartCoords , strideEndCoords , vectorDimension ) ;
1447
+ dstGen = dst._eachVectorInRegion( dstStrideStartCoords , dstStrideEndCoords , vectorDimension ) ;
1448
+ }
1449
+
1450
+ for ( ;; ) {
1451
+ srcItem = srcGen.next().value ;
1452
+ if ( ! srcItem ) { return ; }
1453
+ dstItem = dstGen.next().value ;
1454
+ dst._setVectorAtIndex( dstItem.index , vectorDimension , callback( srcItem , dstItem , this ) ) ;
1455
+ }
1456
+ } ;
1457
+
1458
+
1459
+
1460
+ NDArray.prototype._checkRegion = function( mins , maxs , skipD = null ) {
1461
+ // Check mins/maxs range errors
1462
+ for ( let d = 0 ; d < this.dimensions ; d ++ ) {
1463
+ if (
1464
+ d !== skipD
1465
+ && (
1466
+ mins[ d ] > maxs[ d ]
1467
+ || mins[ d ] < this.mins[ d ]
1468
+ || mins[ d ] > this.maxs[ d ]
1469
+ || maxs[ d ] < this.mins[ d ]
1470
+ || maxs[ d ] > this.maxs[ d ]
1471
+ )
1472
+ ) {
1473
+ throw new RangeError( "Min-max coordinate [" + mins[ d ] + "," + maxs[ d ] + "] out of bounds for dimension #" + d + " which is [" + this.mins[ d ] + "," + this.maxs[ d ] + "]" ) ;
1474
+ }
1475
+ }
1476
+ } ;
1477
+
1478
+
1479
+
1480
+ NDArray.prototype._clipRegion = function( mins , maxs , vectorDimension = null ) {
1481
+ for ( let d = 0 ; d < this.dimensions ; d ++ ) {
1482
+ if ( d !== vectorDimension ) {
1483
+ mins[ d ] = Math.max( this.mins[ d ] , Math.min( this.maxs[ d ] , maxs[ d ] , mins[ d ] ) ) ;
1484
+ maxs[ d ] = Math.max( this.mins[ d ] , Math.min( this.maxs[ d ] , maxs[ d ] ) ) ;
1485
+ }
1486
+ }
1487
+ } ;
1488
+
1489
+
1490
+
1491
+ // Return true if there is still a non-zero region, false otherwise
1492
+ NDArray.prototype._mutualClipRegion = function( mins , maxs , dstMins , dstMaxs , shift , vectorDimension = null ) {
1493
+ for ( let d = 0 ; d < this.dimensions ; d ++ ) {
1494
+ // Check if they have the same size
1495
+ if ( d !== vectorDimension && maxs[ d ] - mins[ d ] !== dstMaxs[ d ] - dstMins[ d ] ) {
1496
+ // Need clipping!
1497
+
1498
+ // unshift dst minmax
1499
+ let uDstMin = dstMins[ d ] - shift[ d ] ;
1500
+ let uDstMax = dstMaxs[ d ] - shift[ d ] ;
1501
+ uDstMin = mins[ d ] = Math.max( uDstMin , mins[ d ] ) ;
1502
+ uDstMax = maxs[ d ] = Math.min( uDstMax , maxs[ d ] ) ;
1503
+
1504
+ // Check if the mutual region still exist or has been entirely clipped away
1505
+ if ( uDstMin > uDstMax ) { return false ; }
1506
+
1507
+ // shift again dst minmax
1508
+ dstMins[ d ] = uDstMin + shift[ d ] ;
1509
+ dstMaxs[ d ] = uDstMax + shift[ d ] ;
1510
+ }
1511
+ }
1512
+
1513
+ return true ;
1514
+ } ;
1515
+