Skip to content

Commit 5d3c42e

Browse files
authored
Merge pull request #1465 from thewtex/downsample-types
downsample types
2 parents ab78511 + 273d84b commit 5d3c42e

File tree

10 files changed

+248
-103
lines changed

10 files changed

+248
-103
lines changed

packages/downsample/downsample.cxx

Lines changed: 164 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,86 @@
2424
#include "itkDiscreteGaussianImageFilter.h"
2525
#include "itkLinearInterpolateImageFunction.h"
2626
#include "itkResampleImageFilter.h"
27+
#include "itkVectorIndexSelectionCastImageFilter.h"
28+
#include "itkComposeImageFilter.h"
2729

2830
#include "downsampleSigma.h"
2931

32+
// Scalar image processing
33+
template <typename TImage>
34+
int
35+
DownsampleScalarImage(itk::wasm::Pipeline & pipeline, const TImage * inputImage)
36+
{
37+
using ImageType = TImage;
38+
constexpr unsigned int ImageDimension = ImageType::ImageDimension;
39+
40+
pipeline.get_option("input")->required()->type_name("INPUT_IMAGE");
41+
42+
std::vector<unsigned int> shrinkFactors{ 2, 2 };
43+
pipeline.add_option("-s,--shrink-factors", shrinkFactors, "Shrink factors")->required()->type_size(ImageDimension);
44+
45+
std::vector<unsigned int> cropRadius;
46+
pipeline.add_option("-r,--crop-radius", cropRadius, "Optional crop radius in pixel units.")
47+
->type_size(ImageDimension);
48+
49+
using OutputImageType = itk::wasm::OutputImage<ImageType>;
50+
OutputImageType downsampledImage;
51+
pipeline.add_option("downsampled", downsampledImage, "Output downsampled image")
52+
->required()
53+
->type_name("OUTPUT_IMAGE");
54+
55+
ITK_WASM_PARSE(pipeline);
56+
57+
auto sigmaValues = downsampleSigma(shrinkFactors);
58+
59+
using GaussianFilterType = itk::DiscreteGaussianImageFilter<ImageType, ImageType>;
60+
auto gaussianFilter = GaussianFilterType::New();
61+
gaussianFilter->SetInput(inputImage);
62+
typename GaussianFilterType::ArrayType sigmaArray;
63+
for (unsigned int i = 0; i < ImageDimension; ++i)
64+
{
65+
sigmaArray[i] = sigmaValues[i];
66+
}
67+
gaussianFilter->SetSigmaArray(sigmaArray);
68+
gaussianFilter->SetUseImageSpacingOff();
69+
70+
const auto inputOrigin = inputImage->GetOrigin();
71+
const auto inputSpacing = inputImage->GetSpacing();
72+
const auto inputSize = inputImage->GetLargestPossibleRegion().GetSize();
73+
74+
typename ImageType::PointType outputOrigin;
75+
typename ImageType::SpacingType outputSpacing;
76+
typename ImageType::SizeType outputSize;
77+
for (unsigned int i = 0; i < ImageDimension; ++i)
78+
{
79+
const double cropRadiusValue = cropRadius.size() ? cropRadius[i] : 0.0;
80+
81+
outputOrigin[i] = inputOrigin[i] + cropRadiusValue * inputSpacing[i];
82+
outputSpacing[i] = inputSpacing[i] * shrinkFactors[i];
83+
outputSize[i] = std::max<itk::SizeValueType>(0, (inputSize[i] - 2 * cropRadiusValue) / shrinkFactors[i]);
84+
}
85+
86+
using InterpolatorType = itk::LinearInterpolateImageFunction<ImageType, double>;
87+
auto interpolator = InterpolatorType::New();
88+
89+
using ResampleFilterType = itk::ResampleImageFilter<ImageType, ImageType>;
90+
auto shrinkFilter = ResampleFilterType::New();
91+
shrinkFilter->SetInput(gaussianFilter->GetOutput());
92+
shrinkFilter->SetInterpolator(interpolator);
93+
shrinkFilter->SetOutputOrigin(outputOrigin);
94+
shrinkFilter->SetOutputSpacing(outputSpacing);
95+
shrinkFilter->SetOutputDirection(inputImage->GetDirection());
96+
shrinkFilter->SetSize(outputSize);
97+
shrinkFilter->SetOutputStartIndex(inputImage->GetLargestPossibleRegion().GetIndex());
98+
99+
ITK_WASM_CATCH_EXCEPTION(pipeline, shrinkFilter->UpdateLargestPossibleRegion());
100+
101+
typename ImageType::ConstPointer result = shrinkFilter->GetOutput();
102+
downsampledImage.Set(result);
103+
104+
return EXIT_SUCCESS;
105+
}
106+
30107
template <typename TImage>
31108
class PipelineFunctor
32109
{
@@ -39,44 +116,64 @@ class PipelineFunctor
39116

40117
using InputImageType = itk::wasm::InputImage<ImageType>;
41118
InputImageType inputImage;
119+
pipeline.add_option("input", inputImage, "Input image")->type_name("INPUT_IMAGE");
120+
121+
ITK_WASM_PRE_PARSE(pipeline);
122+
123+
typename ImageType::ConstPointer image = inputImage.Get();
124+
return DownsampleScalarImage<ImageType>(pipeline, image);
125+
}
126+
};
127+
128+
// Specialization for VectorImage types
129+
template <typename TPixel, unsigned int VDimension>
130+
class PipelineFunctor<itk::VectorImage<TPixel, VDimension>>
131+
{
132+
public:
133+
int
134+
operator()(itk::wasm::Pipeline & pipeline)
135+
{
136+
constexpr unsigned int Dimension = VDimension;
137+
using PixelType = TPixel;
138+
using VectorImageType = itk::VectorImage<PixelType, Dimension>;
139+
using ScalarImageType = itk::Image<PixelType, Dimension>;
140+
141+
using InputImageType = itk::wasm::InputImage<VectorImageType>;
142+
InputImageType inputImage;
42143
pipeline.add_option("input", inputImage, "Input image")->required()->type_name("INPUT_IMAGE");
43144

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

47148
std::vector<unsigned int> cropRadius;
48149
pipeline.add_option("-r,--crop-radius", cropRadius, "Optional crop radius in pixel units.")
49-
->type_size(ImageDimension);
150+
->type_size(Dimension);
50151

51-
using OutputImageType = itk::wasm::OutputImage<ImageType>;
152+
using OutputImageType = itk::wasm::OutputImage<VectorImageType>;
52153
OutputImageType downsampledImage;
53154
pipeline.add_option("downsampled", downsampledImage, "Output downsampled image")
54155
->required()
55156
->type_name("OUTPUT_IMAGE");
56157

57158
ITK_WASM_PARSE(pipeline);
58159

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

61-
using GaussianFilterType = itk::DiscreteGaussianImageFilter<ImageType, ImageType>;
62-
auto gaussianFilter = GaussianFilterType::New();
63-
gaussianFilter->SetInput(inputImage.Get());
64-
typename GaussianFilterType::ArrayType sigmaArray;
65-
for (unsigned int i = 0; i < ImageDimension; ++i)
66-
{
67-
sigmaArray[i] = sigmaValues[i];
68-
}
69-
gaussianFilter->SetSigmaArray(sigmaArray);
70-
gaussianFilter->SetUseImageSpacingOff();
163+
// Extract, process, and compose each component
164+
using ExtractFilterType = itk::VectorIndexSelectionCastImageFilter<VectorImageType, ScalarImageType>;
165+
using ComposeFilterType = itk::ComposeImageFilter<ScalarImageType>;
166+
167+
auto sigmaValues = downsampleSigma(shrinkFactors);
71168

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

76-
typename ImageType::PointType outputOrigin;
77-
typename ImageType::SpacingType outputSpacing;
78-
typename ImageType::SizeType outputSize;
79-
for (unsigned int i = 0; i < ImageDimension; ++i)
173+
typename VectorImageType::PointType outputOrigin;
174+
typename VectorImageType::SpacingType outputSpacing;
175+
typename VectorImageType::SizeType outputSize;
176+
for (unsigned int i = 0; i < Dimension; ++i)
80177
{
81178
const double cropRadiusValue = cropRadius.size() ? cropRadius[i] : 0.0;
82179

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

88-
using InterpolatorType = itk::LinearInterpolateImageFunction<ImageType, double>;
89-
auto interpolator = InterpolatorType::New();
185+
auto composeFilter = ComposeFilterType::New();
186+
187+
auto extractFilter = ExtractFilterType::New();
188+
extractFilter->SetInput(inputImage.Get());
189+
190+
using GaussianFilterType = itk::DiscreteGaussianImageFilter<ScalarImageType, ScalarImageType>;
191+
auto gaussianFilter = GaussianFilterType::New();
192+
193+
for (unsigned int component = 0; component < numberOfComponents; ++component)
194+
{
195+
// Extract component
196+
extractFilter->SetIndex(component);
197+
198+
// Smooth component
199+
gaussianFilter->SetInput(extractFilter->GetOutput());
200+
typename GaussianFilterType::ArrayType sigmaArray;
201+
for (unsigned int i = 0; i < Dimension; ++i)
202+
{
203+
sigmaArray[i] = sigmaValues[i];
204+
}
205+
gaussianFilter->SetSigmaArray(sigmaArray);
206+
gaussianFilter->SetUseImageSpacingOff();
90207

91-
using ResampleFilterType = itk::ResampleImageFilter<ImageType, ImageType>;
92-
auto shrinkFilter = ResampleFilterType::New();
93-
shrinkFilter->SetInput(gaussianFilter->GetOutput());
94-
shrinkFilter->SetInterpolator(interpolator);
95-
shrinkFilter->SetOutputOrigin(outputOrigin);
96-
shrinkFilter->SetOutputSpacing(outputSpacing);
97-
shrinkFilter->SetOutputDirection(inputImage.Get()->GetDirection());
98-
shrinkFilter->SetSize(outputSize);
99-
shrinkFilter->SetOutputStartIndex(inputImage.Get()->GetLargestPossibleRegion().GetIndex());
208+
// Resample component
209+
using InterpolatorType = itk::LinearInterpolateImageFunction<ScalarImageType, double>;
210+
auto interpolator = InterpolatorType::New();
211+
212+
using ResampleFilterType = itk::ResampleImageFilter<ScalarImageType, ScalarImageType>;
213+
auto shrinkFilter = ResampleFilterType::New();
214+
shrinkFilter->SetInput(gaussianFilter->GetOutput());
215+
shrinkFilter->SetInterpolator(interpolator);
216+
shrinkFilter->SetOutputOrigin(outputOrigin);
217+
shrinkFilter->SetOutputSpacing(outputSpacing);
218+
shrinkFilter->SetOutputDirection(inputImage.Get()->GetDirection());
219+
shrinkFilter->SetSize(outputSize);
220+
shrinkFilter->SetOutputStartIndex(inputImage.Get()->GetLargestPossibleRegion().GetIndex());
221+
shrinkFilter->UpdateLargestPossibleRegion();
222+
223+
// Add to compose filter
224+
composeFilter->SetInput(component, shrinkFilter->GetOutput());
225+
}
100226

101-
ITK_WASM_CATCH_EXCEPTION(pipeline, shrinkFilter->UpdateLargestPossibleRegion());
227+
ITK_WASM_CATCH_EXCEPTION(pipeline, composeFilter->UpdateLargestPossibleRegion());
102228

103-
typename ImageType::ConstPointer result = shrinkFilter->GetOutput();
104-
downsampledImage.Set(result);
229+
downsampledImage.Set(composeFilter->GetOutput());
105230

106231
return EXIT_SUCCESS;
107232
}
@@ -123,5 +248,11 @@ main(int argc, char * argv[])
123248
uint64_t,
124249
int64_t,
125250
float,
126-
double>::Dimensions<2U, 3U, 4U, 5U>("input", pipeline);
251+
double,
252+
itk::VariableLengthVector<uint8_t>,
253+
itk::VariableLengthVector<uint16_t>,
254+
itk::VariableLengthVector<int16_t>,
255+
itk::VariableLengthVector<float>,
256+
itk::VariableLengthVector<double>
257+
>::Dimensions<2U, 3U, 4U, 5U>("input", pipeline);
127258
}

packages/downsample/package.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
{
22
"name": "@itk-wasm/downsample-build",
3-
"version": "1.7.1",
3+
"version": "1.8.0",
44
"private": true,
55
"description": "Pipelines for downsampling images.",
66
"type": "module",
77
"itk-wasm": {
88
"emscripten-docker-image": "quay.io/itkwasm/emscripten:latest",
99
"wasi-docker-image": "quay.io/itkwasm/wasi:latest",
10-
"test-data-hash": "bafkreic7utwwa32sc7ekhouzdlnla4kffytphcwc7qwam5ndhixwjulydq",
10+
"test-data-hash": "bafkreigtrr3oeqaunbyhrpuvvhc5hfmpbddc4u52jvonsvr2m2xe5ni3jq",
1111
"test-data-urls": [
12-
"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",
13-
"https://w3s.link/ipfs/bafybeifwebok64osjl2i3zc6rkn3izgon333wsjotqzqlxorkkvrbldjcy/data.tar.gz"
12+
"https://itk.mypinata.cloud/ipfs/bafkreigtrr3oeqaunbyhrpuvvhc5hfmpbddc4u52jvonsvr2m2xe5ni3jq"
1413
],
1514
"typescript-package-name": "@itk-wasm/downsample",
1615
"python-package-name": "itkwasm-downsample",
@@ -29,7 +28,7 @@
2928
"build:gen:typescript": "itk-wasm pnpm-script build:gen:typescript",
3029
"build:gen:python": "pnpm build:wasi && pnpm bindgen:python",
3130
"test": "pnpm test:data:download && pnpm build:gen:python && pnpm test:python",
32-
"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",
31+
"test:data:download": "dam download test/data test/data.tar.gz bafkreigtrr3oeqaunbyhrpuvvhc5hfmpbddc4u52jvonsvr2m2xe5ni3jq https://itk.mypinata.cloud/ipfs/bafkreigtrr3oeqaunbyhrpuvvhc5hfmpbddc4u52jvonsvr2m2xe5ni3jq",
3332
"test:data:pack": "dam pack test/data test/data.tar.gz",
3433
"test:python:wasi": "pnpm test:data:download && pixi run --manifest-path=./pixi.toml test-wasi",
3534
"test:python:emscripten": "pnpm test:data:download && pixi run --manifest-path=./pixi.toml test-emscripten",

0 commit comments

Comments
 (0)