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