diff --git a/CITATION.cff b/CITATION.cff index 9a67cea95..3a96da754 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -10,7 +10,7 @@ type: software authors: - given-names: Matthew family-names: McCormick - email: matt@mmmccormick.com + email: matt@fideus.io affiliation: 'Kitware, Inc' orcid: 'https://orcid.org/0000-0001-9475-3756' keywords: diff --git a/examples/debugging/package.json b/examples/debugging/package.json index 07399ee2e..9af7dbd91 100644 --- a/examples/debugging/package.json +++ b/examples/debugging/package.json @@ -17,7 +17,7 @@ "start": "http-server -o" }, "type": "module", - "author": "Matt McCormick ", + "author": "Matt McCormick ", "license": "Apache-2.0", "dependencies": { "http-server": "^14.1.0", diff --git a/examples/different-input-types/package.json b/examples/different-input-types/package.json index 88e2161b6..d43e0aa7b 100644 --- a/examples/different-input-types/package.json +++ b/examples/different-input-types/package.json @@ -10,7 +10,7 @@ "test:wasi": "itk-wasm -b wasi-build run different-input-types.wasi.wasm -- ./Gourds.png label.png overlay.png", "test:wasi:help": "itk-wasm -b wasi-build run different-input-types.wasi.wasm -- --help" }, - "author": "Matt McCormick ", + "author": "Matt McCormick ", "license": "Apache-2.0", "dependencies": { "itk-wasm": "workspace:^" diff --git a/examples/hello-pipeline/package.json b/examples/hello-pipeline/package.json index 1233ecc14..ad01745fa 100644 --- a/examples/hello-pipeline/package.json +++ b/examples/hello-pipeline/package.json @@ -9,7 +9,7 @@ "test:quiet": "itk-wasm run wasi-build/hello-pipeline.wasi.wasm -- --quiet cthead1.png", "test:help": "itk-wasm run wasi-build/hello-pipeline.wasi.wasm -- --help" }, - "author": "Matt McCormick ", + "author": "Matt McCormick ", "license": "Apache-2.0", "dependencies": { "fs-extra": "^10.0.0", diff --git a/examples/hello-world/package.json b/examples/hello-world/package.json index 1182b1cfd..067a682d4 100644 --- a/examples/hello-world/package.json +++ b/examples/hello-world/package.json @@ -15,7 +15,7 @@ "test:browser": "playwright test", "test:browser:debug": "playwright test --debug" }, - "author": "Matt McCormick ", + "author": "Matt McCormick ", "license": "Apache-2.0", "dependencies": { "fs-extra": "^11.1.0", diff --git a/examples/inputs-outputs/package.json b/examples/inputs-outputs/package.json index f33924c50..be6099b7b 100644 --- a/examples/inputs-outputs/package.json +++ b/examples/inputs-outputs/package.json @@ -38,7 +38,7 @@ "test:wasi": "itk-wasm -i itkwasm/wasi:latest -b wasi-build run inputs-outputs.wasi.wasm -- -- cthead1.png smoothed.png", "test:wasi:help": "itk-wasm -i itkwasm/wasi:latest -b wasi-build run inputs-outputs.wasi.wasm -- -- --help" }, - "author": "Matt McCormick ", + "author": "Matt McCormick ", "license": "Apache-2.0", "devDependencies": { "@itk-wasm/image-io": "workspace:^", diff --git a/examples/mean-squares-versor-registration/package.json b/examples/mean-squares-versor-registration/package.json index 1ddc69987..232495ec5 100644 --- a/examples/mean-squares-versor-registration/package.json +++ b/examples/mean-squares-versor-registration/package.json @@ -38,7 +38,7 @@ "test:wasi:long": "itk-wasm test -- -- -V", "test:wasi:help": "itk-wasm -i itkwasm/wasi:latest run mean-squares-versor-registration.wasi.wasm -- -- --help" }, - "author": "Matt McCormick ", + "author": "Matt McCormick ", "license": "Apache-2.0", "devDependencies": { "@itk-wasm/image-io": "workspace:^", diff --git a/include/itkPipeline.h b/include/itkPipeline.h index f01af638b..314064c7f 100644 --- a/include/itkPipeline.h +++ b/include/itkPipeline.h @@ -25,6 +25,13 @@ #include "itkMacro.h" #include "itkImage.h" #include "itkVectorImage.h" +#include "itkConfigure.h" +#if defined(ITK_USE_PTHREADS) || defined(ITK_USE_WIN32_THREADS) +#define ITK_WASM_PIPELINE_USE_THREADS 1 +#endif +#ifdef ITK_WASM_PIPELINE_USE_THREADS +#include "itkMultiThreaderBase.h" +#endif #include "glaze/glaze.hpp" @@ -164,6 +171,16 @@ class WebAssemblyInterface_EXPORT Pipeline : public CLI::App return m_UseMemoryIO; } + int + get_threads() const + { +#ifdef ITK_WASM_PIPELINE_USE_THREADS + return m_Threads; +#else + return 1; +#endif + } + int get_argc() const { @@ -198,6 +215,9 @@ class WebAssemblyInterface_EXPORT Pipeline : public CLI::App int m_argc; char ** m_argv; std::string m_Version; +#ifdef ITK_WASM_PIPELINE_USE_THREADS + int m_Threads; +#endif }; diff --git a/packages/compare-images/pixi.toml b/packages/compare-images/pixi.toml index bfebfeda2..4b5f23817 100644 --- a/packages/compare-images/pixi.toml +++ b/packages/compare-images/pixi.toml @@ -1,5 +1,5 @@ [project] -authors = ["Matt McCormick "] +authors = ["Matt McCormick "] channels = ["conda-forge"] description = "Compare images with a tolerance for regression testing." name = "compare-images" diff --git a/packages/compare-meshes/pixi.toml b/packages/compare-meshes/pixi.toml index 353194104..3bb1a62fd 100644 --- a/packages/compare-meshes/pixi.toml +++ b/packages/compare-meshes/pixi.toml @@ -1,5 +1,5 @@ [project] -authors = ["Matt McCormick "] +authors = ["Matt McCormick "] channels = ["conda-forge"] description = "Compare meshes with a tolerance for regression testing." name = "compare-meshes" diff --git a/packages/compress-stringify/pixi.toml b/packages/compress-stringify/pixi.toml index a6382c1f7..c274fa2d5 100644 --- a/packages/compress-stringify/pixi.toml +++ b/packages/compress-stringify/pixi.toml @@ -1,5 +1,5 @@ [project] -authors = ["Matt McCormick "] +authors = ["Matt McCormick "] channels = ["conda-forge"] description = "Zstandard compression and decompression and base64 encoding and decoding in WebAssembly." name = "compress-stringify" diff --git a/packages/core/typescript/itk-wasm/src/cli/find-oci-exe.js b/packages/core/typescript/itk-wasm/src/cli/find-oci-exe.js index 4895fc0bd..0b73bb2ff 100644 --- a/packages/core/typescript/itk-wasm/src/cli/find-oci-exe.js +++ b/packages/core/typescript/itk-wasm/src/cli/find-oci-exe.js @@ -1,46 +1,46 @@ -import fs from 'fs' -import path from 'path' -import os from 'os' - -import chalk from 'chalk' - -import die from './die.js' - -function findOciExe() { - // Check for OCI_EXE environmental variable - const ociExe = process.env.OCI_EXE - if (ociExe && fs.existsSync(ociExe)) { - return ociExe - } - - // Get the PATH environment variable - const PATH = process.env.PATH.split(path.delimiter) - - // Check for podman executable - const podmanExe = os.platform() === 'win32' ? 'podman.exe' : 'podman' - for (let p of PATH) { - if (fs.existsSync(path.join(p, podmanExe))) { - return podmanExe - } - } - - // Check for docker executable - const dockerExe = os.platform() === 'win32' ? 'docker.exe' : 'docker' - for (let p of PATH) { - if (fs.existsSync(path.join(p, dockerExe))) { - return dockerExe - } - } - - // If none of the above exist, die - die(`${chalk.magenta(`Could not find podman or docker executable in the - PATH or OCI_EXE environmental variables.`)} - -${chalk.blue(`Please find installation instructions at: - - https://podman.io/docs/installation`)} - -`) -} - -export default findOciExe +import fs from 'fs' +import path from 'path' +import os from 'os' + +import chalk from 'chalk' + +import die from './die.js' + +function findOciExe() { + // Check for OCI_EXE environmental variable + const ociExe = process.env.OCI_EXE + if (ociExe) { + return ociExe + } + + // Get the PATH environment variable + const PATH = process.env.PATH.split(path.delimiter) + + // Check for podman executable + const podmanExe = os.platform() === 'win32' ? 'podman.exe' : 'podman' + for (let p of PATH) { + if (fs.existsSync(path.join(p, podmanExe))) { + return podmanExe + } + } + + // Check for docker executable + const dockerExe = os.platform() === 'win32' ? 'docker.exe' : 'docker' + for (let p of PATH) { + if (fs.existsSync(path.join(p, dockerExe))) { + return dockerExe + } + } + + // If none of the above exist, die + die(`${chalk.magenta(`Could not find podman or docker executable in the + PATH or OCI_EXE environmental variables.`)} + +${chalk.blue(`Please find installation instructions at: + + https://podman.io/docs/installation`)} + +`) +} + +export default findOciExe diff --git a/packages/core/typescript/itk-wasm/src/pipeline/internal/load-emscripten-module-node.ts b/packages/core/typescript/itk-wasm/src/pipeline/internal/load-emscripten-module-node.ts index 4c7eff837..1e8fe21d8 100644 --- a/packages/core/typescript/itk-wasm/src/pipeline/internal/load-emscripten-module-node.ts +++ b/packages/core/typescript/itk-wasm/src/pipeline/internal/load-emscripten-module-node.ts @@ -2,12 +2,14 @@ import fs from 'fs' import EmscriptenModule from '../itk-wasm-emscripten-module.js' import { pathToFileURL } from 'url' import { ZSTDDecoder } from '@thewtex/zstddec' +import pthreadSupportAvailable from '../pthread-support-available.js' const zstdDecoder = new ZSTDDecoder() await zstdDecoder.init() -async function loadEmscriptenModuleNode ( - modulePath: string +async function loadEmscriptenModuleNode( + modulePath: string, + disableThreads?: boolean ): Promise { let modulePrefix = modulePath if (modulePath.endsWith('.js')) { @@ -19,9 +21,49 @@ async function loadEmscriptenModuleNode ( if (modulePath.endsWith('.wasm.zst')) { modulePrefix = modulePath.substring(0, modulePath.length - 9) } - const compressedWasmBinaryPath = `${modulePrefix}.wasm.zst` - const compressedWasmBinary = fs.readFileSync(compressedWasmBinaryPath) - const wasmBinary = zstdDecoder.decode(new Uint8Array(compressedWasmBinary)) + + // Check for pthread support and use the appropriate WASM file + const hasPthreadSupport = pthreadSupportAvailable() && !disableThreads + let wasmFileName = `${modulePrefix}.wasm.zst` + let wasmBinary: Uint8Array + + if (hasPthreadSupport) { + const threadsWasmPath = `${modulePrefix}.threads.wasm.zst` + if (fs.existsSync(threadsWasmPath)) { + wasmFileName = threadsWasmPath + const compressedWasmBinary = fs.readFileSync(wasmFileName) + wasmBinary = zstdDecoder.decode(new Uint8Array(compressedWasmBinary)) + } else { + // Fall back to checking for compressed non-threaded version + if (fs.existsSync(wasmFileName)) { + const compressedWasmBinary = fs.readFileSync(wasmFileName) + wasmBinary = zstdDecoder.decode(new Uint8Array(compressedWasmBinary)) + } else { + // Fall back to uncompressed WASM file + const uncompressedWasmPath = `${modulePrefix}.wasm` + if (fs.existsSync(uncompressedWasmPath)) { + wasmBinary = fs.readFileSync(uncompressedWasmPath) + } else { + throw new Error(`No WASM file found for module: ${modulePrefix}`) + } + } + } + } else { + // Check for compressed non-threaded version first + if (fs.existsSync(wasmFileName)) { + const compressedWasmBinary = fs.readFileSync(wasmFileName) + wasmBinary = zstdDecoder.decode(new Uint8Array(compressedWasmBinary)) + } else { + // Fall back to uncompressed WASM file + const uncompressedWasmPath = `${modulePrefix}.wasm` + if (fs.existsSync(uncompressedWasmPath)) { + wasmBinary = fs.readFileSync(uncompressedWasmPath) + } else { + throw new Error(`No WASM file found for module: ${modulePrefix}`) + } + } + } + const fullModulePath = pathToFileURL(`${modulePrefix}.js`).href const result = await import( /* webpackIgnore: true */ /* @vite-ignore */ fullModulePath diff --git a/packages/core/typescript/itk-wasm/src/pipeline/run-pipeline-node.ts b/packages/core/typescript/itk-wasm/src/pipeline/run-pipeline-node.ts index 159f385d0..9c4033069 100644 --- a/packages/core/typescript/itk-wasm/src/pipeline/run-pipeline-node.ts +++ b/packages/core/typescript/itk-wasm/src/pipeline/run-pipeline-node.ts @@ -8,7 +8,7 @@ import PipelineOutput from './pipeline-output.js' import PipelineInput from './pipeline-input.js' import RunPipelineResult from './run-pipeline-result.js' -function windowsToEmscriptenPath (filePath: string): string { +function windowsToEmscriptenPath(filePath: string): string { // Following mount logic in itkJSPost.js const fileBasename = path.basename(filePath) const containingDir = path.dirname(filePath) @@ -22,7 +22,7 @@ function windowsToEmscriptenPath (filePath: string): string { return mountedPath } -function replaceArgumentsWithEmscriptenPaths ( +function replaceArgumentsWithEmscriptenPaths( args: string[], mountDirs: Set ): string[] { @@ -39,15 +39,26 @@ function replaceArgumentsWithEmscriptenPaths ( }) } -async function runPipelineNode ( +function shouldDisableThreads(args: string[]): boolean { + for (let i = 0; i < args.length - 1; i++) { + if (args[i] === '--threads' && args[i + 1] === '0') { + return true + } + } + return false +} + +async function runPipelineNode( pipelinePath: string, args: string[], outputs: PipelineOutput[], inputs: PipelineInput[] | null, mountDirs?: Set ): Promise { + const disableThreads = shouldDisableThreads(args) const Module = (await loadEmscriptenModuleNode( - pipelinePath + pipelinePath, + disableThreads )) as PipelineEmscriptenModule const mountedDirs: Set = new Set() const unmountable: Set = new Set() @@ -61,8 +72,7 @@ async function runPipelineNode ( */ Array.from(mountedDirs) .filter((x, _, a) => a.every((y) => x === y || !x.includes(y))) - .forEach((dir) => unmountable.add(dir) - ) + .forEach((dir) => unmountable.add(dir)) } if (typeof mountDirs !== 'undefined') { args = replaceArgumentsWithEmscriptenPaths(args, mountDirs) diff --git a/packages/core/typescript/itk-wasm/test/node/pipeline/run-pipeline-node-test.js b/packages/core/typescript/itk-wasm/test/node/pipeline/run-pipeline-node-test.js index a252d6c6f..af5299aca 100644 --- a/packages/core/typescript/itk-wasm/test/node/pipeline/run-pipeline-node-test.js +++ b/packages/core/typescript/itk-wasm/test/node/pipeline/run-pipeline-node-test.js @@ -10,7 +10,7 @@ import { InterfaceTypes } from '../../../dist/index-node.js' -function readCthead1 () { +function readCthead1() { const testInputImageDir = path.resolve( 'test', 'pipelines', @@ -49,7 +49,7 @@ function readCthead1 () { return image } -function readCow () { +function readCow() { const testInputMeshDir = path.resolve( 'test', 'pipelines', @@ -88,7 +88,7 @@ function readCow () { return mesh } -function readLinearTransform () { +function readLinearTransform() { const testInputTransformDir = path.resolve( 'test', 'pipelines', @@ -126,7 +126,7 @@ function readLinearTransform () { return transformList } -function readCompositeTransform () { +function readCompositeTransform() { const testInputTransformDir = path.resolve( 'test', 'pipelines', @@ -477,7 +477,7 @@ test('runPipelineNode writes and reads an itk.TransformList via memory io', asyn verifyTransform(outputs[0].data) }) -test.only('runPipelineNode writes and reads a CompositeTransform itk.TransformList via memory io', async (t) => { +test('runPipelineNode writes and reads a CompositeTransform itk.TransformList via memory io', async (t) => { const verifyTransform = (transformList) => { t.is( transformList.length, @@ -643,6 +643,66 @@ test.only('runPipelineNode writes and reads a CompositeTransform itk.TransformLi verifyTransform(outputs[0].data) }) +test('runPipelineNode runs pthreads-enabled pipeline', async (t) => { + const pipelinePath = path.resolve( + 'test', + 'pipelines', + 'emscripten-threads-build', + 'pthreads-pipeline', + 'pthreads-test' + ) + const args = ['--memory-io', '--number-of-threads', '4', '0'] + const desiredOutputs = [{ type: InterfaceTypes.JsonCompatible }] + const inputs = [] + + const { outputs } = await runPipelineNode( + pipelinePath, + args, + desiredOutputs, + inputs + ) + + const result = outputs[0].data + t.is(typeof result, 'object', 'output should be an object') + t.is( + typeof result.createdThreads, + 'number', + 'createdThreads should be a number' + ) + t.true(result.createdThreads >= 0, 'should have created at least 0 threads') + t.true( + result.createdThreads <= 4, + 'should not have created more than 4 threads' + ) +}) + +test('runPipelineNode with --threads 0 disables threading', async (t) => { + const image = readCthead1() + const pipelinePath = path.resolve( + 'test', + 'pipelines', + 'emscripten-build', + 'median-filter-pipeline', + 'median-filter-test' + ) + // Include --threads 0 to test that threading is disabled at the loading level + const args = ['0', '0', '--radius', '4', '--memory-io', '--threads', '0'] + const desiredOutputs = [{ type: InterfaceTypes.Image }] + const inputs = [{ type: InterfaceTypes.Image, data: image }] + + const { outputs } = await runPipelineNode( + pipelinePath, + args, + desiredOutputs, + inputs + ) + + // The test should pass successfully, indicating that --threads 0 was handled + // and the non-threaded WASM was loaded instead of the threaded version + t.is(outputs.length, 1, 'should have one output') + t.is(typeof outputs[0].data, 'object', 'output should be an object') +}) + test('runPipelineNode creates a composite transform with expected parameters', async (t) => { const verifyCompositeTransform = (transformList) => { t.is( diff --git a/packages/core/typescript/itk-wasm/test/pipelines/CMakeLists.txt b/packages/core/typescript/itk-wasm/test/pipelines/CMakeLists.txt index fce328ba4..34971d18d 100644 --- a/packages/core/typescript/itk-wasm/test/pipelines/CMakeLists.txt +++ b/packages/core/typescript/itk-wasm/test/pipelines/CMakeLists.txt @@ -10,7 +10,5 @@ add_subdirectory("mesh-read-write-pipeline") add_subdirectory("transform-read-write-pipeline") add_subdirectory("read-image") add_subdirectory("stdout-stderr-pipeline") -if (NOT EMSCRIPTEN) - add_subdirectory("pthreads-pipeline") - add_subdirectory("cxx-threads-pipeline") -endif() +add_subdirectory("pthreads-pipeline") +add_subdirectory("cxx-threads-pipeline") diff --git a/packages/dicom/pixi.toml b/packages/dicom/pixi.toml index aeb3a3c03..863c9f112 100644 --- a/packages/dicom/pixi.toml +++ b/packages/dicom/pixi.toml @@ -1,5 +1,5 @@ [project] -authors = ["Matt McCormick "] +authors = ["Matt McCormick "] channels = ["conda-forge"] description = "Read and write files and images related to DICOM file format." name = "dicom" diff --git a/packages/downsample/pixi.toml b/packages/downsample/pixi.toml index f2411d30c..4604d8d7b 100644 --- a/packages/downsample/pixi.toml +++ b/packages/downsample/pixi.toml @@ -1,5 +1,5 @@ [project] -authors = ["Matt McCormick "] +authors = ["Matt McCormick "] channels = ["conda-forge"] description = "Pipelines for downsampling images." name = "downsample" diff --git a/packages/image-io/pixi.toml b/packages/image-io/pixi.toml index 94d29de23..d9ee4311d 100644 --- a/packages/image-io/pixi.toml +++ b/packages/image-io/pixi.toml @@ -1,5 +1,5 @@ [project] -authors = ["Matt McCormick "] +authors = ["Matt McCormick "] channels = ["conda-forge"] description = "Input and output for scientific and medical image file formats." name = "image-io" diff --git a/packages/mesh-filters/pixi.toml b/packages/mesh-filters/pixi.toml index ad2fb494a..8b1169594 100644 --- a/packages/mesh-filters/pixi.toml +++ b/packages/mesh-filters/pixi.toml @@ -1,5 +1,5 @@ [project] -authors = ["Matt McCormick "] +authors = ["Matt McCormick "] channels = ["conda-forge"] description = "Mesh filters to repair, remesh, subdivide, decimate, smooth, triangulate, etc." name = "mesh-filters" diff --git a/packages/mesh-io/pixi.toml b/packages/mesh-io/pixi.toml index 37df2650f..8b8517b0b 100644 --- a/packages/mesh-io/pixi.toml +++ b/packages/mesh-io/pixi.toml @@ -1,5 +1,5 @@ [project] -authors = ["Matt McCormick "] +authors = ["Matt McCormick "] channels = ["conda-forge"] description = "Input and output for scientific and medical mesh file formats." name = "mesh-io" diff --git a/packages/transform-io/pixi.toml b/packages/transform-io/pixi.toml index e6f8f6f6f..bb7bfc32e 100644 --- a/packages/transform-io/pixi.toml +++ b/packages/transform-io/pixi.toml @@ -1,5 +1,5 @@ [project] -authors = ["Matt McCormick "] +authors = ["Matt McCormick "] channels = ["conda-forge"] description = "Input and output for scientific and medical transform file formats." name = "transform-io" diff --git a/packages/transform/pixi.toml b/packages/transform/pixi.toml index 777576ecf..489a53c4f 100644 --- a/packages/transform/pixi.toml +++ b/packages/transform/pixi.toml @@ -1,5 +1,5 @@ [project] -authors = ["Matt McCormick "] +authors = ["Matt McCormick "] channels = ["conda-forge"] description = "Pipelines for downsampling images." name = "transform" diff --git a/pixi.toml b/pixi.toml index e653674cf..6e9b16526 100644 --- a/pixi.toml +++ b/pixi.toml @@ -1,5 +1,5 @@ [project] -authors = ["Matt McCormick "] +authors = ["Matt McCormick "] channels = ["conda-forge"] description = "ITK-Wasm native build and test configuration." name = "ITK-Wasm" diff --git a/pyproject.toml b/pyproject.toml index f5579a9ac..3cb9bfdb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ description = "IO with the itk-wasm file formats" readme = "docs/itk-webassemblyinterface-description.md" license = {file = "LICENSE"} authors = [ - { name = "Matthew M. McCormick", email = "matt@mmmccormick.com" }, + { name = "Matthew M. McCormick", email = "matt@fideus.io" }, ] keywords = [ "itk", diff --git a/src/docker/itk-wasm-base/Dockerfile b/src/docker/itk-wasm-base/Dockerfile index c212c7592..3cb9f2fa0 100644 --- a/src/docker/itk-wasm-base/Dockerfile +++ b/src/docker/itk-wasm-base/Dockerfile @@ -3,7 +3,7 @@ ARG HOST_ARCH=amd64 FROM $BASE_IMAGE:20250123-74d1a0b-$HOST_ARCH ARG BASE_IMAGE -LABEL maintainer="Matt McCormick matt@mmmccormick.com" +LABEL maintainer="Matt McCormick matt@fideus.io" LABEL org.opencontainers.image.source="https://github.com/InsightSoftwareConsortium/ITK-Wasm" WORKDIR / diff --git a/src/docker/itk-wasm/Dockerfile b/src/docker/itk-wasm/Dockerfile index 68cc2d3d5..c6687e5cf 100644 --- a/src/docker/itk-wasm/Dockerfile +++ b/src/docker/itk-wasm/Dockerfile @@ -3,7 +3,7 @@ ARG BASE_IMAGE=quay.io/itkwasm/emscripten-base ARG BASE_TAG=latest-$HOST_ARCH FROM $BASE_IMAGE:$BASE_TAG -LABEL maintainer="Matt McCormick matt@mmmccormick.com" +LABEL maintainer="Matt McCormick matt@fideus.io" ARG BASE_IMAGE ARG HOST_ARCH=amd64 ARG BASE_IMAGE=quay.io/itkwasm/emscripten-base diff --git a/src/itkPipeline.cxx b/src/itkPipeline.cxx index 2af08d48f..383554cc3 100644 --- a/src/itkPipeline.cxx +++ b/src/itkPipeline.cxx @@ -31,6 +31,9 @@ Pipeline ::Pipeline(std::string name, std::string description, int argc, char ** , m_argc(argc) , m_argv(argv) , m_Version("0.1.0") +#ifdef ITK_WASM_PIPELINE_USE_THREADS + , m_Threads(8) +#endif { this->footer("Enjoy ITK!"); @@ -39,9 +42,16 @@ Pipeline ::Pipeline(std::string name, std::string description, int argc, char ** this->add_flag("--memory-io", m_UseMemoryIO, "Use itk-wasm memory IO")->group(""); this->set_version_flag("--version", m_Version); +#ifdef ITK_WASM_PIPELINE_USE_THREADS + this->add_option("--threads", m_Threads, "Number of threads to use for processing")->group(""); +#endif + // Set m_UseMemoryIO before it is used by other memory parsers this->preparse_callback([this](size_t arg) { m_UseMemoryIO = false; +#ifdef ITK_WASM_PIPELINE_USE_THREADS + m_Threads = 8; // Reset to default +#endif for (int ii = 0; ii < this->m_argc; ++ii) { const std::string arg(this->m_argv[ii]); @@ -49,7 +59,21 @@ Pipeline ::Pipeline(std::string name, std::string description, int argc, char ** { m_UseMemoryIO = true; } +#ifdef ITK_WASM_PIPELINE_USE_THREADS + if (arg == "--threads" && ii + 1 < this->m_argc) + { + try { + m_Threads = std::stoi(this->m_argv[ii + 1]); + } catch (...) { + // Keep default value if parsing fails + } + } +#endif } +#ifdef ITK_WASM_PIPELINE_USE_THREADS + // Set ITK's global number of threads + itk::MultiThreaderBase::SetGlobalDefaultNumberOfThreads(m_Threads); +#endif }); #ifndef ITK_WASM_NO_FILESYSTEM_IO