Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ from itkwasm import (
? ` if ${snake}:\n`
: ` if ${snake} is not None:\n`
if (parameter.type.startsWith('TEXT:{')) {
const choices = parameter.type.split('{')[1].split('}')[0].split(', ')
const choices = parameter.type.split('{')[1].split('}')[0].split(',')
args += ` if ${snake} not in (${choices.map((c) => `'${c}'`).join(',')}):\n`
args += ` raise ValueError(f'${snake} must be one of ${choices.join(', ')}')\n`
}
Expand Down
68 changes: 68 additions & 0 deletions packages/downsample/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ else()
set(io_components
ITKImageIO
ITKTransformIO
ITKIOTransformInsightLegacy
ITKIOTransformHDF5
)
endif()

Expand All @@ -22,6 +24,7 @@ find_package(ITK REQUIRED
WebAssemblyInterface
ITKSmoothing
ITKImageGrid
ITKImageFunction
ITKTransform
GenericLabelInterpolator
${io_components}
Expand All @@ -35,6 +38,7 @@ set(pipelines
downsample-bin-shrink
downsample-label-image
resample-bounding-box
resample-to-reference
)

# resample-bounding-box-generate-inputs fabricates the self-contained inputs (fixed image, moving image,
Expand Down Expand Up @@ -163,3 +167,67 @@ if(NOT EMSCRIPTEN)
COMMAND resample-bounding-box-test
)
endif()

# Interpolator coverage: one self-contained test per interpolator, using
# cthead1.png as both the moving image and the reference geometry (identity
# transform), so every interpolator selection is exercised. Needs no new data.
# The linear case doubles as the pipeline smoke test.
add_test(NAME resample-to-reference-linear
COMMAND resample-to-reference
${CMAKE_CURRENT_SOURCE_DIR}/test/data/input/cthead1.png
${CMAKE_CURRENT_SOURCE_DIR}/test/data/input/cthead1.png
${CMAKE_CURRENT_BINARY_DIR}/cthead1_resampled_linear.png
--interpolator linear
)

add_test(NAME resample-to-reference-nearest-neighbor
COMMAND resample-to-reference
${CMAKE_CURRENT_SOURCE_DIR}/test/data/input/cthead1.png
${CMAKE_CURRENT_SOURCE_DIR}/test/data/input/cthead1.png
${CMAKE_CURRENT_BINARY_DIR}/cthead1_resampled_nearest_neighbor.png
--interpolator nearest_neighbor
)

add_test(NAME resample-to-reference-b-spline
COMMAND resample-to-reference
${CMAKE_CURRENT_SOURCE_DIR}/test/data/input/cthead1.png
${CMAKE_CURRENT_SOURCE_DIR}/test/data/input/cthead1.png
${CMAKE_CURRENT_BINARY_DIR}/cthead1_resampled_b_spline.png
--interpolator b_spline
)

add_test(NAME resample-to-reference-windowed-sinc
COMMAND resample-to-reference
${CMAKE_CURRENT_SOURCE_DIR}/test/data/input/cthead1.png
${CMAKE_CURRENT_SOURCE_DIR}/test/data/input/cthead1.png
${CMAKE_CURRENT_BINARY_DIR}/cthead1_resampled_windowed_sinc.png
--interpolator windowed_sinc
)

add_test(NAME resample-to-reference-gaussian
COMMAND resample-to-reference
${CMAKE_CURRENT_SOURCE_DIR}/test/data/input/cthead1.png
${CMAKE_CURRENT_SOURCE_DIR}/test/data/input/cthead1.png
${CMAKE_CURRENT_BINARY_DIR}/cthead1_resampled_gaussian.png
--interpolator gaussian
)

# Label-image interpolator on the existing multi-label test image.
add_test(NAME resample-to-reference-label-image
COMMAND resample-to-reference
${CMAKE_CURRENT_SOURCE_DIR}/test/data/input/2th_cthead1.png
${CMAKE_CURRENT_SOURCE_DIR}/test/data/input/2th_cthead1.png
${CMAKE_CURRENT_BINARY_DIR}/2th_cthead1_resampled_label_image.png
--interpolator label_image
)

# Background value: exercise the --default-value flag on the filesystem/CLI path (the memory-IO
# pixel-value assertions live in the Node and Python suites). Identity resample onto the same grid.
add_test(NAME resample-to-reference-default-value
COMMAND resample-to-reference
${CMAKE_CURRENT_SOURCE_DIR}/test/data/input/cthead1.png
${CMAKE_CURRENT_SOURCE_DIR}/test/data/input/cthead1.png
${CMAKE_CURRENT_BINARY_DIR}/cthead1_resampled_default_value.png
--interpolator linear
--default-value 128
)
68 changes: 45 additions & 23 deletions packages/downsample/downsample.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,39 @@

#include "downsampleSigma.h"

// Groups the CLI options shared by the scalar and vector downsample paths so both declare exactly one
// option surface -- identical names, order, type_names, and help text. The option-target members must
// outlive ITK_WASM_PARSE (CLI11 binds them by reference), so the caller owns this struct on its own stack
// and passes it to addDownsampleOptions() before parsing.
template <typename TImage>
struct DownsampleOptions
{
std::vector<unsigned int> shrinkFactors{ 2, 2 };
std::vector<unsigned int> cropRadius;
itk::wasm::OutputImage<TImage> downsampledImage;
};

// Declares the shared downsample option surface into `pipeline`, binding each option to the corresponding
// member of `options`. The `input` option is declared separately by each functor (the scalar path adds it
// before pre-parse dispatch), so it is not part of this shared surface.
template <typename TImage>
void
addDownsampleOptions(itk::wasm::Pipeline & pipeline, DownsampleOptions<TImage> & options)
{
constexpr unsigned int ImageDimension = TImage::ImageDimension;

pipeline.add_option("-s,--shrink-factors", options.shrinkFactors, "Shrink factors")
->required()
->type_size(ImageDimension);

pipeline.add_option("-r,--crop-radius", options.cropRadius, "Optional crop radius in pixel units.")
->type_size(ImageDimension);

pipeline.add_option("downsampled", options.downsampledImage, "Output downsampled image")
->required()
->type_name("OUTPUT_IMAGE");
}

// Scalar image processing
template <typename TImage>
int
Expand All @@ -39,21 +72,15 @@ DownsampleScalarImage(itk::wasm::Pipeline & pipeline, const TImage * inputImage)

pipeline.get_option("input")->required()->type_name("INPUT_IMAGE");

std::vector<unsigned int> shrinkFactors{ 2, 2 };
pipeline.add_option("-s,--shrink-factors", shrinkFactors, "Shrink factors")->required()->type_size(ImageDimension);

std::vector<unsigned int> cropRadius;
pipeline.add_option("-r,--crop-radius", cropRadius, "Optional crop radius in pixel units.")
->type_size(ImageDimension);

using OutputImageType = itk::wasm::OutputImage<ImageType>;
OutputImageType downsampledImage;
pipeline.add_option("downsampled", downsampledImage, "Output downsampled image")
->required()
->type_name("OUTPUT_IMAGE");
DownsampleOptions<ImageType> options;
addDownsampleOptions(pipeline, options);

ITK_WASM_PARSE(pipeline);

const std::vector<unsigned int> & shrinkFactors = options.shrinkFactors;
const std::vector<unsigned int> & cropRadius = options.cropRadius;
auto & downsampledImage = options.downsampledImage;

auto sigmaValues = downsampleSigma(shrinkFactors);

using GaussianFilterType = itk::DiscreteGaussianImageFilter<ImageType, ImageType>;
Expand Down Expand Up @@ -142,20 +169,15 @@ class PipelineFunctor<itk::VectorImage<TPixel, VDimension>>
InputImageType inputImage;
pipeline.add_option("input", inputImage, "Input image")->required()->type_name("INPUT_IMAGE");

std::vector<unsigned int> shrinkFactors{ 2, 2 };
pipeline.add_option("-s,--shrink-factors", shrinkFactors, "Shrink factors")->required()->type_size(Dimension);

std::vector<unsigned int> cropRadius;
pipeline.add_option("-r,--crop-radius", cropRadius, "Optional crop radius in pixel units.")->type_size(Dimension);

using OutputImageType = itk::wasm::OutputImage<VectorImageType>;
OutputImageType downsampledImage;
pipeline.add_option("downsampled", downsampledImage, "Output downsampled image")
->required()
->type_name("OUTPUT_IMAGE");
DownsampleOptions<VectorImageType> options;
addDownsampleOptions(pipeline, options);

ITK_WASM_PARSE(pipeline);

const std::vector<unsigned int> & shrinkFactors = options.shrinkFactors;
const std::vector<unsigned int> & cropRadius = options.cropRadius;
auto & downsampledImage = options.downsampledImage;

// Get number of components
const unsigned int numberOfComponents = inputImage.Get()->GetNumberOfComponentsPerPixel();

Expand Down
6 changes: 3 additions & 3 deletions packages/downsample/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
"itk-wasm": {
"emscripten-docker-image": "docker.io/itkwasm/emscripten:latest",
"wasi-docker-image": "docker.io/itkwasm/wasi:latest",
"test-data-hash": "bafkreigtrr3oeqaunbyhrpuvvhc5hfmpbddc4u52jvonsvr2m2xe5ni3jq",
"test-data-hash": "bafkreiesjpg3sjqkyjb77djrffdvznjjsgvudd2y44iqjrfgnd45uyyprq",
"test-data-urls": [
"https://itk.mypinata.cloud/ipfs/bafkreigtrr3oeqaunbyhrpuvvhc5hfmpbddc4u52jvonsvr2m2xe5ni3jq"
"https://itk.mypinata.cloud/ipfs/bafkreiesjpg3sjqkyjb77djrffdvznjjsgvudd2y44iqjrfgnd45uyyprq"
],
"typescript-package-name": "@itk-wasm/downsample",
"python-package-name": "itkwasm-downsample",
Expand All @@ -32,7 +32,7 @@
"build:gen:typescript": "itk-wasm pnpm-script build:gen:typescript",
"build:gen:python": "pnpm build:wasi && pnpm bindgen:python",
"test": "pnpm test:data:download && pnpm build:gen:python && pnpm test:python",
"test:data:download": "dam download test/data test/data.tar.gz bafkreigtrr3oeqaunbyhrpuvvhc5hfmpbddc4u52jvonsvr2m2xe5ni3jq https://itk.mypinata.cloud/ipfs/bafkreigtrr3oeqaunbyhrpuvvhc5hfmpbddc4u52jvonsvr2m2xe5ni3jq",
"test:data:download": "dam download test/data test/data.tar.gz bafkreiesjpg3sjqkyjb77djrffdvznjjsgvudd2y44iqjrfgnd45uyyprq https://itk.mypinata.cloud/ipfs/bafkreiesjpg3sjqkyjb77djrffdvznjjsgvudd2y44iqjrfgnd45uyyprq",
"test:data:pack": "dam pack test/data test/data.tar.gz",
"test:python:wasi": "pnpm test:data:download && pixi run --manifest-path=./pixi.toml test-wasi",
"test:python:emscripten": "pnpm test:data:download && pixi run --manifest-path=./pixi.toml test-emscripten",
Expand Down
Loading
Loading