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
197 changes: 164 additions & 33 deletions packages/downsample/downsample.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,86 @@
#include "itkDiscreteGaussianImageFilter.h"
#include "itkLinearInterpolateImageFunction.h"
#include "itkResampleImageFilter.h"
#include "itkVectorIndexSelectionCastImageFilter.h"
#include "itkComposeImageFilter.h"

#include "downsampleSigma.h"

// Scalar image processing
template <typename TImage>
int
DownsampleScalarImage(itk::wasm::Pipeline & pipeline, const TImage * inputImage)
{
using ImageType = TImage;
constexpr unsigned int ImageDimension = ImageType::ImageDimension;

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");

ITK_WASM_PARSE(pipeline);

auto sigmaValues = downsampleSigma(shrinkFactors);

using GaussianFilterType = itk::DiscreteGaussianImageFilter<ImageType, ImageType>;
auto gaussianFilter = GaussianFilterType::New();
gaussianFilter->SetInput(inputImage);
typename GaussianFilterType::ArrayType sigmaArray;
for (unsigned int i = 0; i < ImageDimension; ++i)
{
sigmaArray[i] = sigmaValues[i];
}
gaussianFilter->SetSigmaArray(sigmaArray);
gaussianFilter->SetUseImageSpacingOff();

const auto inputOrigin = inputImage->GetOrigin();
const auto inputSpacing = inputImage->GetSpacing();
const auto inputSize = inputImage->GetLargestPossibleRegion().GetSize();

typename ImageType::PointType outputOrigin;
typename ImageType::SpacingType outputSpacing;
typename ImageType::SizeType outputSize;
for (unsigned int i = 0; i < ImageDimension; ++i)
{
const double cropRadiusValue = cropRadius.size() ? cropRadius[i] : 0.0;

outputOrigin[i] = inputOrigin[i] + cropRadiusValue * inputSpacing[i];
outputSpacing[i] = inputSpacing[i] * shrinkFactors[i];
outputSize[i] = std::max<itk::SizeValueType>(0, (inputSize[i] - 2 * cropRadiusValue) / shrinkFactors[i]);
}

using InterpolatorType = itk::LinearInterpolateImageFunction<ImageType, double>;
auto interpolator = InterpolatorType::New();

using ResampleFilterType = itk::ResampleImageFilter<ImageType, ImageType>;
auto shrinkFilter = ResampleFilterType::New();
shrinkFilter->SetInput(gaussianFilter->GetOutput());
shrinkFilter->SetInterpolator(interpolator);
shrinkFilter->SetOutputOrigin(outputOrigin);
shrinkFilter->SetOutputSpacing(outputSpacing);
shrinkFilter->SetOutputDirection(inputImage->GetDirection());
shrinkFilter->SetSize(outputSize);
shrinkFilter->SetOutputStartIndex(inputImage->GetLargestPossibleRegion().GetIndex());

ITK_WASM_CATCH_EXCEPTION(pipeline, shrinkFilter->UpdateLargestPossibleRegion());

typename ImageType::ConstPointer result = shrinkFilter->GetOutput();
downsampledImage.Set(result);

return EXIT_SUCCESS;
}

template <typename TImage>
class PipelineFunctor
{
Expand All @@ -39,44 +116,64 @@ class PipelineFunctor

using InputImageType = itk::wasm::InputImage<ImageType>;
InputImageType inputImage;
pipeline.add_option("input", inputImage, "Input image")->type_name("INPUT_IMAGE");

ITK_WASM_PRE_PARSE(pipeline);

typename ImageType::ConstPointer image = inputImage.Get();
return DownsampleScalarImage<ImageType>(pipeline, image);
}
};

// Specialization for VectorImage types
template <typename TPixel, unsigned int VDimension>
class PipelineFunctor<itk::VectorImage<TPixel, VDimension>>
{
public:
int
operator()(itk::wasm::Pipeline & pipeline)
{
constexpr unsigned int Dimension = VDimension;
using PixelType = TPixel;
using VectorImageType = itk::VectorImage<PixelType, Dimension>;
using ScalarImageType = itk::Image<PixelType, Dimension>;

using InputImageType = itk::wasm::InputImage<VectorImageType>;
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(ImageDimension);
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(ImageDimension);
->type_size(Dimension);

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

ITK_WASM_PARSE(pipeline);

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

using GaussianFilterType = itk::DiscreteGaussianImageFilter<ImageType, ImageType>;
auto gaussianFilter = GaussianFilterType::New();
gaussianFilter->SetInput(inputImage.Get());
typename GaussianFilterType::ArrayType sigmaArray;
for (unsigned int i = 0; i < ImageDimension; ++i)
{
sigmaArray[i] = sigmaValues[i];
}
gaussianFilter->SetSigmaArray(sigmaArray);
gaussianFilter->SetUseImageSpacingOff();
// Extract, process, and compose each component
using ExtractFilterType = itk::VectorIndexSelectionCastImageFilter<VectorImageType, ScalarImageType>;
using ComposeFilterType = itk::ComposeImageFilter<ScalarImageType>;

auto sigmaValues = downsampleSigma(shrinkFactors);

const auto inputOrigin = inputImage.Get()->GetOrigin();
const auto inputSpacing = inputImage.Get()->GetSpacing();
const auto inputSize = inputImage.Get()->GetLargestPossibleRegion().GetSize();

typename ImageType::PointType outputOrigin;
typename ImageType::SpacingType outputSpacing;
typename ImageType::SizeType outputSize;
for (unsigned int i = 0; i < ImageDimension; ++i)
typename VectorImageType::PointType outputOrigin;
typename VectorImageType::SpacingType outputSpacing;
typename VectorImageType::SizeType outputSize;
for (unsigned int i = 0; i < Dimension; ++i)
{
const double cropRadiusValue = cropRadius.size() ? cropRadius[i] : 0.0;

Expand All @@ -85,23 +182,51 @@ class PipelineFunctor
outputSize[i] = std::max<itk::SizeValueType>(0, (inputSize[i] - 2 * cropRadiusValue) / shrinkFactors[i]);
}

using InterpolatorType = itk::LinearInterpolateImageFunction<ImageType, double>;
auto interpolator = InterpolatorType::New();
auto composeFilter = ComposeFilterType::New();

auto extractFilter = ExtractFilterType::New();
extractFilter->SetInput(inputImage.Get());

using GaussianFilterType = itk::DiscreteGaussianImageFilter<ScalarImageType, ScalarImageType>;
auto gaussianFilter = GaussianFilterType::New();

for (unsigned int component = 0; component < numberOfComponents; ++component)
{
// Extract component
extractFilter->SetIndex(component);

// Smooth component
gaussianFilter->SetInput(extractFilter->GetOutput());
typename GaussianFilterType::ArrayType sigmaArray;
for (unsigned int i = 0; i < Dimension; ++i)
{
sigmaArray[i] = sigmaValues[i];
}
gaussianFilter->SetSigmaArray(sigmaArray);
gaussianFilter->SetUseImageSpacingOff();

using ResampleFilterType = itk::ResampleImageFilter<ImageType, ImageType>;
auto shrinkFilter = ResampleFilterType::New();
shrinkFilter->SetInput(gaussianFilter->GetOutput());
shrinkFilter->SetInterpolator(interpolator);
shrinkFilter->SetOutputOrigin(outputOrigin);
shrinkFilter->SetOutputSpacing(outputSpacing);
shrinkFilter->SetOutputDirection(inputImage.Get()->GetDirection());
shrinkFilter->SetSize(outputSize);
shrinkFilter->SetOutputStartIndex(inputImage.Get()->GetLargestPossibleRegion().GetIndex());
// Resample component
using InterpolatorType = itk::LinearInterpolateImageFunction<ScalarImageType, double>;
auto interpolator = InterpolatorType::New();

using ResampleFilterType = itk::ResampleImageFilter<ScalarImageType, ScalarImageType>;
auto shrinkFilter = ResampleFilterType::New();
shrinkFilter->SetInput(gaussianFilter->GetOutput());
shrinkFilter->SetInterpolator(interpolator);
shrinkFilter->SetOutputOrigin(outputOrigin);
shrinkFilter->SetOutputSpacing(outputSpacing);
shrinkFilter->SetOutputDirection(inputImage.Get()->GetDirection());
shrinkFilter->SetSize(outputSize);
shrinkFilter->SetOutputStartIndex(inputImage.Get()->GetLargestPossibleRegion().GetIndex());
shrinkFilter->UpdateLargestPossibleRegion();

// Add to compose filter
composeFilter->SetInput(component, shrinkFilter->GetOutput());
}

ITK_WASM_CATCH_EXCEPTION(pipeline, shrinkFilter->UpdateLargestPossibleRegion());
ITK_WASM_CATCH_EXCEPTION(pipeline, composeFilter->UpdateLargestPossibleRegion());

typename ImageType::ConstPointer result = shrinkFilter->GetOutput();
downsampledImage.Set(result);
downsampledImage.Set(composeFilter->GetOutput());

return EXIT_SUCCESS;
}
Expand All @@ -123,5 +248,11 @@ main(int argc, char * argv[])
uint64_t,
int64_t,
float,
double>::Dimensions<2U, 3U, 4U, 5U>("input", pipeline);
double,
itk::VariableLengthVector<uint8_t>,
itk::VariableLengthVector<uint16_t>,
itk::VariableLengthVector<int16_t>,
itk::VariableLengthVector<float>,
itk::VariableLengthVector<double>
>::Dimensions<2U, 3U, 4U, 5U>("input", pipeline);
}
9 changes: 4 additions & 5 deletions packages/downsample/package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
{
"name": "@itk-wasm/downsample-build",
"version": "1.7.1",
"version": "1.8.0",
"private": true,
"description": "Pipelines for downsampling images.",
"type": "module",
"itk-wasm": {
"emscripten-docker-image": "quay.io/itkwasm/emscripten:latest",
"wasi-docker-image": "quay.io/itkwasm/wasi:latest",
"test-data-hash": "bafkreic7utwwa32sc7ekhouzdlnla4kffytphcwc7qwam5ndhixwjulydq",
"test-data-hash": "bafkreigtrr3oeqaunbyhrpuvvhc5hfmpbddc4u52jvonsvr2m2xe5ni3jq",
"test-data-urls": [
"https://github.com/InsightSoftwareConsortium/ITK-Wasm/releases/download/itk-wasm-v1.0.0-b.163/itkwasm-downsample-test-data.tar.gz https://w3s.link/ipfs/bafybeifwebok64osjl2i3zc6rkn3izgon333wsjotqzqlxorkkvrbldjcy/data.tar.gz",
"https://w3s.link/ipfs/bafybeifwebok64osjl2i3zc6rkn3izgon333wsjotqzqlxorkkvrbldjcy/data.tar.gz"
"https://itk.mypinata.cloud/ipfs/bafkreigtrr3oeqaunbyhrpuvvhc5hfmpbddc4u52jvonsvr2m2xe5ni3jq"
],
"typescript-package-name": "@itk-wasm/downsample",
"python-package-name": "itkwasm-downsample",
Expand All @@ -29,7 +28,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 bafkreic7utwwa32sc7ekhouzdlnla4kffytphcwc7qwam5ndhixwjulydq https://github.com/InsightSoftwareConsortium/ITK-Wasm/releases/download/itk-wasm-v1.0.0-b.163/itkwasm-downsample-test-data.tar.gz https://w3s.link/ipfs/bafybeifwebok64osjl2i3zc6rkn3izgon333wsjotqzqlxorkkvrbldjcy/data.tar.gz",
"test:data:download": "dam download test/data test/data.tar.gz bafkreigtrr3oeqaunbyhrpuvvhc5hfmpbddc4u52jvonsvr2m2xe5ni3jq https://itk.mypinata.cloud/ipfs/bafkreigtrr3oeqaunbyhrpuvvhc5hfmpbddc4u52jvonsvr2m2xe5ni3jq",
"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