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 +1515 -0
- package/lib/array-kit.js +2 -1
- package/package.json +1 -1
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
|
+
|