Repository: InsightSoftwareConsortium/ITKVkFFTBackend (language: python)

Issue:
ENH: Add acceleration for 1DFFT image filters
See [ITKUltrasound](https://github.com/kitwaremedical/itkultrasound) classes.


Discussion / hints:
@thewtex to clarify, what project should have ownership of the non-accelerated 1D FFT classes? Should these be moved into ITK proper or duplicated in `ITKVkFFTBackend`? 

For starting development `ITKVkFFTBackend` can depend on `ITKUltrasound` for these implementations for now.
@tbirdso since the 1D FFT base classes are in `ITKUltrasound`, let's take this path:

1. Have ITKUltrasound depend on ITKVkFFTBackend
2. Improve the ND FFT classes in ITK so they use ITK's factory mechanism and can pick up CUDA or OpenCL implementations when these remote modules are installed.
3. Add the 1D FFT base classes to ITK
4. Move the 1D VkFFT implementations from ITKUltrasound to ITKVkFFTBackend

CC @aylward @SimonRit @LucasGandel 

Fix patch:
diff --git a/include/itkVkCommon.h b/include/itkVkCommon.h
index b8b4096d..63ada0c1 100644
--- a/include/itkVkCommon.h
+++ b/include/itkVkCommon.h
@@ -63,9 +63,13 @@ class VkFFTBackend_EXPORT VkCommon
 
   struct VkParameters
   {
-    uint64_t      X = 0;                    // size of fastest varying dimension
-    uint64_t      Y = 1;                    // size of second-fastest varying dimension, if any, otherwise 1.
-    uint64_t      Z = 1;                    // size of third-fastest varying dimension, if any, otherwise 1.
+    uint64_t X = 0; // size of fastest varying dimension
+    uint64_t Y = 1; // size of second-fastest varying dimension, if any, otherwise 1.
+    uint64_t Z = 1; // size of third-fastest varying dimension, if any, otherwise 1.
+    uint64_t omitDimension[3] = { 0,
+                                  0,
+                                  0 }; // disable FFT for this dimension (0 - FFT enabled, 1 - FFT disabled). Default 0.
+                                       // Doesn't work for R2C dimension 0 for now. Doesn't work with convolutions.
     PrecisionEnum P = PrecisionEnum::FLOAT; // type for real numbers
     uint64_t      B = 1;                    // Number of batches -- always 1
     uint64_t      N = 1;                    // Number of redundant iterations, for benchmarking -- always 1.
diff --git a/include/itkVkComplexToComplex1DFFTImageFilter.h b/include/itkVkComplexToComplex1DFFTImageFilter.h
new file mode 100644
index 00000000..41157ae1
--- /dev/null
+++ b/include/itkVkComplexToComplex1DFFTImageFilter.h
@@ -0,0 +1,108 @@
+/*=========================================================================
+ *
+ *  Copyright NumFOCUS
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0.txt
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *=========================================================================*/
+
+#ifndef itkVkComplexToComplex1DFFTImageFilter_h
+#define itkVkComplexToComplex1DFFTImageFilter_h
+
+#include "itkVkCommon.h"
+#include "itkComplexToComplex1DFFTImageFilter.h"
+
+namespace itk
+{
+
+/**
+ *\class VkComplexToComplex1DFFTImageFilter
+ *
+ *  \brief Implements an API to enable the 1D Fourier transform or the inverse
+ *  Fourier transform of images with complex valued voxels to be computed using
+ *  the VkFFT library.
+ *
+ * This filter is multithreaded and by default supports input images with sizes which are
+ * divisible by primes up to 13. 
+ * 
+ * Execution on input images with sizes divisible by primes greater than 17 may succeed 
+ * with a fallback on Bluestein's algorithm per VkFFT with a cost to performance and output precision.
+ *
+ * \ingroup FourierTransform
+ * \ingroup MultiThreaded
+ * \ingroup ITKFFT
+ * \ingroup VkFFTBackend
+ */
+template <typename TImage>
+class VkComplexToComplex1DFFTImageFilter : public ComplexToComplex1DFFTImageFilter<TImage>
+{
+public:
+  ITK_DISALLOW_COPY_AND_MOVE(VkComplexToComplex1DFFTImageFilter);
+
+  using InputImageType = TImage;
+  using OutputImageType = TImage;
+  static_assert(std::is_same<typename TImage::PixelType, std::complex<float>>::value ||
+                  std::is_same<typename TImage::PixelType, std::complex<double>>::value,
+                "Unsupported pixel type");
+  static_assert(TImage::ImageDimension >= 1 && TImage::ImageDimension <= 3, "Unsupported image dimension");
+
+  /** Standard class type aliases. */
+  using Self = VkComplexToComplex1DFFTImageFilter;
+  using Superclass = ComplexToComplex1DFFTImageFilter<TImage>;
+  using Pointer = SmartPointer<Self>;
+  using ConstPointer = SmartPointer<const Self>;
+  using InputPixelType = typename InputImageType::PixelType;
+  using OutputPixelType = typename OutputImageType::PixelType;
+  using ComplexType = InputPixelType;
+  using RealType = typename ComplexType::value_type;
+  using SizeType = typename InputImageType::SizeType;
+  using SizeValueType = typename InputImageType::SizeValueType;
+  using OutputImageRegionType = typename OutputImageType::RegionType;
+
+  /** Method for creation through the object factory. */
+  itkNewMacro(Self);
+
+  /** Run-time type information (and related methods). */
+  itkTypeMacro(VkComplexToComplex1DFFTImageFilter, ComplexToComplex1DFFTImageFilter);
+
+  static constexpr unsigned int ImageDimension = InputImageType::ImageDimension;
+
+  itkGetMacro(DeviceID, uint64_t);
+  itkSetMacro(DeviceID, uint64_t);
+
+  SizeValueType
+  GetSizeGreatestPrimeFactor() const override;
+
+protected:
+  VkComplexToComplex1DFFTImageFilter() = default;
+  ~VkComplexToComplex1DFFTImageFilter() override = default;
+
+  void
+  GenerateData() override;
+
+  void
+  PrintSelf(std::ostream & os, Indent indent) const override;
+
+private:
+  uint64_t m_DeviceID{ 0UL };
+
+  VkCommon m_VkCommon{};
+};
+
+} // namespace itk
+
+#ifndef ITK_MANUAL_INSTANTIATION
+#  include "itkVkComplexToComplex1DFFTImageFilter.hxx"
+#endif
+
+#endif // itkVkComplexToComplex1DFFTImageFilter_h
diff --git a/include/itkVkComplexToComplex1DFFTImageFilter.hxx b/include/itkVkComplexToComplex1DFFTImageFilter.hxx
new file mode 100644
index 00000000..7eb66096
--- /dev/null
+++ b/include/itkVkComplexToComplex1DFFTImageFilter.hxx
@@ -0,0 +1,127 @@
+/*=========================================================================
+ *
+ *  Copyright NumFOCUS
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0.txt
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *=========================================================================*/
+
+#ifndef itkVkComplexToComplex1DFFTImageFilter_hxx
+#define itkVkComplexToComplex1DFFTImageFilter_hxx
+
+#include "itkVkComplexToComplex1DFFTImageFilter.h"
+#include "vkFFT.h"
+#include "itkImageRegionIterator.h"
+#include "itkIndent.h"
+#include "itkMetaDataObject.h"
+#include "itkProgressReporter.h"
+
+namespace itk
+{
+template <typename TImage>
+void
+VkComplexToComplex1DFFTImageFilter<TImage>::GenerateData()
+{
+  // get pointers to the input and output
+  const InputImageType * const input{ this->GetInput() };
+  OutputImageType * const      output{ this->GetOutput() };
+
+  if (!input || !output)
+  {
+    return;
+  }
+
+  // we don't have a nice progress to report, but at least this simple line
+  // reports the beginning and the end of the process
+  const ProgressReporter progress(this, 0, 1);
+
+  // allocate output buffer memory
+  output->SetBufferedRegion(output->GetRequestedRegion());
+  output->Allocate();
+
+  const SizeType & inputSize{ input->GetLargestPossibleRegion().GetSize() };
+
+  const InputPixelType * const inputCPUBuffer{ input->GetBufferPointer() };
+  OutputPixelType * const      outputCPUBuffer{ output->GetBufferPointer() };
+  itkAssertOrThrowMacro(inputCPUBuffer != nullptr, "No CPU input buffer");
+  itkAssertOrThrowMacro(outputCPUBuffer != nullptr, "No CPU output buffer");
+  const SizeValueType inBytes{ input->GetLargestPossibleRegion().GetNumberOfPixels() * sizeof(InputPixelType) };
+  const SizeValueType outBytes{ output->GetLargestPossibleRegion().GetNumberOfPixels() * sizeof(OutputPixelType) };
+  itkAssertOrThrowMacro(inBytes == outBytes, "CPU input and output buffers are of different sizes.");
+
+  // Mostly use defaults for VkCommon::VkGPU
+  typename VkCommon::VkGPU vkGPU;
+  vkGPU.device_id = m_DeviceID;
+
+  // Describe this filter in VkCommon::VkParameters
+  typename VkCommon::VkParameters vkParameters;
+  if (ImageDimension > 0)
+    vkParameters.X = inputSize[0];
+  if (ImageDimension > 1)
+    vkParameters.Y = inputSize[1];
+  if (ImageDimension > 2)
+    vkParameters.Z = inputSize[2];
+  if (std::is_same<RealType, float>::value)
+    vkParameters.P = VkCommon::PrecisionEnum::FLOAT;
+  else if (std::is_same<RealType, double>::value)
+    vkParameters.P = VkCommon::PrecisionEnum::DOUBLE;
+  else
+    itkAssertOrThrowMacro(false, "Unsupported type for real numbers.");
+  vkParameters.fft = VkCommon::FFTEnum::C2C;
+  vkParameters.PSize = sizeof(RealType);
+  vkParameters.I = this->GetTransformDirection() == Superclass::TransformDirectionType::INVERSE
+                     ? VkCommon::DirectionEnum::INVERSE
+                     : VkCommon::DirectionEnum::FORWARD;
+  vkParameters.normalized = vkParameters.I == VkCommon::DirectionEnum::INVERSE
+                              ? VkCommon::NormalizationEnum::NORMALIZED
+                              : VkCommon::NormalizationEnum::UNNORMALIZED;
+  for (size_t dim = 0; dim < ImageDimension; ++dim)
+  {
+    if (this->GetDirection() != dim)
+    {
+      vkParameters.omitDimension[dim] = 1; // omit dimensions other than in the given direction.
+    }
+  }
+
+  vkParameters.inputCPUBuffer = inputCPUBuffer;
+  vkParameters.inputBufferBytes = inBytes;
+  vkParameters.outputCPUBuffer = outputCPUBuffer;
+  vkParameters.outputBufferBytes = outBytes;
+
+  const VkFFTResult resFFT{ m_VkCommon.Run(vkGPU, vkParameters) };
+  if (resFFT != VKFFT_SUCCESS)
+  {
+    std::ostringstream mesg;
+    mesg << "VkFFT third-party library failed with error code " << resFFT << ".";
+    itkAssertOrThrowMacro(false, mesg.str());
+  }
+}
+
+template <typename TImage>
+void
+VkComplexToComplex1DFFTImageFilter<TImage>::PrintSelf(std::ostream & os, Indent indent) const
+{
+  Superclass::PrintSelf(os, indent);
+  os << indent << "DeviceID: " << m_DeviceID << std::endl;
+}
+
+template <typename TImage>
+typename VkComplexToComplex1DFFTImageFilter<TImage>::SizeValueType
+VkComplexToComplex1DFFTImageFilter<TImage>::GetSizeGreatestPrimeFactor() const
+{
+  return m_VkCommon.GetGreatestPrimeFactor();
+}
+
+} // end namespace itk
+
+#endif // _itkVkComplexToComplex1DFFTImageFilter_hxx
diff --git a/include/itkVkForward1DFFTImageFilter.h b/include/itkVkForward1DFFTImageFilter.h
new file mode 100644
index 00000000..3ab78d75
--- /dev/null
+++ b/include/itkVkForward1DFFTImageFilter.h
@@ -0,0 +1,113 @@
+/*=========================================================================
+ *
+ *  Copyright NumFOCUS
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0.txt
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *=========================================================================*/
+#ifndef itkVkForward1DFFTImageFilter_h
+#define itkVkForward1DFFTImageFilter_h
+
+#include "itkForward1DFFTImageFilter.h"
+#include "itkVkCommon.h"
+
+namespace itk
+{
+/**
+ *\class VkForward1DFFTImageFilter
+ *
+ * \brief Vk-based forward 1D Fast Fourier Transform.
+ *
+ * This filter computes the forward Fourier transform in one dimension of an image. The
+ * implementation is based on the VkFFT library.
+ *
+ * This filter is multithreaded and by default supports input images with sizes which are
+ * divisible by primes up to 13.
+ * 
+ * Execution on input images with sizes divisible by primes greater than 17 may succeed 
+ * with a fallback on Bluestein's algorithm per VkFFT with a cost to performance and output precision.
+ *
+ * \ingroup FourierTransform
+ * \ingroup MultiThreaded
+ * \ingroup ITKFFT
+ * \ingroup VkFFTBackend
+ *
+ * \sa VkGlobalConfiguration
+ * \sa Forward1DFFTImageFilter
+ */
+template <typename TInputImage>
+class VkForward1DFFTImageFilter
+  : public Forward1DFFTImageFilter<TInputImage,
+                                 Image<std::complex<typename TInputImage::PixelType>, TInputImage::ImageDimension>>
+{
+public:
+  ITK_DISALLOW_COPY_AND_MOVE(VkForward1DFFTImageFilter);
+
+  using InputImageType = TInputImage;
+  using OutputImageType = Image<std::complex<typename TInputImage::PixelType>, TInputImage::ImageDimension>;
+  static_assert(std::is_same<typename TInputImage::PixelType, float>::value ||
+                  std::is_same<typename TInputImage::PixelType, double>::value,
+                "Unsupported pixel type");
+  static_assert(TInputImage::ImageDimension >= 1 && TInputImage::ImageDimension <= 3, "Unsupported image dimension");
+
+  /** Standard class type aliases. */
+  using Self = VkForward1DFFTImageFilter;
+  using Superclass = Forward1DFFTImageFilter<InputImageType, OutputImageType>;
+  using Pointer = SmartPointer<Self>;
+  using ConstPointer = SmartPointer<const Self>;
+
+  using InputPixelType = typename InputImageType::PixelType;
+  using OutputPixelType = typename OutputImageType::PixelType;
+  using ComplexType = OutputPixelType;
+  using RealType = typename ComplexType::value_type;
+  using SizeType = typename InputImageType::SizeType;
+  using SizeValueType = typename InputImageType::SizeValueType;
+  using OutputImageRegionType = typename OutputImageType::RegionType;
+
+  /** Method for creation through the object factory. */
+  itkNewMacro(Self);
+
+  /** Run-time type information (and related methods). */
+  itkTypeMacro(VkForward1DFFTImageFilter, Forward1DFFTImageFilter);
+
+  static constexpr unsigned int ImageDimension = InputImageType::ImageDimension;
+
+  itkGetMacro(DeviceID, uint64_t);
+  itkSetMacro(DeviceID, uint64_t);
+
+  SizeValueType
+  GetSizeGreatestPrimeFactor() const override;
+
+protected:
+  VkForward1DFFTImageFilter() = default;
+  ~VkForward1DFFTImageFilter() override = default;
+
+  void
+  GenerateData() override;
+
+  void
+  PrintSelf(std::ostream & os, Indent indent) const override;
+
+private:
+  uint64_t m_DeviceID{ 0UL };
+
+  VkCommon m_VkCommon{};
+};
+
+} // namespace itk
+
+#ifndef ITK_MANUAL_INSTANTIATION
+#  include "itkVkForward1DFFTImageFilter.hxx"
+#endif
+
+#endif // itkVkForward1DFFTImageFilter_h
diff --git a/include/itkVkForward1DFFTImageFilter.hxx b/include/itkVkForward1DFFTImageFilter.hxx
new file mode 100644
index 00000000..968a3b85
--- /dev/null
+++ b/include/itkVkForward1DFFTImageFilter.hxx
@@ -0,0 +1,123 @@
+/*=========================================================================
+ *
+ *  Copyright NumFOCUS
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0.txt
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *=========================================================================*/
+#ifndef itkVkForward1DFFTImageFilter_hxx
+#define itkVkForward1DFFTImageFilter_hxx
+
+#include "itkHalfToFullHermitianImageFilter.h"
+#include "itkVkForward1DFFTImageFilter.h"
+#include "itkIndent.h"
+#include "itkMetaDataObject.h"
+#include "itkProgressReporter.h"
+#include "itkMultiThreaderBase.h"
+
+#include <iostream>
+
+namespace itk
+{
+template <typename TInputImage>
+void
+VkForward1DFFTImageFilter<TInputImage>::GenerateData()
+{
+  // get pointers to the input and output
+  const InputImageType * const input{ this->GetInput() };
+  OutputImageType * const      output{ this->GetOutput() };
+
+  if (!input || !output)
+  {
+    return;
+  }
+
+  // we don't have a nice progress to report, but at least this simple line
+  // reports the beginning and the end of the process
+  const ProgressReporter progress(this, 0, 1);
+
+  // allocate output buffer memory
+  output->SetBufferedRegion(output->GetRequestedRegion());
+  output->Allocate();
+
+  const SizeType & inputSize{ input->GetLargestPossibleRegion().GetSize() };
+
+  const InputPixelType * const inputCPUBuffer{ input->GetBufferPointer() };
+  OutputPixelType * const      outputCPUBuffer{ output->GetBufferPointer() };
+  itkAssertOrThrowMacro(inputCPUBuffer != nullptr, "No CPU input buffer");
+  itkAssertOrThrowMacro(outputCPUBuffer != nullptr, "No CPU output buffer");
+  const SizeValueType inBytes{ input->GetLargestPossibleRegion().GetNumberOfPixels() * sizeof(InputPixelType) };
+  const SizeValueType outBytes{ output->GetLargestPossibleRegion().GetNumberOfPixels() * sizeof(OutputPixelType) };
+
+  // Mostly use defaults for VkCommon::VkGPU
+  typename VkCommon::VkGPU vkGPU;
+  vkGPU.device_id = m_DeviceID;
+
+  // Describe this filter in VkCommon::VkParameters
+  typename VkCommon::VkParameters vkParameters;
+  if (ImageDimension > 0)
+    vkParameters.X = inputSize[0];
+  if (ImageDimension > 1)
+    vkParameters.Y = inputSize[1];
+  if (ImageDimension > 2)
+    vkParameters.Z = inputSize[2];
+  if (std::is_same<RealType, float>::value)
+    vkParameters.P = VkCommon::PrecisionEnum::FLOAT;
+  else if (std::is_same<RealType, double>::value)
+    vkParameters.P = VkCommon::PrecisionEnum::DOUBLE;
+  else
+    itkAssertOrThrowMacro(false, "Unsupported type for real numbers.");
+  vkParameters.fft = VkCommon::FFTEnum::R2FullH;
+  vkParameters.PSize = sizeof(RealType);
+  vkParameters.I = VkCommon::DirectionEnum::FORWARD;
+  vkParameters.normalized = VkCommon::NormalizationEnum::UNNORMALIZED;
+  for (size_t dim = 0; dim < ImageDimension; ++dim)
+  {
+    if (this->GetDirection() != dim)
+    {
+      vkParameters.omitDimension[dim] = 1; // omit dimensions other than in the given direction.
+    }
+  }
+
+  vkParameters.inputCPUBuffer = inputCPUBuffer;
+  vkParameters.inputBufferBytes = inBytes;
+  vkParameters.outputCPUBuffer = outputCPUBuffer;
+  vkParameters.outputBufferBytes = outBytes;
+
+  const VkFFTResult resFFT{ m_VkCommon.Run(vkGPU, vkParameters) };
+  if (resFFT != VKFFT_SUCCESS)
+  {
+    std::ostringstream mesg;
+    mesg << "VkFFT third-party library failed with error code " << resFFT << ".";
+    itkAssertOrThrowMacro(false, mesg.str());
+  }
+}
+
+template <typename TInputImage>
+void
+VkForward1DFFTImageFilter<TInputImage>::PrintSelf(std::ostream & os, Indent indent) const
+{
+  Superclass::PrintSelf(os, indent);
+  os << indent << "DeviceID: " << m_DeviceID << std::endl;
+}
+
+template <typename TInputImage>
+typename VkForward1DFFTImageFilter<TInputImage>::SizeValueType
+VkForward1DFFTImageFilter<TInputImage>::GetSizeGreatestPrimeFactor() const
+{
+  return m_VkCommon.GetGreatestPrimeFactor();
+}
+
+} // end namespace itk
+
+#endif // _itkVkForward1DFFTImageFilter_hxx
diff --git a/include/itkVkInverse1DFFTImageFilter.h b/include/itkVkInverse1DFFTImageFilter.h
new file mode 100644
index 00000000..ea3e9fbb
--- /dev/null
+++ b/include/itkVkInverse1DFFTImageFilter.h
@@ -0,0 +1,113 @@
+/*=========================================================================
+ *
+ *  Copyright NumFOCUS
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0.txt
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *=========================================================================*/
+#ifndef itkVkInverse1DFFTImageFilter_h
+#define itkVkInverse1DFFTImageFilter_h
+
+#include "itkInverse1DFFTImageFilter.h"
+#include "itkVkCommon.h"
+
+namespace itk
+{
+/**
+ *\class VkInverse1DFFTImageFilter
+ *
+ * \brief Vk-based inverse 1D Fast Fourier Transform.
+ *
+ * This filter computes the inverse Fourier transform in one dimension of an image. The
+ * implementation is based on the VkFFT library.
+ *
+ * This filter is multithreaded and by default supports input images with sizes which are
+ * divisible by primes up to 13.
+ * 
+ * Execution on input images with sizes divisible by primes greater than 17 may succeed 
+ * with a fallback on Bluestein's algorithm per VkFFT with a cost to performance and output precision.
+ *
+ * \ingroup FourierTransform
+ * \ingroup MultiThreaded
+ * \ingroup ITKFFT
+ * \ingroup VkFFTBackend
+ *
+ * \sa VkGlobalConfiguration
+ * \sa Inverse1DFFTImageFilter
+ */
+template <typename TInputImage>
+class VkInverse1DFFTImageFilter
+  : public Inverse1DFFTImageFilter<TInputImage,
+                                 Image<typename TInputImage::PixelType::value_type, TInputImage::ImageDimension>>
+{
+public:
+  ITK_DISALLOW_COPY_AND_MOVE(VkInverse1DFFTImageFilter);
+
+  using InputImageType = TInputImage;
+  using OutputImageType = Image<typename TInputImage::PixelType::value_type, TInputImage::ImageDimension>;
+  static_assert(std::is_same<typename TInputImage::PixelType, std::complex<float>>::value ||
+                  std::is_same<typename TInputImage::PixelType, std::complex<double>>::value,
+                "Unsupported pixel type");
+  static_assert(TInputImage::ImageDimension >= 1 && TInputImage::ImageDimension <= 3, "Unsupported image dimension");
+
+  /** Standard class type aliases. */
+  using Self = VkInverse1DFFTImageFilter;
+  using Superclass = Inverse1DFFTImageFilter<InputImageType, OutputImageType>;
+  using Pointer = SmartPointer<Self>;
+  using ConstPointer = SmartPointer<const Self>;
+
+  using InputPixelType = typename InputImageType::PixelType;
+  using OutputPixelType = typename OutputImageType::PixelType;
+  using ComplexType = InputPixelType;
+  using RealType = typename ComplexType::value_type;
+  using SizeType = typename InputImageType::SizeType;
+  using SizeValueType = typename InputImageType::SizeValueType;
+  using OutputImageRegionType = typename OutputImageType::RegionType;
+
+  /** Method for creation through the object factory. */
+  itkNewMacro(Self);
+
+  /** Run-time type information (and related methods). */
+  itkTypeMacro(VkInverse1DFFTImageFilter, Inverse1DFFTImageFilter);
+
+  static constexpr unsigned int ImageDimension = InputImageType::ImageDimension;
+
+  itkGetMacro(DeviceID, uint64_t);
+  itkSetMacro(DeviceID, uint64_t);
+
+  SizeValueType
+  GetSizeGreatestPrimeFactor() const override;
+
+protected:
+  VkInverse1DFFTImageFilter() = default;
+  ~VkInverse1DFFTImageFilter() override = default;
+
+  void
+  GenerateData() override;
+
+  void
+  PrintSelf(std::ostream & os, Indent indent) const override;
+
+private:
+  uint64_t m_DeviceID{ 0UL };
+
+  VkCommon m_VkCommon{};
+};
+
+} // namespace itk
+
+#ifndef ITK_MANUAL_INSTANTIATION
+#  include "itkVkInverse1DFFTImageFilter.hxx"
+#endif
+
+#endif // itkVkInverse1DFFTImageFilter_h
diff --git a/include/itkVkInverse1DFFTImageFilter.hxx b/include/itkVkInverse1DFFTImageFilter.hxx
new file mode 100644
index 00000000..76c80a93
--- /dev/null
+++ b/include/itkVkInverse1DFFTImageFilter.hxx
@@ -0,0 +1,123 @@
+/*=========================================================================
+ *
+ *  Copyright NumFOCUS
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0.txt
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *=========================================================================*/
+#ifndef itkVkInverse1DFFTImageFilter_hxx
+#define itkVkInverse1DFFTImageFilter_hxx
+
+#include "itkHalfToFullHermitianImageFilter.h"
+#include "itkVkInverse1DFFTImageFilter.h"
+#include "itkIndent.h"
+#include "itkMetaDataObject.h"
+#include "itkProgressReporter.h"
+#include "itkMultiThreaderBase.h"
+
+#include <iostream>
+
+namespace itk
+{
+template <typename TInputImage>
+void
+VkInverse1DFFTImageFilter<TInputImage>::GenerateData()
+{
+  // get pointers to the input and output
+  const InputImageType * const input{ this->GetInput() };
+  OutputImageType * const      output{ this->GetOutput() };
+
+  if (!input || !output)
+  {
+    return;
+  }
+
+  // we don't have a nice progress to report, but at least this simple line
+  // reports the beginning and the end of the process
+  const ProgressReporter progress(this, 0, 1);
+
+  // allocate output buffer memory
+  output->SetBufferedRegion(output->GetRequestedRegion());
+  output->Allocate();
+
+  const SizeType & inputSize{ input->GetLargestPossibleRegion().GetSize() };
+
+  const InputPixelType * const inputCPUBuffer{ input->GetBufferPointer() };
+  OutputPixelType * const      outputCPUBuffer{ output->GetBufferPointer() };
+  itkAssertOrThrowMacro(inputCPUBuffer != nullptr, "No CPU input buffer");
+  itkAssertOrThrowMacro(outputCPUBuffer != nullptr, "No CPU output buffer");
+  const SizeValueType inBytes{ input->GetLargestPossibleRegion().GetNumberOfPixels() * sizeof(InputPixelType) };
+  const SizeValueType outBytes{ output->GetLargestPossibleRegion().GetNumberOfPixels() * sizeof(OutputPixelType) };
+
+  // Mostly use defaults for VkCommon::VkGPU
+  typename VkCommon::VkGPU vkGPU;
+  vkGPU.device_id = m_DeviceID;
+
+  // Describe this filter in VkCommon::VkParameters
+  typename VkCommon::VkParameters vkParameters;
+  if (ImageDimension > 0)
+    vkParameters.X = inputSize[0];
+  if (ImageDimension > 1)
+    vkParameters.Y = inputSize[1];
+  if (ImageDimension > 2)
+    vkParameters.Z = inputSize[2];
+  if (std::is_same<RealType, float>::value)
+    vkParameters.P = VkCommon::PrecisionEnum::FLOAT;
+  else if (std::is_same<RealType, double>::value)
+    vkParameters.P = VkCommon::PrecisionEnum::DOUBLE;
+  else
+    itkAssertOrThrowMacro(false, "Unsupported type for real numbers.");
+  vkParameters.fft = VkCommon::FFTEnum::R2FullH;
+  vkParameters.PSize = sizeof(RealType);
+  vkParameters.I = VkCommon::DirectionEnum::INVERSE;
+  vkParameters.normalized = VkCommon::NormalizationEnum::NORMALIZED;
+  for (size_t dim = 0; dim < ImageDimension; ++dim)
+  {
+    if (this->GetDirection() != dim)
+    {
+      vkParameters.omitDimension[dim] = 1; // omit dimensions other than in the given direction.
+    }
+  }
+
+  vkParameters.inputCPUBuffer = inputCPUBuffer;
+  vkParameters.inputBufferBytes = inBytes;
+  vkParameters.outputCPUBuffer = outputCPUBuffer;
+  vkParameters.outputBufferBytes = outBytes;
+
+  const VkFFTResult resFFT{ m_VkCommon.Run(vkGPU, vkParameters) };
+  if (resFFT != VKFFT_SUCCESS)
+  {
+    std::ostringstream mesg;
+    mesg << "VkFFT third-party library failed with error code " << resFFT << ".";
+    itkAssertOrThrowMacro(false, mesg.str());
+  }
+}
+
+template <typename TInputImage>
+void
+VkInverse1DFFTImageFilter<TInputImage>::PrintSelf(std::ostream & os, Indent indent) const
+{
+  Superclass::PrintSelf(os, indent);
+  os << indent << "DeviceID: " << m_DeviceID << std::endl;
+}
+
+template <typename TInputImage>
+typename VkInverse1DFFTImageFilter<TInputImage>::SizeValueType
+VkInverse1DFFTImageFilter<TInputImage>::GetSizeGreatestPrimeFactor() const
+{
+  return m_VkCommon.GetGreatestPrimeFactor();
+}
+
+} // end namespace itk
+
+#endif // _itkVkInverse1DFFTImageFilter_hxx
diff --git a/itk-module.cmake b/itk-module.cmake
index b93138b9..5c74aa87 100644
--- a/itk-module.cmake
+++ b/itk-module.cmake
@@ -21,6 +21,8 @@ itk_module(VkFFTBackend
   TEST_DEPENDS
     ITKTestKernel
     ITKMetaIO
+    ITKImageCompose
+    ITKImageIntensity
   DESCRIPTION
     "${DOCUMENTATION}"
   EXCLUDE_FROM_DEFAULT
diff --git a/src/itkVkCommon.cxx b/src/itkVkCommon.cxx
index a7eeb005..d27f3592 100644
--- a/src/itkVkCommon.cxx
+++ b/src/itkVkCommon.cxx
@@ -143,6 +143,10 @@ VkCommon::ConfigureBackend()
   {
     m_VkFFTConfiguration.doublePrecision = 1;
   }
+  for (size_t dim = 0; dim < 3; ++dim)
+  {
+    m_VkFFTConfiguration.omitDimension[dim] = m_VkParameters.omitDimension[dim];
+  }
   // if (m_VkParameters.P == HALF)
   //   m_VkFFTConfiguration.halfPrecision = 1;
   m_VkFFTConfiguration.normalize = m_VkParameters.normalized == NormalizationEnum::NORMALIZED ? 1 : 0;


Test patch:
diff --git a/.github/workflows/test-gpu.yml b/.github/workflows/test-gpu.yml
index 8befe81b..762bfa36 100644
--- a/.github/workflows/test-gpu.yml
+++ b/.github/workflows/test-gpu.yml
@@ -12,7 +12,7 @@ jobs:
             opencl-headers-git-tag: "v2021.04.29"
             opencl-version: 120
             vkfft-backend: 3
-            itk-git-tag: "c693dbc830859ea10d573301f075e121a4c6c581"
+            itk-git-tag: "b1a0a91614dc091f9a17c1e4727095c80e9c05ea"
             cmake-build-type: "MinSizeRel"
             platform-name: "ubuntu-nvidia-gpu"
             os: ubuntu-20.04
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index b5efb275..a9818c62 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -2,8 +2,13 @@ itk_module_test()
 
 set(VkFFTBackendTests
   itkVkComplexToComplexFFTImageFilterTest.cxx
+  itkVkComplexToComplex1DFFTImageFilterBaselineTest.cxx
+  itkVkComplexToComplex1DFFTImageFilterSizesTest.cxx
   itkVkForwardInverseFFTImageFilterTest.cxx
+  itkVkForwardInverse1DFFTImageFilterTest.cxx
+  itkVkForward1DFFTImageFilterBaselineTest.cxx
   itkVkHalfHermitianFFTImageFilterTest.cxx
+  itkVkInverse1DFFTImageFilterBaselineTest.cxx
   )
 
 include_directories(${VkFFTBackend_INCLUDE_DIRS})
@@ -21,11 +26,59 @@ CreateTestDriver(VkFFTBackend
    itkVkComplexToComplexFFTImageFilterTest
      ${ITK_TEST_OUTPUT_DIR}/itkVkComplexToComplexFFTImageFilterTestOutput.mha
    )
+   
+ itk_add_test(NAME itkVkComplexToComplex1DFFTImageFilterSizesTest
+   COMMAND VkFFTBackendTestDriver
+     --compare
+     DATA{Baseline/itkVkComplexToComplexFFTImageFilterTestOutput.mha}
+     ${ITK_TEST_OUTPUT_DIR}/itkVkComplexToComplex1DFFTImageFilterSizesTestOutput.mha
+   itkVkComplexToComplex1DFFTImageFilterSizesTest
+     ${ITK_TEST_OUTPUT_DIR}/itkVkComplexToComplex1DFFTImageFilterSizesTestOutput.mha
+   )
+
+   itk_add_test(NAME itkVkComplexToComplex1DFFTImageFilterBaselineTest
+  COMMAND VkFFTBackendTestDriver
+  --compare
+    DATA{Input/TreeBarkTexture.png}
+    ${ITK_TEST_OUTPUT_DIR}/itkVkComplexToComplex1DFFTImageFilterBaselineTestOutput.mha
+  itkVkComplexToComplex1DFFTImageFilterBaselineTest
+      DATA{Input/itkForward1DFFTImageFilterTestBaselineRealFull.mhd,itkForward1DFFTImageFilterTestBaselineRealFull.raw}
+      DATA{Input/itkForward1DFFTImageFilterTestBaselineImaginaryFull.mhd,itkForward1DFFTImageFilterTestBaselineImaginaryFull.raw}
+    ${ITK_TEST_OUTPUT_DIR}/itkVkComplexToComplex1DFFTImageFilterBaselineTestOutput.mha
+    )
+
+itk_add_test(NAME itkVkForward1DFFTImageFilterBaselineTest
+  COMMAND VkFFTBackendTestDriver
+  --compare
+    DATA{Input/itkForward1DFFTImageFilterTestBaselineRealFull.mhd,itkForward1DFFTImageFilterTestBaselineRealFull.raw}
+    ${ITK_TEST_OUTPUT_DIR}/itkVkForward1DFFTImageFilterTestBaselineOutputReal.mha
+  --compare
+    DATA{Input/itkForward1DFFTImageFilterTestBaselineImaginaryFull.mhd,itkForward1DFFTImageFilterTestBaselineImaginaryFull.raw}
+    ${ITK_TEST_OUTPUT_DIR}/itkVkForward1DFFTImageFilterTestBaselineOutputImaginary.mha
+  itkVkForward1DFFTImageFilterBaselineTest
+    DATA{Input/TreeBarkTexture.png}
+    ${ITK_TEST_OUTPUT_DIR}/itkVkForward1DFFTImageFilterTestBaselineOutput
+    )
+    
+itk_add_test( NAME itkVkInverse1DFFTImageFilterBaselineTest
+  COMMAND VkFFTBackendTestDriver
+  --compare
+    DATA{Input/TreeBarkTexture.png}
+    ${ITK_TEST_OUTPUT_DIR}/itkVkInverse1DFFTImageFilterBaselineTest.mhd
+  itkVkInverse1DFFTImageFilterBaselineTest
+    DATA{Input/itkForward1DFFTImageFilterTestBaselineRealFull.mhd,itkForward1DFFTImageFilterTestBaselineRealFull.raw}
+    DATA{Input/itkForward1DFFTImageFilterTestBaselineImaginaryFull.mhd,itkForward1DFFTImageFilterTestBaselineImaginaryFull.raw}
+    ${ITK_TEST_OUTPUT_DIR}/itkVkInverse1DFFTImageFilterBaselineTest.mhd
+    )
 
  itk_add_test(NAME itkVkForwardInverseFFTImageFilterTest
    COMMAND VkFFTBackendTestDriver itkVkForwardInverseFFTImageFilterTest
    )
 
+itk_add_test(NAME itkVkForwardInverse1DFFTImageFilterTest
+  COMMAND VkFFTBackendTestDriver itkVkForwardInverse1DFFTImageFilterTest
+  )
+
  itk_add_test(NAME itkVkHalfHermitianFFTImageFilterTest
    COMMAND VkFFTBackendTestDriver itkVkHalfHermitianFFTImageFilterTest
    )
diff --git a/test/Input/TreeBarkTexture.png.sha512 b/test/Input/TreeBarkTexture.png.sha512
new file mode 100644
index 00000000..6d0f2699
--- /dev/null
+++ b/test/Input/TreeBarkTexture.png.sha512
@@ -0,0 +1,1 @@
+0b2ebe903f15a4693d08875e78b1d44c9e844339e1bcdb05b4f2ef02d3afd4b97b5329fbc3bd29c9270d10429099199e69995366707ea3674ba044f878a60501
diff --git a/test/Input/itkForward1DFFTImageFilterTestBaselineImaginaryFull.mhd.sha512 b/test/Input/itkForward1DFFTImageFilterTestBaselineImaginaryFull.mhd.sha512
new file mode 100644
index 00000000..6e08f698
--- /dev/null
+++ b/test/Input/itkForward1DFFTImageFilterTestBaselineImaginaryFull.mhd.sha512
@@ -0,0 +1,1 @@
+9b49bb5ee6b5dcd8942bc8c61165830b9850e0774405adef6793ec3416bd6e434aff9ada9a366f890b4137eb58eddcdc86648594072273f7c4a6f024898d9db2
diff --git a/test/Input/itkForward1DFFTImageFilterTestBaselineImaginaryFull.raw.sha512 b/test/Input/itkForward1DFFTImageFilterTestBaselineImaginaryFull.raw.sha512
new file mode 100644
index 00000000..8e0bc79a
--- /dev/null
+++ b/test/Input/itkForward1DFFTImageFilterTestBaselineImaginaryFull.raw.sha512
@@ -0,0 +1,1 @@
+a1942aa327ac7e508c695b04207c7c5aaae507745047c3b0ff37a94eadc228ff7200e6e5e4d1e5678ffa799117de04b0cb026d52ed9d7973ad06920404827cfd
diff --git a/test/Input/itkForward1DFFTImageFilterTestBaselineRealFull.mhd.sha512 b/test/Input/itkForward1DFFTImageFilterTestBaselineRealFull.mhd.sha512
new file mode 100644
index 00000000..ffc75871
--- /dev/null
+++ b/test/Input/itkForward1DFFTImageFilterTestBaselineRealFull.mhd.sha512
@@ -0,0 +1,1 @@
+7eb2848d40d26cf6e3489b3bf6db8c19754c703f2cbd761baa2f23483c0e5c97dfc81714968fe9e487a3da8964bf610c29de8512b1ce7d1da24fbcd7a60b5cea
diff --git a/test/Input/itkForward1DFFTImageFilterTestBaselineRealFull.raw.sha512 b/test/Input/itkForward1DFFTImageFilterTestBaselineRealFull.raw.sha512
new file mode 100644
index 00000000..3977008c
--- /dev/null
+++ b/test/Input/itkForward1DFFTImageFilterTestBaselineRealFull.raw.sha512
@@ -0,0 +1,1 @@
+c48c35de9c6444ce061916d08652fa5fa30fdd92eec8d77468ee1000faa1c40a20190f4d033fd1b8a03f3fc2b991cc10a5a25416d2d85e12da959ab2e99b3e24
diff --git a/test/itkVkComplexToComplex1DFFTImageFilterBaselineTest.cxx b/test/itkVkComplexToComplex1DFFTImageFilterBaselineTest.cxx
new file mode 100644
index 00000000..9e6ea481
--- /dev/null
+++ b/test/itkVkComplexToComplex1DFFTImageFilterBaselineTest.cxx
@@ -0,0 +1,89 @@
+/*=========================================================================
+ *
+ *  Copyright NumFOCUS
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0.txt
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *=========================================================================*/
+
+#include <complex>
+#include <string>
+
+#include "itkComplexToRealImageFilter.h"
+#include "itkComposeImageFilter.h"
+#include "itkImageFileReader.h"
+#include "itkImageFileWriter.h"
+
+#include "itkVkComplexToComplex1DFFTImageFilter.h"
+#include "itkTestingMacros.h"
+
+template <typename FFTType>
+int
+doTest(const char * inputRealFullImage, const char * inputImaginaryFullImage, const char * outputImage)
+{
+  using ComplexImageType = typename FFTType::InputImageType;
+  using ImageType = itk::Image<typename itk::NumericTraits<typename ComplexImageType::PixelType>::ValueType,
+                               ComplexImageType::ImageDimension>;
+
+  using ReaderType = itk::ImageFileReader<ImageType>;
+  using JoinFilterType = itk::ComposeImageFilter<ImageType, ComplexImageType>;
+  using ToRealFilterType = itk::ComplexToRealImageFilter<ComplexImageType, ImageType>;
+  using WriterType = itk::ImageFileWriter<ImageType>;
+
+  typename ReaderType::Pointer       readerReal = ReaderType::New();
+  typename ReaderType::Pointer       readerImag = ReaderType::New();
+  typename FFTType::Pointer          fft = FFTType::New();
+  typename JoinFilterType::Pointer   joinFilter = JoinFilterType::New();
+  typename ToRealFilterType::Pointer toReal = ToRealFilterType::New();
+  typename WriterType::Pointer       writer = WriterType::New();
+
+  readerReal->SetFileName(inputRealFullImage);
+  readerImag->SetFileName(inputImaginaryFullImage);
+  joinFilter->SetInput1(readerReal->GetOutput());
+  joinFilter->SetInput2(readerImag->GetOutput());
+  fft->SetTransformDirection(FFTType::INVERSE);
+  fft->SetInput(joinFilter->GetOutput());
+  toReal->SetInput(fft->GetOutput());
+  writer->SetInput(toReal->GetOutput());
+  writer->SetFileName(outputImage);
+
+  ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update());
+
+  fft.Print(std::cout);
+
+  return EXIT_SUCCESS;
+}
+
+int
+itkVkComplexToComplex1DFFTImageFilterBaselineTest(int argc, char * argv[])
+{
+  if (argc < 3)
+  {
+    std::cerr << "Missing Parameters." << std::endl;
+    std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv);
+    std::cerr << " inputImageRealFull inputImageImaginaryFull outputImage" << std::endl;
+    std::cerr << std::flush;
+    return EXIT_FAILURE;
+  }
+
+  using PixelType = double;
+  const unsigned int Dimension = 2;
+  using ComplexImageType = itk::Image<std::complex<PixelType>, Dimension>;
+  using FFTInverseType = itk::VkComplexToComplex1DFFTImageFilter<ComplexImageType>;
+
+  // Instantiate a filter to exercise basic object methods
+  typename FFTInverseType::Pointer fft = FFTInverseType::New();
+  ITK_EXERCISE_BASIC_OBJECT_METHODS(fft, VkComplexToComplex1DFFTImageFilter, ComplexToComplex1DFFTImageFilter);
+
+  return doTest<FFTInverseType>(argv[1], argv[2], argv[3]);
+}
diff --git a/test/itkVkComplexToComplex1DFFTImageFilterSizesTest.cxx b/test/itkVkComplexToComplex1DFFTImageFilterSizesTest.cxx
new file mode 100644
index 00000000..9ed3208d
--- /dev/null
+++ b/test/itkVkComplexToComplex1DFFTImageFilterSizesTest.cxx
@@ -0,0 +1,203 @@
+/*=========================================================================
+ *
+ *  Copyright NumFOCUS
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0.txt
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *=========================================================================*/
+
+#include "itkVkComplexToComplex1DFFTImageFilter.h"
+
+#include <complex.h>
+
+#include "itkCommand.h"
+#include "itkImageFileWriter.h"
+#include "itkTestingMacros.h"
+
+namespace
+{
+class ShowProgress : public itk::Command
+{
+public:
+  itkNewMacro(ShowProgress);
+
+  void
+  Execute(itk::Object * caller, const itk::EventObject & event) override
+  {
+    Execute((const itk::Object *)caller, event);
+  }
+
+  void
+  Execute(const itk::Object * caller, const itk::EventObject & event) override
+  {
+    if (!itk::ProgressEvent().CheckEvent(&event))
+    {
+      return;
+    }
+    const auto * processObject = dynamic_cast<const itk::ProcessObject *>(caller);
+    if (!processObject)
+    {
+      return;
+    }
+    std::cout << " " << processObject->GetProgress();
+  }
+};
+} // namespace
+
+int
+itkVkComplexToComplex1DFFTImageFilterSizesTest(int argc, char * argv[])
+{
+  int testNumber{ 0 };
+  {
+    if (argc != 2)
+    {
+      std::cerr << "Missing parameters." << std::endl;
+      std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv);
+      std::cerr << " outputImage";
+      std::cerr << std::endl;
+      return EXIT_FAILURE;
+    }
+    const char * outputImageFileName = argv[1];
+
+    constexpr unsigned int Dimension{ 2 };
+    using ComplexType = std::complex<double>;
+    using ComplexImageType = itk::Image<ComplexType, Dimension>;
+
+    using FilterType = itk::VkComplexToComplex1DFFTImageFilter<ComplexImageType>;
+    typename FilterType::Pointer filter{ FilterType::New() };
+    filter->SetDeviceID(0);
+
+    ITK_EXERCISE_BASIC_OBJECT_METHODS(filter, VkComplexToComplex1DFFTImageFilter, ComplexToComplex1DFFTImageFilter);
+
+    // Create input image to avoid test dependencies.
+    typename ComplexImageType::SizeType size;
+    size.Fill(128);
+    typename ComplexImageType::Pointer image{ ComplexImageType::New() };
+    image->SetRegions(size);
+    image->Allocate();
+
+    typename ShowProgress::Pointer showProgress{ ShowProgress::New() };
+
+    for (int i = 0; i < 2; ++i)
+    {
+      image->FillBuffer(1.2f);
+      image->FillBuffer(1.1f);
+      filter->AddObserver(itk::ProgressEvent(), showProgress);
+      filter->SetInput(image);
+
+      using WriterType = itk::ImageFileWriter<ComplexImageType>;
+      typename WriterType::Pointer writer{ WriterType::New() };
+      writer->SetFileName(outputImageFileName);
+      writer->SetInput(filter->GetOutput());
+      writer->SetUseCompression(true);
+
+      ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update());
+      std::cout << std::endl << "Test " << ++testNumber << "... passed." << std::endl;
+    }
+  }
+
+  bool testsPassed{ true };
+  {
+    constexpr unsigned int Dimension{ 1 };
+    using RealType = float;
+    using ComplexType = std::complex<RealType>;
+    using ComplexImageType = itk::Image<ComplexType, Dimension>;
+    typename ComplexImageType::SizeType size;
+    // Skip trivial case where 1D image of size 1 fails.
+    for (int mySize = 2; mySize <= 20; ++mySize)
+    {
+      // We expect that anything evenly divisible by a prime number greater than 13
+      // will succeed with Bluestein's Algorithm implementation in VkFFT, though
+      // with less precision.
+
+      float valueTolerance = (mySize == 17 || mySize == 19) ? 1e-5 : 1e-6;
+
+      size.Fill(0);
+      size[0] = mySize;
+      typename ComplexImageType::Pointer image{ ComplexImageType::New() };
+      image->SetRegions(size);
+      image->Allocate();
+      const ComplexType zeroValue{ 0.0, 0.0 };
+      image->FillBuffer(zeroValue);
+      typename ComplexImageType::IndexType index;
+      index.Fill(0);
+      const ComplexType someValue{ 1.23, 4.567 };
+      image->SetPixel(index, someValue);
+
+      using FilterType = itk::VkComplexToComplex1DFFTImageFilter<ComplexImageType>;
+      typename FilterType::Pointer filter{ FilterType::New() };
+      filter->SetDeviceID(0);
+      filter->SetInput(image);
+
+      ITK_TRY_EXPECT_NO_EXCEPTION(filter->Update());
+      typename ComplexImageType::Pointer        output{ filter->GetOutput() };
+      bool                                      thisTestPassed{ true };
+      const typename ComplexImageType::SizeType outputSize{ output->GetLargestPossibleRegion().GetSize() };
+      if (outputSize[0] != mySize)
+      {
+        thisTestPassed = false;
+      }
+      for (int i = 0; i < mySize; ++i)
+      {
+        index[0] = i;
+
+        if (std::abs(output->GetPixel(index) - someValue) > valueTolerance)
+        {
+          thisTestPassed = false;
+        }
+      }
+      std::cout << "Test " << ++testNumber << " (forward, size=" << mySize << ") ... "
+                << (thisTestPassed ? "passed." : "failed.") << std::endl;
+      testsPassed &= thisTestPassed;
+
+      filter->SetTransformDirection(FilterType::TransformDirectionType::INVERSE);
+      filter->SetInput(output);
+      filter->Update();
+      typename ComplexImageType::Pointer output2{ filter->GetOutput() };
+      thisTestPassed = true;
+      const typename ComplexImageType::SizeType output2Size{ output2->GetLargestPossibleRegion().GetSize() };
+      if (output2Size[0] != mySize)
+      {
+        std::cout << "Size is " << output2Size[0] << " but should be " << mySize << "." << std::endl;
+        thisTestPassed = false;
+      }
+      index[0] = 0;
+      if (std::abs(output2->GetPixel(index) - someValue) > valueTolerance)
+      {
+        std::cout << "|difference| = " << std::abs(output2->GetPixel(index) - someValue) << std::endl;
+        thisTestPassed = false;
+      }
+      for (int i = 1; i < mySize; ++i)
+      {
+        index[0] = i;
+        if (std::abs(output2->GetPixel(index) - zeroValue) > valueTolerance)
+        {
+          std::cout << "|difference| = " << std::abs(output2->GetPixel(index) - zeroValue) << std::endl;
+          thisTestPassed = false;
+        }
+      }
+      std::cout << std::endl;
+      std::cout << "Test " << ++testNumber << " (inverse, size=" << mySize << ") ... "
+                << (thisTestPassed ? "passed." : "failed.") << std::endl;
+      testsPassed &= thisTestPassed;
+    }
+  }
+
+  if (testsPassed)
+  {
+    std::cout << "All tests passed." << std::endl;
+    return EXIT_SUCCESS;
+  }
+
+  return EXIT_FAILURE;
+}
diff --git a/test/itkVkForward1DFFTImageFilterBaselineTest.cxx b/test/itkVkForward1DFFTImageFilterBaselineTest.cxx
new file mode 100644
index 00000000..6fa0fd2a
--- /dev/null
+++ b/test/itkVkForward1DFFTImageFilterBaselineTest.cxx
@@ -0,0 +1,90 @@
+/*=========================================================================
+ *
+ *  Copyright NumFOCUS
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0.txt
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *=========================================================================*/
+
+#include <complex>
+#include <string>
+
+#include "itkComplexToImaginaryImageFilter.h"
+#include "itkComplexToRealImageFilter.h"
+#include "itkImageFileReader.h"
+#include "itkImageFileWriter.h"
+
+#include "itkVkForward1DFFTImageFilter.h"
+#include "itkTestingMacros.h"
+
+template <typename FFTType>
+int
+doTest(const char * inputImage, const char * outputImagePrefix)
+{
+  using ImageType = typename FFTType::InputImageType;
+  using ComplexImageType = typename FFTType::OutputImageType;
+
+  using ReaderType = itk::ImageFileReader<ImageType>;
+  using RealFilterType = itk::ComplexToRealImageFilter<ComplexImageType, ImageType>;
+  using ImaginaryFilterType = itk::ComplexToImaginaryImageFilter<ComplexImageType, ImageType>;
+  using WriterType = itk::ImageFileWriter<ImageType>;
+
+  typename ReaderType::Pointer          reader = ReaderType::New();
+  typename FFTType::Pointer             fft = FFTType::New();
+  typename RealFilterType::Pointer      realFilter = RealFilterType::New();
+  typename ImaginaryFilterType::Pointer imaginaryFilter = ImaginaryFilterType::New();
+  typename WriterType::Pointer          writer = WriterType::New();
+
+  reader->SetFileName(inputImage);
+  fft->SetInput(reader->GetOutput());
+  realFilter->SetInput(fft->GetOutput());
+  imaginaryFilter->SetInput(fft->GetOutput());
+
+  writer->SetInput(realFilter->GetOutput());
+  writer->SetFileName(std::string(outputImagePrefix) + "Real.mha");
+
+  ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update());
+
+  writer->SetInput(imaginaryFilter->GetOutput());
+  writer->SetFileName(std::string(outputImagePrefix) + "Imaginary.mha");
+
+  ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update());
+
+  return EXIT_SUCCESS;
+}
+
+int
+itkVkForward1DFFTImageFilterBaselineTest(int argc, char * argv[])
+{
+  if (argc < 3)
+  {
+    std::cerr << "Missing Parameters." << std::endl;
+    std::cerr << "Usage: " << argv[0];
+    std::cerr << " inputImage outputImagePrefix [backend]" << std::endl;
+    std::cerr << std::flush;
+    return EXIT_FAILURE;
+  }
+
+  using PixelType = double;
+  const unsigned int Dimension = 2;
+
+  using ImageType = itk::Image<PixelType, Dimension>;
+  using FFTForwardType = itk::VkForward1DFFTImageFilter<ImageType>;
+  using ComplexImageType = typename FFTForwardType::OutputImageType;
+
+  // Instantiate a filter to exercise basic object methods
+  typename FFTForwardType::Pointer fft = FFTForwardType::New();
+  ITK_EXERCISE_BASIC_OBJECT_METHODS(fft, VkForward1DFFTImageFilter, Forward1DFFTImageFilter);
+
+  return doTest<FFTForwardType>(argv[1], argv[2]);
+}
diff --git a/test/itkVkForwardInverse1DFFTImageFilterTest.cxx b/test/itkVkForwardInverse1DFFTImageFilterTest.cxx
new file mode 100644
index 00000000..dfa13682
--- /dev/null
+++ b/test/itkVkForwardInverse1DFFTImageFilterTest.cxx
@@ -0,0 +1,188 @@
+/*=========================================================================
+ *
+ *  Copyright NumFOCUS
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0.txt
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *=========================================================================*/
+
+#include "itkVkForward1DFFTImageFilter.h"
+#include "itkVkInverse1DFFTImageFilter.h"
+
+#include "itkCommand.h"
+#include "itkImageFileWriter.h"
+#include "itkTestingMacros.h"
+
+namespace
+{
+class ShowProgress : public itk::Command
+{
+public:
+  itkNewMacro(ShowProgress);
+
+  void
+  Execute(itk::Object * caller, const itk::EventObject & event) override
+  {
+    Execute((const itk::Object *)caller, event);
+  }
+
+  void
+  Execute(const itk::Object * caller, const itk::EventObject & event) override
+  {
+    if (!itk::ProgressEvent().CheckEvent(&event))
+    {
+      return;
+    }
+    const auto * processObject = dynamic_cast<const itk::ProcessObject *>(caller);
+    if (!processObject)
+    {
+      return;
+    }
+    std::cout << " " << processObject->GetProgress();
+  }
+};
+} // namespace
+
+int
+itkVkForwardInverse1DFFTImageFilterTest(int argc, char * argv[])
+{
+  int  testNumber{ 0 };
+  bool testsPassed{ true };
+  {
+    if (argc != 1)
+    {
+      std::cerr << "Missing parameters." << std::endl;
+      std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv);
+      std::cerr << std::endl;
+      return EXIT_FAILURE;
+    }
+
+    constexpr unsigned int Dimension{ 1 };
+    using RealType = float;
+    using ComplexType = std::complex<RealType>;
+    using RealImageType = itk::Image<RealType, Dimension>;
+    using ComplexImageType = itk::Image<ComplexType, Dimension>;
+    typename RealImageType::SizeType  size;
+    typename RealImageType::IndexType index;
+    bool                              firstPass{ true };
+
+    // Skip trivial case where 1D image of size 1 fails.
+    for (int mySize = 2; mySize <= 20; ++mySize)
+    {
+      // We expect that anything evenly divisible by a prime number greater than 13
+      // will succeed with Bluestein's Algorithm implementation in VkFFT, though
+      // with less precision.
+      float valueTolerance = (mySize == 17 || mySize == 19) ? 1e-5 : 1e-6;
+
+      size.Fill(0);
+      size[0] = mySize;
+
+      typename RealImageType::Pointer realImage{ RealImageType::New() };
+      realImage->SetRegions(size);
+      realImage->Allocate();
+      const RealType realZeroValue{ 0.0 };
+      realImage->FillBuffer(realZeroValue);
+      index.Fill(0);
+      const RealType realSomeValue{ 4.567 };
+      realImage->SetPixel(index, realSomeValue);
+      using ForwardFilterType = itk::VkForward1DFFTImageFilter<RealImageType>;
+      typename ForwardFilterType::Pointer forwardFilter{ ForwardFilterType::New() };
+      if (firstPass)
+      {
+        ITK_EXERCISE_BASIC_OBJECT_METHODS(forwardFilter, VkForward1DFFTImageFilter, Forward1DFFTImageFilter);
+      }
+      forwardFilter->SetDeviceID(0);
+      forwardFilter->SetInput(realImage);
+
+      typename ComplexImageType::Pointer complexImage{ ComplexImageType::New() };
+      complexImage->SetRegions(size);
+      complexImage->Allocate();
+      const ComplexType complexZeroValue{ 0.0, 0.0 };
+      complexImage->FillBuffer(complexZeroValue);
+      index.Fill(0);
+      const ComplexType complexSomeValue{ 4.567, 0.0 };
+      complexImage->SetPixel(index, complexSomeValue);
+      using InverseFilterType = itk::VkInverse1DFFTImageFilter<ComplexImageType>;
+      typename InverseFilterType::Pointer inverseFilter{ InverseFilterType::New() };
+      if (firstPass)
+      {
+        ITK_EXERCISE_BASIC_OBJECT_METHODS(inverseFilter, VkInverse1DFFTImageFilter, Inverse1DFFTImageFilter);
+      }
+      inverseFilter->SetDeviceID(0);
+      inverseFilter->SetInput(complexImage);
+
+        bool thisTestPassed{ true };
+        forwardFilter->Update();
+        typename ComplexImageType::Pointer        output{ forwardFilter->GetOutput() };
+        const typename ComplexImageType::SizeType outputSize{ output->GetLargestPossibleRegion().GetSize() };
+        if (outputSize[0] != mySize)
+        {
+          std::cout << "Size is " << outputSize[0] << " but should be " << mySize << "." << std::endl;
+          thisTestPassed = false;
+        }
+        for (int i = 0; i < mySize; ++i)
+        {
+          index[0] = i;
+          if (std::abs(output->GetPixel(index) - complexSomeValue) > valueTolerance)
+          {
+            std::cout << output->GetPixel(index)
+                      << ": |difference| = " << std::abs(output->GetPixel(index) - complexSomeValue) << std::endl;
+            thisTestPassed = false;
+          }
+        }
+        std::cout << "Test " << ++testNumber << " (forward, size=" << mySize << ") ... "
+                  << (thisTestPassed ? "passed." : "failed.") << std::endl;
+        testsPassed &= thisTestPassed;
+
+        thisTestPassed = true;
+        inverseFilter->SetInput(output);
+        inverseFilter->Update();
+        typename RealImageType::Pointer        output2{ inverseFilter->GetOutput() };
+        const typename RealImageType::SizeType output2Size{ output2->GetLargestPossibleRegion().GetSize() };
+        if (output2Size[0] != mySize)
+        {
+          std::cout << "Size is " << output2Size[0] << " but should be " << mySize << "." << std::endl;
+          thisTestPassed = false;
+        }
+        index[0] = 0;
+        if (std::abs(output2->GetPixel(index) - realSomeValue) > valueTolerance)
+        {
+          std::cout << output2->GetPixel(index)
+                    << ": |difference| = " << std::abs(output2->GetPixel(index) - realSomeValue) << std::endl;
+          thisTestPassed = false;
+        }
+        for (int i = 1; i < mySize; ++i)
+        {
+          index[0] = i;
+          if (std::abs(output2->GetPixel(index) - realZeroValue) > valueTolerance)
+          {
+            std::cout << output2->GetPixel(index)
+                      << ": |difference| = " << std::abs(output2->GetPixel(index) - realZeroValue) << std::endl;
+            thisTestPassed = false;
+          }
+        }
+        std::cout << "Test " << ++testNumber << " (inverse, size=" << mySize << ") ... "
+                  << (thisTestPassed ? "passed." : "failed.") << std::endl;
+        testsPassed &= thisTestPassed;
+      
+    }
+  }
+
+  if (testsPassed)
+  {
+    std::cout << "All tests passed." << std::endl;
+    return EXIT_SUCCESS;
+  }
+
+  return EXIT_FAILURE;
+}
diff --git a/test/itkVkInverse1DFFTImageFilterBaselineTest.cxx b/test/itkVkInverse1DFFTImageFilterBaselineTest.cxx
new file mode 100644
index 00000000..5a32c154
--- /dev/null
+++ b/test/itkVkInverse1DFFTImageFilterBaselineTest.cxx
@@ -0,0 +1,83 @@
+/*=========================================================================
+ *
+ *  Copyright NumFOCUS
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0.txt
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *=========================================================================*/
+
+#include <complex>
+#include <string>
+
+#include "itkComposeImageFilter.h"
+#include "itkImageFileReader.h"
+#include "itkImageFileWriter.h"
+
+#include "itkVkInverse1DFFTImageFilter.h"
+#include "itkTestingMacros.h"
+
+template <typename FFTType>
+int
+doTest(const char * inputRealFullImage, const char * inputImaginaryFullImage, const char * outputImage)
+{
+  using ImageType = typename FFTType::OutputImageType;
+  using ComplexImageType = typename FFTType::InputImageType;
+
+  using ReaderType = itk::ImageFileReader<ImageType>;
+  using JoinFilterType = itk::ComposeImageFilter<ImageType, ComplexImageType>;
+  using WriterType = itk::ImageFileWriter<ImageType>;
+
+  typename ReaderType::Pointer     readerReal = ReaderType::New();
+  typename ReaderType::Pointer     readerImag = ReaderType::New();
+  typename FFTType::Pointer        fft = FFTType::New();
+  typename JoinFilterType::Pointer joinFilter = JoinFilterType::New();
+  typename WriterType::Pointer     writer = WriterType::New();
+
+  readerReal->SetFileName(inputRealFullImage);
+  readerImag->SetFileName(inputImaginaryFullImage);
+  joinFilter->SetInput1(readerReal->GetOutput());
+  joinFilter->SetInput2(readerImag->GetOutput());
+  fft->SetInput(joinFilter->GetOutput());
+  writer->SetInput(fft->GetOutput());
+  writer->SetFileName(outputImage);
+
+  ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update());
+
+  return EXIT_SUCCESS;
+}
+
+int
+itkVkInverse1DFFTImageFilterBaselineTest(int argc, char * argv[])
+{
+  if (argc < 3)
+  {
+    std::cerr << "Missing Parameters." << std::endl;
+    std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv);
+    std::cerr << " inputImagePrefix outputImage [backend]" << std::endl;
+    std::cerr << std::flush;
+    return EXIT_FAILURE;
+  }
+
+  using PixelType = double;
+  const unsigned int Dimension = 2;
+
+  using ComplexImageType = itk::Image<std::complex<PixelType>, Dimension>;
+  using FFTInverseType = itk::VkInverse1DFFTImageFilter<ComplexImageType>;
+  using ImageType = typename FFTInverseType::OutputImageType;
+
+  // Instantiate a filter to exercise basic object methods
+  typename FFTInverseType::Pointer fft = FFTInverseType::New();
+  ITK_EXERCISE_BASIC_OBJECT_METHODS(fft, VkInverse1DFFTImageFilter, Inverse1DFFTImageFilter);
+
+  return doTest<FFTInverseType>(argv[1], argv[2], argv[3]);
+}