itk-wasm
Version:
High-performance spatial analysis in a web browser, Node.js, and reproducible execution across programming languages and hardware architectures.
805 lines (772 loc) • 26.3 kB
text/typescript
import InterfaceTypes from '../../interface-types/interface-types.js'
import bufferToTypedArray from '../../buffer-to-typed-array.js'
import TypedArray from '../../typed-array.js'
import TextStream from '../../interface-types/text-stream.js'
import BinaryStream from '../../interface-types/binary-stream.js'
import TextFile from '../../interface-types/text-file.js'
import BinaryFile from '../../interface-types/binary-file.js'
import Image from '../../interface-types/image.js'
import Mesh from '../../interface-types/mesh.js'
import PointSet from '../../interface-types/point-set.js'
import PolyData from '../../interface-types/poly-data.js'
import TransformList from '../../interface-types/transform-list.js'
import FloatTypes from '../../interface-types/float-types.js'
import IntTypes from '../../interface-types/int-types.js'
import PipelineEmscriptenModule from '../pipeline-emscripten-module.js'
import PipelineInput from '../pipeline-input.js'
import PipelineOutput from '../pipeline-output.js'
import RunPipelineResult from '../run-pipeline-result.js'
const haveSharedArrayBuffer = typeof globalThis.SharedArrayBuffer === 'function'
const encoder = new TextEncoder()
const decoder = new TextDecoder('utf-8')
function readFileSharedArray (
emscriptenModule: PipelineEmscriptenModule,
path: string
): Uint8Array {
const opts = { flags: 'r', encoding: 'binary' }
const stream = emscriptenModule.fs_open(path, opts.flags)
const stat = emscriptenModule.fs_stat(path)
const length = stat.size
let arrayBufferData = null
if (haveSharedArrayBuffer) {
arrayBufferData = new SharedArrayBuffer(length) // eslint-disable-line
} else {
arrayBufferData = new ArrayBuffer(length)
}
const array = new Uint8Array(arrayBufferData)
emscriptenModule.fs_read(stream, array, 0, length, 0)
emscriptenModule.fs_close(stream)
return array
}
function memoryUint8SharedArray (
emscriptenModule: PipelineEmscriptenModule,
byteOffset: number,
length: number
): Uint8Array {
let arrayBufferData = null
if (haveSharedArrayBuffer) {
arrayBufferData = new SharedArrayBuffer(length) // eslint-disable-line
} else {
arrayBufferData = new ArrayBuffer(length)
}
const array = new Uint8Array(arrayBufferData)
const dataArrayView = new Uint8Array(
emscriptenModule.HEAPU8.buffer,
byteOffset,
length
)
array.set(dataArrayView)
return array
}
function setPipelineModuleInputArray (
emscriptenModule: PipelineEmscriptenModule,
dataArray: TypedArray | null,
inputIndex: number,
subIndex: number
): number {
let dataPtr = 0
if (dataArray !== null) {
dataPtr = emscriptenModule.ccall(
'itk_wasm_input_array_alloc',
'number',
['number', 'number', 'number', 'number'],
[0, inputIndex, subIndex, dataArray.buffer.byteLength]
) >>> 0
emscriptenModule.HEAPU8.set(new Uint8Array(dataArray.buffer), dataPtr)
}
return dataPtr
}
function setPipelineModuleInputJSON (
emscriptenModule: PipelineEmscriptenModule,
dataObject: object,
inputIndex: number
): void {
const dataJSON = JSON.stringify(dataObject)
const length = emscriptenModule.lengthBytesUTF8(dataJSON) + 1
const jsonPtr = emscriptenModule.ccall(
'itk_wasm_input_json_alloc',
'number',
['number', 'number', 'number'],
[0, inputIndex, length]
) >>> 0
emscriptenModule.stringToUTF8(dataJSON, jsonPtr, length)
}
function getPipelineModuleOutputArray (
emscriptenModule: PipelineEmscriptenModule,
outputIndex: number,
subIndex: number,
componentType:
| (typeof IntTypes)[keyof typeof IntTypes]
| (typeof FloatTypes)[keyof typeof FloatTypes]
): TypedArray | Float32Array | Uint32Array | null {
const dataPtr = emscriptenModule.ccall(
'itk_wasm_output_array_address',
'number',
['number', 'number', 'number'],
[0, outputIndex, subIndex]
) >>> 0
const dataSize = emscriptenModule.ccall(
'itk_wasm_output_array_size',
'number',
['number', 'number', 'number'],
[0, outputIndex, subIndex]
)
const dataUint8 = memoryUint8SharedArray(emscriptenModule, dataPtr, dataSize)
const data = bufferToTypedArray(componentType, dataUint8.buffer)
return data
}
function getPipelineModuleOutputJSON (
emscriptenModule: PipelineEmscriptenModule,
outputIndex: number
): object {
const jsonPtr = emscriptenModule.ccall(
'itk_wasm_output_json_address',
'number',
['number', 'number'],
[0, outputIndex]
) >>> 0
const dataJSON = emscriptenModule.UTF8ToString(jsonPtr)
const dataObject = JSON.parse(dataJSON)
return dataObject
}
function runPipelineEmscripten (
pipelineModule: PipelineEmscriptenModule,
args: string[],
outputs: PipelineOutput[] | null,
inputs: PipelineInput[] | null
): RunPipelineResult {
if (!(inputs == null) && inputs.length > 0) {
inputs.forEach(function (input, index) {
switch (input.type) {
case InterfaceTypes.TextStream: {
const dataArray = encoder.encode((input.data as TextStream).data)
const arrayPtr = setPipelineModuleInputArray(
pipelineModule,
dataArray,
index,
0
)
const dataJSON = {
size: dataArray.buffer.byteLength,
data: `data:application/vnd.itk.address,0:${arrayPtr}`
}
setPipelineModuleInputJSON(pipelineModule, dataJSON, index)
break
}
case InterfaceTypes.JsonCompatible: {
const dataArray = encoder.encode(JSON.stringify(input.data))
const arrayPtr = setPipelineModuleInputArray(
pipelineModule,
dataArray,
index,
0
)
const dataJSON = {
size: dataArray.buffer.byteLength,
data: `data:application/vnd.itk.address,0:${arrayPtr}`
}
setPipelineModuleInputJSON(pipelineModule, dataJSON, index)
break
}
case InterfaceTypes.BinaryStream: {
const dataArray = (input.data as BinaryStream).data
const arrayPtr = setPipelineModuleInputArray(
pipelineModule,
dataArray,
index,
0
)
const dataJSON = {
size: dataArray.buffer.byteLength,
data: `data:application/vnd.itk.address,0:${arrayPtr}`
}
setPipelineModuleInputJSON(pipelineModule, dataJSON, index)
break
}
case InterfaceTypes.TextFile: {
pipelineModule.fs_writeFile(
(input.data as TextFile).path,
(input.data as TextFile).data
)
break
}
case InterfaceTypes.BinaryFile: {
pipelineModule.fs_writeFile(
(input.data as BinaryFile).path,
(input.data as BinaryFile).data
)
break
}
case InterfaceTypes.Image: {
const image = input.data as Image
const dataPtr = setPipelineModuleInputArray(
pipelineModule,
image.data,
index,
0
)
const directionPtr = setPipelineModuleInputArray(
pipelineModule,
image.direction,
index,
1
)
const metadata =
typeof image.metadata?.entries !== 'undefined'
? Array.from(image.metadata.entries())
: []
const imageJSON = {
imageType: image.imageType,
name: image.name,
origin: image.origin,
spacing: image.spacing,
direction: `data:application/vnd.itk.address,0:${directionPtr}`,
size: image.size,
data: `data:application/vnd.itk.address,0:${dataPtr}`,
metadata
}
setPipelineModuleInputJSON(pipelineModule, imageJSON, index)
break
}
case InterfaceTypes.Mesh: {
const mesh = input.data as Mesh
const pointsPtr = setPipelineModuleInputArray(
pipelineModule,
mesh.points,
index,
0
)
const cellsPtr = setPipelineModuleInputArray(
pipelineModule,
mesh.cells,
index,
1
)
const pointDataPtr = setPipelineModuleInputArray(
pipelineModule,
mesh.pointData,
index,
2
)
const cellDataPtr = setPipelineModuleInputArray(
pipelineModule,
mesh.cellData,
index,
3
)
const meshJSON = {
meshType: mesh.meshType,
name: mesh.name,
numberOfPoints: mesh.numberOfPoints,
points: `data:application/vnd.itk.address,0:${pointsPtr}`,
numberOfCells: mesh.numberOfCells,
cells: `data:application/vnd.itk.address,0:${cellsPtr}`,
cellBufferSize: mesh.cellBufferSize,
numberOfPointPixels: mesh.numberOfPointPixels,
pointData: `data:application/vnd.itk.address,0:${pointDataPtr}`,
numberOfCellPixels: mesh.numberOfCellPixels,
cellData: `data:application/vnd.itk.address,0:${cellDataPtr}`
}
setPipelineModuleInputJSON(pipelineModule, meshJSON, index)
break
}
case InterfaceTypes.PointSet: {
const pointSet = input.data as PointSet
const pointsPtr = setPipelineModuleInputArray(
pipelineModule,
pointSet.points,
index,
0
)
const pointDataPtr = setPipelineModuleInputArray(
pipelineModule,
pointSet.pointData,
index,
1
)
const pointSetJSON = {
pointSetType: pointSet.pointSetType,
name: pointSet.name,
numberOfPoints: pointSet.numberOfPoints,
points: `data:application/vnd.itk.address,0:${pointsPtr}`,
numberOfPointPixels: pointSet.numberOfPointPixels,
pointData: `data:application/vnd.itk.address,0:${pointDataPtr}`
}
setPipelineModuleInputJSON(pipelineModule, pointSetJSON, index)
break
}
case InterfaceTypes.TransformList: {
const transformList = input.data as TransformList
const transformListJSON: any = []
let inputArrayIndex = 0
transformList.forEach((transform) => {
let fixedParameters = ''
let parameters = ''
// Skip setting input arrays for Composite transforms as they don't have array data
if (
transform.transformType.transformParameterization !== 'Composite'
) {
const fixedParameterPtr = setPipelineModuleInputArray(
pipelineModule,
transform.fixedParameters,
index,
inputArrayIndex
)
fixedParameters = `data:application/vnd.itk.address,0:${fixedParameterPtr}`
inputArrayIndex += 1
const parameterPtr = setPipelineModuleInputArray(
pipelineModule,
transform.parameters,
index,
inputArrayIndex
)
parameters = `data:application/vnd.itk.address,0:${parameterPtr}`
inputArrayIndex += 1
}
const transformJSON = {
transformType: transform.transformType,
numberOfFixedParameters: transform.numberOfFixedParameters,
numberOfParameters: transform.numberOfParameters,
name: transform.name,
inputSpaceName: transform.inputSpaceName,
outputSpaceName: transform.outputSpaceName,
parameters,
fixedParameters
}
transformListJSON.push(transformJSON)
})
setPipelineModuleInputJSON(pipelineModule, transformListJSON, index)
break
}
case InterfaceTypes.PolyData: {
const polyData = input.data as PolyData
const pointsPtr = setPipelineModuleInputArray(
pipelineModule,
polyData.points,
index,
0
)
const verticesPtr = setPipelineModuleInputArray(
pipelineModule,
polyData.vertices,
index,
1
)
const linesPtr = setPipelineModuleInputArray(
pipelineModule,
polyData.lines,
index,
2
)
const polygonsPtr = setPipelineModuleInputArray(
pipelineModule,
polyData.polygons,
index,
3
)
const triangleStripsPtr = setPipelineModuleInputArray(
pipelineModule,
polyData.triangleStrips,
index,
4
)
const pointDataPtr = setPipelineModuleInputArray(
pipelineModule,
polyData.pointData,
index,
5
)
const cellDataPtr = setPipelineModuleInputArray(
pipelineModule,
polyData.pointData,
index,
6
)
const polyDataJSON = {
polyDataType: polyData.polyDataType,
name: polyData.name,
numberOfPoints: polyData.numberOfPoints,
points: `data:application/vnd.itk.address,0:${pointsPtr}`,
verticesBufferSize: polyData.verticesBufferSize,
vertices: `data:application/vnd.itk.address,0:${verticesPtr}`,
linesBufferSize: polyData.linesBufferSize,
lines: `data:application/vnd.itk.address,0:${linesPtr}`,
polygonsBufferSize: polyData.polygonsBufferSize,
polygons: `data:application/vnd.itk.address,0:${polygonsPtr}`,
triangleStripsBufferSize: polyData.triangleStripsBufferSize,
triangleStrips: `data:application/vnd.itk.address,0:${triangleStripsPtr}`,
numberOfPointPixels: polyData.numberOfPointPixels,
pointData: `data:application/vnd.itk.address,0:${pointDataPtr}`,
numberOfCellPixels: polyData.numberOfCellPixels,
cellData: `data:application/vnd.itk.address,0:${cellDataPtr}`
}
setPipelineModuleInputJSON(pipelineModule, polyDataJSON, index)
break
}
default:
throw Error('Unsupported input InterfaceType')
}
})
}
pipelineModule.resetModuleStdout()
pipelineModule.resetModuleStderr()
const stackPtr = pipelineModule.stackSave()
let returnValue = 0
try {
returnValue = pipelineModule.callMain(args.slice())
} catch (exception) {
// Note: Module must be built with CMAKE_BUILD_TYPE set to Debug.
// e.g.: itk-wasm build my/project -- -DCMAKE_BUILD_TYPE:STRING=Debug
if (typeof exception === 'number') {
console.log('Exception while running pipeline:')
console.log('stdout:', pipelineModule.getModuleStdout())
console.error('stderr:', pipelineModule.getModuleStderr())
if (typeof pipelineModule.getExceptionMessage !== 'undefined') {
console.error(
'exception:',
pipelineModule.getExceptionMessage(exception)
)
} else {
console.error(
'Build module in Debug mode for exception message information.'
)
}
}
throw exception
} finally {
pipelineModule.stackRestore(stackPtr)
}
const stdout = pipelineModule.getModuleStdout()
const stderr = pipelineModule.getModuleStderr()
const populatedOutputs: PipelineOutput[] = []
if (!(outputs == null) && outputs.length > 0 && returnValue === 0) {
outputs.forEach(function (output, index) {
let outputData: any = null
switch (output.type) {
case InterfaceTypes.TextStream: {
const dataPtr = pipelineModule.ccall(
'itk_wasm_output_array_address',
'number',
['number', 'number', 'number'],
[0, index, 0]
) >>> 0
const dataSize = pipelineModule.ccall(
'itk_wasm_output_array_size',
'number',
['number', 'number', 'number'],
[0, index, 0]
)
const dataArrayView = new Uint8Array(
pipelineModule.HEAPU8.buffer,
dataPtr,
dataSize
)
outputData = { data: decoder.decode(dataArrayView) }
break
}
case InterfaceTypes.JsonCompatible: {
const dataPtr = pipelineModule.ccall(
'itk_wasm_output_array_address',
'number',
['number', 'number', 'number'],
[0, index, 0]
) >>> 0
const dataSize = pipelineModule.ccall(
'itk_wasm_output_array_size',
'number',
['number', 'number', 'number'],
[0, index, 0]
)
const dataArrayView = new Uint8Array(
pipelineModule.HEAPU8.buffer,
dataPtr,
dataSize
)
outputData = JSON.parse(decoder.decode(dataArrayView))
break
}
case InterfaceTypes.BinaryStream: {
const dataPtr = pipelineModule.ccall(
'itk_wasm_output_array_address',
'number',
['number', 'number', 'number'],
[0, index, 0]
) >>> 0
const dataSize = pipelineModule.ccall(
'itk_wasm_output_array_size',
'number',
['number', 'number', 'number'],
[0, index, 0]
)
outputData = {
data: memoryUint8SharedArray(pipelineModule, dataPtr, dataSize)
}
break
}
case InterfaceTypes.TextFile: {
outputData = {
path: (output.data as TextFile).path,
data: pipelineModule.fs_readFile((output.data as TextFile).path, {
encoding: 'utf8'
}) as string
}
break
}
case InterfaceTypes.BinaryFile: {
outputData = {
path: (output.data as BinaryFile).path,
data: readFileSharedArray(
pipelineModule,
(output.data as BinaryFile).path
)
}
break
}
case InterfaceTypes.Image: {
const image = getPipelineModuleOutputJSON(
pipelineModule,
index
) as Image
image.data = getPipelineModuleOutputArray(
pipelineModule,
index,
0,
image.imageType.componentType
)
image.direction = getPipelineModuleOutputArray(
pipelineModule,
index,
1,
FloatTypes.Float64
) as Float64Array
image.metadata = new Map(image.metadata)
outputData = image
break
}
case InterfaceTypes.Mesh: {
const mesh = getPipelineModuleOutputJSON(
pipelineModule,
index
) as Mesh
if (mesh.numberOfPoints > 0) {
mesh.points = getPipelineModuleOutputArray(
pipelineModule,
index,
0,
mesh.meshType.pointComponentType
)
} else {
mesh.points = bufferToTypedArray(
mesh.meshType.pointComponentType,
new ArrayBuffer(0)
)
}
if (mesh.numberOfCells > 0) {
mesh.cells = getPipelineModuleOutputArray(
pipelineModule,
index,
1,
mesh.meshType.cellComponentType
)
} else {
mesh.cells = bufferToTypedArray(
mesh.meshType.cellComponentType,
new ArrayBuffer(0)
)
}
if (mesh.numberOfPointPixels > 0) {
mesh.pointData = getPipelineModuleOutputArray(
pipelineModule,
index,
2,
mesh.meshType.pointPixelComponentType
)
} else {
mesh.pointData = bufferToTypedArray(
mesh.meshType.pointPixelComponentType,
new ArrayBuffer(0)
)
}
if (mesh.numberOfCellPixels > 0) {
mesh.cellData = getPipelineModuleOutputArray(
pipelineModule,
index,
3,
mesh.meshType.cellPixelComponentType
)
} else {
mesh.cellData = bufferToTypedArray(
mesh.meshType.cellPixelComponentType,
new ArrayBuffer(0)
)
}
outputData = mesh
break
}
case InterfaceTypes.PointSet: {
const pointSet = getPipelineModuleOutputJSON(
pipelineModule,
index
) as PointSet
if (pointSet.numberOfPoints > 0) {
pointSet.points = getPipelineModuleOutputArray(
pipelineModule,
index,
0,
pointSet.pointSetType.pointComponentType
)
} else {
pointSet.points = bufferToTypedArray(
pointSet.pointSetType.pointComponentType,
new ArrayBuffer(0)
)
}
if (pointSet.numberOfPointPixels > 0) {
pointSet.pointData = getPipelineModuleOutputArray(
pipelineModule,
index,
1,
pointSet.pointSetType.pointPixelComponentType
)
} else {
pointSet.pointData = bufferToTypedArray(
pointSet.pointSetType.pointPixelComponentType,
new ArrayBuffer(0)
)
}
outputData = pointSet
break
}
case InterfaceTypes.TransformList: {
const transformList = getPipelineModuleOutputJSON(
pipelineModule,
index
) as TransformList
let outputArrayIndex = 0
transformList.forEach((transform, transformIndex) => {
if (
transform.transformType.transformParameterization === 'Composite'
) {
return
}
if (transform.numberOfFixedParameters > 0) {
transformList[transformIndex].fixedParameters =
getPipelineModuleOutputArray(
pipelineModule,
index,
outputArrayIndex,
FloatTypes.Float64
) as TypedArray
}
outputArrayIndex += 1
if (transform.numberOfParameters > 0) {
transformList[transformIndex].parameters =
getPipelineModuleOutputArray(
pipelineModule,
index,
outputArrayIndex,
transform.transformType.parametersValueType
) as TypedArray
}
outputArrayIndex += 1
})
outputData = transformList
break
}
case InterfaceTypes.PolyData: {
const polyData = getPipelineModuleOutputJSON(
pipelineModule,
index
) as PolyData
if (polyData.numberOfPoints > 0) {
polyData.points = getPipelineModuleOutputArray(
pipelineModule,
index,
0,
FloatTypes.Float32
) as Float32Array
} else {
polyData.points = new Float32Array()
}
if (polyData.verticesBufferSize > 0) {
polyData.vertices = getPipelineModuleOutputArray(
pipelineModule,
index,
1,
IntTypes.UInt32
) as Uint32Array
} else {
polyData.vertices = new Uint32Array()
}
if (polyData.linesBufferSize > 0) {
polyData.lines = getPipelineModuleOutputArray(
pipelineModule,
index,
2,
IntTypes.UInt32
) as Uint32Array
} else {
polyData.lines = new Uint32Array()
}
if (polyData.polygonsBufferSize > 0) {
polyData.polygons = getPipelineModuleOutputArray(
pipelineModule,
index,
3,
IntTypes.UInt32
) as Uint32Array
} else {
polyData.polygons = new Uint32Array()
}
if (polyData.triangleStripsBufferSize > 0) {
polyData.triangleStrips = getPipelineModuleOutputArray(
pipelineModule,
index,
4,
IntTypes.UInt32
) as Uint32Array
} else {
polyData.triangleStrips = new Uint32Array()
}
if (polyData.numberOfPointPixels > 0) {
polyData.pointData = getPipelineModuleOutputArray(
pipelineModule,
index,
5,
polyData.polyDataType.pointPixelComponentType
)
} else {
polyData.pointData = bufferToTypedArray(
polyData.polyDataType.pointPixelComponentType,
new ArrayBuffer(0)
)
}
if (polyData.numberOfCellPixels > 0) {
polyData.cellData = getPipelineModuleOutputArray(
pipelineModule,
index,
6,
polyData.polyDataType.cellPixelComponentType
)
} else {
polyData.cellData = bufferToTypedArray(
polyData.polyDataType.cellPixelComponentType,
new ArrayBuffer(0)
)
}
outputData = polyData
break
}
default:
throw Error('Unsupported output InterfaceType')
}
const populatedOutput = {
type: output.type,
data: outputData
}
populatedOutputs.push(populatedOutput)
})
}
return { returnValue, stdout, stderr, outputs: populatedOutputs }
}
export default runPipelineEmscripten