StackGenVis: Alignment of Data, Algorithms, and Models for Stacking Ensemble Learning Using Performance Metrics
https://doi.org/10.1109/TVCG.2020.3030352
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
561 lines
16 KiB
561 lines
16 KiB
'use strict'
|
|
|
|
var ndarray = require('ndarray')
|
|
var ops = require('ndarray-ops')
|
|
var pool = require('typedarray-pool')
|
|
|
|
module.exports = createTexture2D
|
|
|
|
var linearTypes = null
|
|
var filterTypes = null
|
|
var wrapTypes = null
|
|
|
|
function lazyInitLinearTypes(gl) {
|
|
linearTypes = [
|
|
gl.LINEAR,
|
|
gl.NEAREST_MIPMAP_LINEAR,
|
|
gl.LINEAR_MIPMAP_NEAREST,
|
|
gl.LINEAR_MIPMAP_NEAREST
|
|
]
|
|
filterTypes = [
|
|
gl.NEAREST,
|
|
gl.LINEAR,
|
|
gl.NEAREST_MIPMAP_NEAREST,
|
|
gl.NEAREST_MIPMAP_LINEAR,
|
|
gl.LINEAR_MIPMAP_NEAREST,
|
|
gl.LINEAR_MIPMAP_LINEAR
|
|
]
|
|
wrapTypes = [
|
|
gl.REPEAT,
|
|
gl.CLAMP_TO_EDGE,
|
|
gl.MIRRORED_REPEAT
|
|
]
|
|
}
|
|
|
|
function acceptTextureDOM (obj) {
|
|
return (
|
|
('undefined' != typeof HTMLCanvasElement && obj instanceof HTMLCanvasElement) ||
|
|
('undefined' != typeof HTMLImageElement && obj instanceof HTMLImageElement) ||
|
|
('undefined' != typeof HTMLVideoElement && obj instanceof HTMLVideoElement) ||
|
|
('undefined' != typeof ImageData && obj instanceof ImageData))
|
|
}
|
|
|
|
var convertFloatToUint8 = function(out, inp) {
|
|
ops.muls(out, inp, 255.0)
|
|
}
|
|
|
|
function reshapeTexture(tex, w, h) {
|
|
var gl = tex.gl
|
|
var maxSize = gl.getParameter(gl.MAX_TEXTURE_SIZE)
|
|
if(w < 0 || w > maxSize || h < 0 || h > maxSize) {
|
|
throw new Error('gl-texture2d: Invalid texture size')
|
|
}
|
|
tex._shape = [w, h]
|
|
tex.bind()
|
|
gl.texImage2D(gl.TEXTURE_2D, 0, tex.format, w, h, 0, tex.format, tex.type, null)
|
|
tex._mipLevels = [0]
|
|
return tex
|
|
}
|
|
|
|
function Texture2D(gl, handle, width, height, format, type) {
|
|
this.gl = gl
|
|
this.handle = handle
|
|
this.format = format
|
|
this.type = type
|
|
this._shape = [width, height]
|
|
this._mipLevels = [0]
|
|
this._magFilter = gl.NEAREST
|
|
this._minFilter = gl.NEAREST
|
|
this._wrapS = gl.CLAMP_TO_EDGE
|
|
this._wrapT = gl.CLAMP_TO_EDGE
|
|
this._anisoSamples = 1
|
|
|
|
var parent = this
|
|
var wrapVector = [this._wrapS, this._wrapT]
|
|
Object.defineProperties(wrapVector, [
|
|
{
|
|
get: function() {
|
|
return parent._wrapS
|
|
},
|
|
set: function(v) {
|
|
return parent.wrapS = v
|
|
}
|
|
},
|
|
{
|
|
get: function() {
|
|
return parent._wrapT
|
|
},
|
|
set: function(v) {
|
|
return parent.wrapT = v
|
|
}
|
|
}
|
|
])
|
|
this._wrapVector = wrapVector
|
|
|
|
var shapeVector = [this._shape[0], this._shape[1]]
|
|
Object.defineProperties(shapeVector, [
|
|
{
|
|
get: function() {
|
|
return parent._shape[0]
|
|
},
|
|
set: function(v) {
|
|
return parent.width = v
|
|
}
|
|
},
|
|
{
|
|
get: function() {
|
|
return parent._shape[1]
|
|
},
|
|
set: function(v) {
|
|
return parent.height = v
|
|
}
|
|
}
|
|
])
|
|
this._shapeVector = shapeVector
|
|
}
|
|
|
|
var proto = Texture2D.prototype
|
|
|
|
Object.defineProperties(proto, {
|
|
minFilter: {
|
|
get: function() {
|
|
return this._minFilter
|
|
},
|
|
set: function(v) {
|
|
this.bind()
|
|
var gl = this.gl
|
|
if(this.type === gl.FLOAT && linearTypes.indexOf(v) >= 0) {
|
|
if(!gl.getExtension('OES_texture_float_linear')) {
|
|
v = gl.NEAREST
|
|
}
|
|
}
|
|
if(filterTypes.indexOf(v) < 0) {
|
|
throw new Error('gl-texture2d: Unknown filter mode ' + v)
|
|
}
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, v)
|
|
return this._minFilter = v
|
|
}
|
|
},
|
|
magFilter: {
|
|
get: function() {
|
|
return this._magFilter
|
|
},
|
|
set: function(v) {
|
|
this.bind()
|
|
var gl = this.gl
|
|
if(this.type === gl.FLOAT && linearTypes.indexOf(v) >= 0) {
|
|
if(!gl.getExtension('OES_texture_float_linear')) {
|
|
v = gl.NEAREST
|
|
}
|
|
}
|
|
if(filterTypes.indexOf(v) < 0) {
|
|
throw new Error('gl-texture2d: Unknown filter mode ' + v)
|
|
}
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, v)
|
|
return this._magFilter = v
|
|
}
|
|
},
|
|
mipSamples: {
|
|
get: function() {
|
|
return this._anisoSamples
|
|
},
|
|
set: function(i) {
|
|
var psamples = this._anisoSamples
|
|
this._anisoSamples = Math.max(i, 1)|0
|
|
if(psamples !== this._anisoSamples) {
|
|
var ext = this.gl.getExtension('EXT_texture_filter_anisotropic')
|
|
if(ext) {
|
|
this.gl.texParameterf(this.gl.TEXTURE_2D, ext.TEXTURE_MAX_ANISOTROPY_EXT, this._anisoSamples)
|
|
}
|
|
}
|
|
return this._anisoSamples
|
|
}
|
|
},
|
|
wrapS: {
|
|
get: function() {
|
|
return this._wrapS
|
|
},
|
|
set: function(v) {
|
|
this.bind()
|
|
if(wrapTypes.indexOf(v) < 0) {
|
|
throw new Error('gl-texture2d: Unknown wrap mode ' + v)
|
|
}
|
|
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, v)
|
|
return this._wrapS = v
|
|
}
|
|
},
|
|
wrapT: {
|
|
get: function() {
|
|
return this._wrapT
|
|
},
|
|
set: function(v) {
|
|
this.bind()
|
|
if(wrapTypes.indexOf(v) < 0) {
|
|
throw new Error('gl-texture2d: Unknown wrap mode ' + v)
|
|
}
|
|
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, v)
|
|
return this._wrapT = v
|
|
}
|
|
},
|
|
wrap: {
|
|
get: function() {
|
|
return this._wrapVector
|
|
},
|
|
set: function(v) {
|
|
if(!Array.isArray(v)) {
|
|
v = [v,v]
|
|
}
|
|
if(v.length !== 2) {
|
|
throw new Error('gl-texture2d: Must specify wrap mode for rows and columns')
|
|
}
|
|
for(var i=0; i<2; ++i) {
|
|
if(wrapTypes.indexOf(v[i]) < 0) {
|
|
throw new Error('gl-texture2d: Unknown wrap mode ' + v)
|
|
}
|
|
}
|
|
this._wrapS = v[0]
|
|
this._wrapT = v[1]
|
|
|
|
var gl = this.gl
|
|
this.bind()
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, this._wrapS)
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, this._wrapT)
|
|
|
|
return v
|
|
}
|
|
},
|
|
shape: {
|
|
get: function() {
|
|
return this._shapeVector
|
|
},
|
|
set: function(x) {
|
|
if(!Array.isArray(x)) {
|
|
x = [x|0,x|0]
|
|
} else {
|
|
if(x.length !== 2) {
|
|
throw new Error('gl-texture2d: Invalid texture shape')
|
|
}
|
|
}
|
|
reshapeTexture(this, x[0]|0, x[1]|0)
|
|
return [x[0]|0, x[1]|0]
|
|
}
|
|
},
|
|
width: {
|
|
get: function() {
|
|
return this._shape[0]
|
|
},
|
|
set: function(w) {
|
|
w = w|0
|
|
reshapeTexture(this, w, this._shape[1])
|
|
return w
|
|
}
|
|
},
|
|
height: {
|
|
get: function() {
|
|
return this._shape[1]
|
|
},
|
|
set: function(h) {
|
|
h = h|0
|
|
reshapeTexture(this, this._shape[0], h)
|
|
return h
|
|
}
|
|
}
|
|
})
|
|
|
|
proto.bind = function(unit) {
|
|
var gl = this.gl
|
|
if(unit !== undefined) {
|
|
gl.activeTexture(gl.TEXTURE0 + (unit|0))
|
|
}
|
|
gl.bindTexture(gl.TEXTURE_2D, this.handle)
|
|
if(unit !== undefined) {
|
|
return (unit|0)
|
|
}
|
|
return gl.getParameter(gl.ACTIVE_TEXTURE) - gl.TEXTURE0
|
|
}
|
|
|
|
proto.dispose = function() {
|
|
this.gl.deleteTexture(this.handle)
|
|
}
|
|
|
|
proto.generateMipmap = function() {
|
|
this.bind()
|
|
this.gl.generateMipmap(this.gl.TEXTURE_2D)
|
|
|
|
//Update mip levels
|
|
var l = Math.min(this._shape[0], this._shape[1])
|
|
for(var i=0; l>0; ++i, l>>>=1) {
|
|
if(this._mipLevels.indexOf(i) < 0) {
|
|
this._mipLevels.push(i)
|
|
}
|
|
}
|
|
}
|
|
|
|
proto.setPixels = function(data, x_off, y_off, mip_level) {
|
|
var gl = this.gl
|
|
this.bind()
|
|
if(Array.isArray(x_off)) {
|
|
mip_level = y_off
|
|
y_off = x_off[1]|0
|
|
x_off = x_off[0]|0
|
|
} else {
|
|
x_off = x_off || 0
|
|
y_off = y_off || 0
|
|
}
|
|
mip_level = mip_level || 0
|
|
var directData = acceptTextureDOM(data) ? data : data.raw
|
|
if(directData) {
|
|
var needsMip = this._mipLevels.indexOf(mip_level) < 0
|
|
if(needsMip) {
|
|
gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.format, this.type, directData)
|
|
this._mipLevels.push(mip_level)
|
|
} else {
|
|
gl.texSubImage2D(gl.TEXTURE_2D, mip_level, x_off, y_off, this.format, this.type, directData)
|
|
}
|
|
} else if(data.shape && data.stride && data.data) {
|
|
if(data.shape.length < 2 ||
|
|
x_off + data.shape[1] > this._shape[1]>>>mip_level ||
|
|
y_off + data.shape[0] > this._shape[0]>>>mip_level ||
|
|
x_off < 0 ||
|
|
y_off < 0) {
|
|
throw new Error('gl-texture2d: Texture dimensions are out of bounds')
|
|
}
|
|
texSubImageArray(gl, x_off, y_off, mip_level, this.format, this.type, this._mipLevels, data)
|
|
} else {
|
|
throw new Error('gl-texture2d: Unsupported data type')
|
|
}
|
|
}
|
|
|
|
|
|
function isPacked(shape, stride) {
|
|
if(shape.length === 3) {
|
|
return (stride[2] === 1) &&
|
|
(stride[1] === shape[0]*shape[2]) &&
|
|
(stride[0] === shape[2])
|
|
}
|
|
return (stride[0] === 1) &&
|
|
(stride[1] === shape[0])
|
|
}
|
|
|
|
function texSubImageArray(gl, x_off, y_off, mip_level, cformat, ctype, mipLevels, array) {
|
|
var dtype = array.dtype
|
|
var shape = array.shape.slice()
|
|
if(shape.length < 2 || shape.length > 3) {
|
|
throw new Error('gl-texture2d: Invalid ndarray, must be 2d or 3d')
|
|
}
|
|
var type = 0, format = 0
|
|
var packed = isPacked(shape, array.stride.slice())
|
|
if(dtype === 'float32') {
|
|
type = gl.FLOAT
|
|
} else if(dtype === 'float64') {
|
|
type = gl.FLOAT
|
|
packed = false
|
|
dtype = 'float32'
|
|
} else if(dtype === 'uint8') {
|
|
type = gl.UNSIGNED_BYTE
|
|
} else {
|
|
type = gl.UNSIGNED_BYTE
|
|
packed = false
|
|
dtype = 'uint8'
|
|
}
|
|
var channels = 1
|
|
if(shape.length === 2) {
|
|
format = gl.LUMINANCE
|
|
shape = [shape[0], shape[1], 1]
|
|
array = ndarray(array.data, shape, [array.stride[0], array.stride[1], 1], array.offset)
|
|
} else if(shape.length === 3) {
|
|
if(shape[2] === 1) {
|
|
format = gl.ALPHA
|
|
} else if(shape[2] === 2) {
|
|
format = gl.LUMINANCE_ALPHA
|
|
} else if(shape[2] === 3) {
|
|
format = gl.RGB
|
|
} else if(shape[2] === 4) {
|
|
format = gl.RGBA
|
|
} else {
|
|
throw new Error('gl-texture2d: Invalid shape for pixel coords')
|
|
}
|
|
channels = shape[2]
|
|
} else {
|
|
throw new Error('gl-texture2d: Invalid shape for texture')
|
|
}
|
|
//For 1-channel textures allow conversion between formats
|
|
if((format === gl.LUMINANCE || format === gl.ALPHA) &&
|
|
(cformat === gl.LUMINANCE || cformat === gl.ALPHA)) {
|
|
format = cformat
|
|
}
|
|
if(format !== cformat) {
|
|
throw new Error('gl-texture2d: Incompatible texture format for setPixels')
|
|
}
|
|
var size = array.size
|
|
var needsMip = mipLevels.indexOf(mip_level) < 0
|
|
if(needsMip) {
|
|
mipLevels.push(mip_level)
|
|
}
|
|
if(type === ctype && packed) {
|
|
//Array data types are compatible, can directly copy into texture
|
|
if(array.offset === 0 && array.data.length === size) {
|
|
if(needsMip) {
|
|
gl.texImage2D(gl.TEXTURE_2D, mip_level, cformat, shape[0], shape[1], 0, cformat, ctype, array.data)
|
|
} else {
|
|
gl.texSubImage2D(gl.TEXTURE_2D, mip_level, x_off, y_off, shape[0], shape[1], cformat, ctype, array.data)
|
|
}
|
|
} else {
|
|
if(needsMip) {
|
|
gl.texImage2D(gl.TEXTURE_2D, mip_level, cformat, shape[0], shape[1], 0, cformat, ctype, array.data.subarray(array.offset, array.offset+size))
|
|
} else {
|
|
gl.texSubImage2D(gl.TEXTURE_2D, mip_level, x_off, y_off, shape[0], shape[1], cformat, ctype, array.data.subarray(array.offset, array.offset+size))
|
|
}
|
|
}
|
|
} else {
|
|
//Need to do type conversion to pack data into buffer
|
|
var pack_buffer
|
|
if(ctype === gl.FLOAT) {
|
|
pack_buffer = pool.mallocFloat32(size)
|
|
} else {
|
|
pack_buffer = pool.mallocUint8(size)
|
|
}
|
|
var pack_view = ndarray(pack_buffer, shape, [shape[2], shape[2]*shape[0], 1])
|
|
if(type === gl.FLOAT && ctype === gl.UNSIGNED_BYTE) {
|
|
convertFloatToUint8(pack_view, array)
|
|
} else {
|
|
ops.assign(pack_view, array)
|
|
}
|
|
if(needsMip) {
|
|
gl.texImage2D(gl.TEXTURE_2D, mip_level, cformat, shape[0], shape[1], 0, cformat, ctype, pack_buffer.subarray(0, size))
|
|
} else {
|
|
gl.texSubImage2D(gl.TEXTURE_2D, mip_level, x_off, y_off, shape[0], shape[1], cformat, ctype, pack_buffer.subarray(0, size))
|
|
}
|
|
if(ctype === gl.FLOAT) {
|
|
pool.freeFloat32(pack_buffer)
|
|
} else {
|
|
pool.freeUint8(pack_buffer)
|
|
}
|
|
}
|
|
}
|
|
|
|
function initTexture(gl) {
|
|
var tex = gl.createTexture()
|
|
gl.bindTexture(gl.TEXTURE_2D, tex)
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
|
return tex
|
|
}
|
|
|
|
function createTextureShape(gl, width, height, format, type) {
|
|
var maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE)
|
|
if(width < 0 || width > maxTextureSize || height < 0 || height > maxTextureSize) {
|
|
throw new Error('gl-texture2d: Invalid texture shape')
|
|
}
|
|
if(type === gl.FLOAT && !gl.getExtension('OES_texture_float')) {
|
|
throw new Error('gl-texture2d: Floating point textures not supported on this platform')
|
|
}
|
|
var tex = initTexture(gl)
|
|
gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, type, null)
|
|
return new Texture2D(gl, tex, width, height, format, type)
|
|
}
|
|
|
|
function createTextureDOM(gl, directData, width, height, format, type) {
|
|
var tex = initTexture(gl)
|
|
gl.texImage2D(gl.TEXTURE_2D, 0, format, format, type, directData)
|
|
return new Texture2D(gl, tex, width, height, format, type)
|
|
}
|
|
|
|
//Creates a texture from an ndarray
|
|
function createTextureArray(gl, array) {
|
|
var dtype = array.dtype
|
|
var shape = array.shape.slice()
|
|
var maxSize = gl.getParameter(gl.MAX_TEXTURE_SIZE)
|
|
if(shape[0] < 0 || shape[0] > maxSize || shape[1] < 0 || shape[1] > maxSize) {
|
|
throw new Error('gl-texture2d: Invalid texture size')
|
|
}
|
|
var packed = isPacked(shape, array.stride.slice())
|
|
var type = 0
|
|
if(dtype === 'float32') {
|
|
type = gl.FLOAT
|
|
} else if(dtype === 'float64') {
|
|
type = gl.FLOAT
|
|
packed = false
|
|
dtype = 'float32'
|
|
} else if(dtype === 'uint8') {
|
|
type = gl.UNSIGNED_BYTE
|
|
} else {
|
|
type = gl.UNSIGNED_BYTE
|
|
packed = false
|
|
dtype = 'uint8'
|
|
}
|
|
var format = 0
|
|
if(shape.length === 2) {
|
|
format = gl.LUMINANCE
|
|
shape = [shape[0], shape[1], 1]
|
|
array = ndarray(array.data, shape, [array.stride[0], array.stride[1], 1], array.offset)
|
|
} else if(shape.length === 3) {
|
|
if(shape[2] === 1) {
|
|
format = gl.ALPHA
|
|
} else if(shape[2] === 2) {
|
|
format = gl.LUMINANCE_ALPHA
|
|
} else if(shape[2] === 3) {
|
|
format = gl.RGB
|
|
} else if(shape[2] === 4) {
|
|
format = gl.RGBA
|
|
} else {
|
|
throw new Error('gl-texture2d: Invalid shape for pixel coords')
|
|
}
|
|
} else {
|
|
throw new Error('gl-texture2d: Invalid shape for texture')
|
|
}
|
|
if(type === gl.FLOAT && !gl.getExtension('OES_texture_float')) {
|
|
type = gl.UNSIGNED_BYTE
|
|
packed = false
|
|
}
|
|
var buffer, buf_store
|
|
var size = array.size
|
|
if(!packed) {
|
|
var stride = [shape[2], shape[2]*shape[0], 1]
|
|
buf_store = pool.malloc(size, dtype)
|
|
var buf_array = ndarray(buf_store, shape, stride, 0)
|
|
if((dtype === 'float32' || dtype === 'float64') && type === gl.UNSIGNED_BYTE) {
|
|
convertFloatToUint8(buf_array, array)
|
|
} else {
|
|
ops.assign(buf_array, array)
|
|
}
|
|
buffer = buf_store.subarray(0, size)
|
|
} else if (array.offset === 0 && array.data.length === size) {
|
|
buffer = array.data
|
|
} else {
|
|
buffer = array.data.subarray(array.offset, array.offset + size)
|
|
}
|
|
var tex = initTexture(gl)
|
|
gl.texImage2D(gl.TEXTURE_2D, 0, format, shape[0], shape[1], 0, format, type, buffer)
|
|
if(!packed) {
|
|
pool.free(buf_store)
|
|
}
|
|
return new Texture2D(gl, tex, shape[0], shape[1], format, type)
|
|
}
|
|
|
|
function createTexture2D(gl) {
|
|
if(arguments.length <= 1) {
|
|
throw new Error('gl-texture2d: Missing arguments for texture2d constructor')
|
|
}
|
|
if(!linearTypes) {
|
|
lazyInitLinearTypes(gl)
|
|
}
|
|
if(typeof arguments[1] === 'number') {
|
|
return createTextureShape(gl, arguments[1], arguments[2], arguments[3]||gl.RGBA, arguments[4]||gl.UNSIGNED_BYTE)
|
|
}
|
|
if(Array.isArray(arguments[1])) {
|
|
return createTextureShape(gl, arguments[1][0]|0, arguments[1][1]|0, arguments[2]||gl.RGBA, arguments[3]||gl.UNSIGNED_BYTE)
|
|
}
|
|
if(typeof arguments[1] === 'object') {
|
|
var obj = arguments[1]
|
|
var directData = acceptTextureDOM(obj) ? obj : obj.raw
|
|
if (directData) {
|
|
return createTextureDOM(gl, directData, obj.width|0, obj.height|0, arguments[2]||gl.RGBA, arguments[3]||gl.UNSIGNED_BYTE)
|
|
} else if(obj.shape && obj.data && obj.stride) {
|
|
return createTextureArray(gl, obj)
|
|
}
|
|
}
|
|
throw new Error('gl-texture2d: Invalid arguments for texture2d constructor')
|
|
}
|
|
|