Repository: InsightSoftwareConsortium/ITKVkFFTBackend (language: python)

Issue:
ENH: Use object factory to instantiate FFT classes with VkFFT backend
Rather than specifically instantiating `itk::VkForwardFFTImageFilter`, etc, it would be useful for ITK to be able to pick up the VkFFT backend if it is available when instantiating `itk::ForwardFFTImageFilter`. The object factory can be used to make different backends available.

Steps:
- [ ] Add object factory support to ITK FFT filters (in progress)
- [ ] Add object factory support to ITK VkFFT filters

Split from #14 . 


Discussion / hints:

Fix patch:
diff --git a/include/itkVkComplexToComplex1DFFTImageFilter.h b/include/itkVkComplexToComplex1DFFTImageFilter.h
index 41157ae1..2a1848c0 100644
--- a/include/itkVkComplexToComplex1DFFTImageFilter.h
+++ b/include/itkVkComplexToComplex1DFFTImageFilter.h
@@ -19,6 +19,7 @@
 #ifndef itkVkComplexToComplex1DFFTImageFilter_h
 #define itkVkComplexToComplex1DFFTImageFilter_h
 
+#include "itkFFTImageFilterFactory.h"
 #include "itkVkCommon.h"
 #include "itkComplexToComplex1DFFTImageFilter.h"
 
@@ -43,22 +44,25 @@ namespace itk
  * \ingroup ITKFFT
  * \ingroup VkFFTBackend
  */
-template <typename TImage>
-class VkComplexToComplex1DFFTImageFilter : public ComplexToComplex1DFFTImageFilter<TImage>
+template <typename TInputImage, typename TOutputImage = TInputImage>
+class VkComplexToComplex1DFFTImageFilter : public ComplexToComplex1DFFTImageFilter<TInputImage, TOutputImage>
 {
 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,
+  using InputImageType = TInputImage;
+  using OutputImageType = TOutputImage;
+  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(TImage::ImageDimension >= 1 && TImage::ImageDimension <= 3, "Unsupported image dimension");
+  static_assert(std::is_same<typename TOutputImage::PixelType, std::complex<float>>::value ||
+                  std::is_same<typename TOutputImage::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 = VkComplexToComplex1DFFTImageFilter;
-  using Superclass = ComplexToComplex1DFFTImageFilter<TImage>;
+  using Superclass = ComplexToComplex1DFFTImageFilter<TInputImage, TOutputImage>;
   using Pointer = SmartPointer<Self>;
   using ConstPointer = SmartPointer<const Self>;
   using InputPixelType = typename InputImageType::PixelType;
@@ -99,6 +103,18 @@ class VkComplexToComplex1DFFTImageFilter : public ComplexToComplex1DFFTImageFilt
   VkCommon m_VkCommon{};
 };
 
+// Describe whether input/output are real- or complex-valued
+// for factory registration
+template <>
+struct FFTImageFilterTraits<VkComplexToComplex1DFFTImageFilter>
+{
+  template <typename TUnderlying>
+  using InputPixelType = std::complex<TUnderlying>;
+  template <typename TUnderlying>
+  using OutputPixelType = std::complex<TUnderlying>;
+  using FilterDimensions = std::integer_sequence<unsigned int, 3, 2, 1>;
+};
+
 } // namespace itk
 
 #ifndef ITK_MANUAL_INSTANTIATION
diff --git a/include/itkVkComplexToComplex1DFFTImageFilter.hxx b/include/itkVkComplexToComplex1DFFTImageFilter.hxx
index 7eb66096..7692ea1d 100644
--- a/include/itkVkComplexToComplex1DFFTImageFilter.hxx
+++ b/include/itkVkComplexToComplex1DFFTImageFilter.hxx
@@ -28,9 +28,9 @@
 
 namespace itk
 {
-template <typename TImage>
+template <typename TInputImage, typename TOutputImage>
 void
-VkComplexToComplex1DFFTImageFilter<TImage>::GenerateData()
+VkComplexToComplex1DFFTImageFilter<TInputImage, TOutputImage>::GenerateData()
 {
   // get pointers to the input and output
   const InputImageType * const input{ this->GetInput() };
@@ -107,17 +107,17 @@ VkComplexToComplex1DFFTImageFilter<TImage>::GenerateData()
   }
 }
 
-template <typename TImage>
+template <typename TInputImage, typename TOutputImage>
 void
-VkComplexToComplex1DFFTImageFilter<TImage>::PrintSelf(std::ostream & os, Indent indent) const
+VkComplexToComplex1DFFTImageFilter<TInputImage, TOutputImage>::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
+template <typename TInputImage, typename TOutputImage>
+typename VkComplexToComplex1DFFTImageFilter<TInputImage, TOutputImage>::SizeValueType
+VkComplexToComplex1DFFTImageFilter<TInputImage, TOutputImage>::GetSizeGreatestPrimeFactor() const
 {
   return m_VkCommon.GetGreatestPrimeFactor();
 }
diff --git a/include/itkVkComplexToComplexFFTImageFilter.h b/include/itkVkComplexToComplexFFTImageFilter.h
index 6ceea61c..eb5e60c8 100644
--- a/include/itkVkComplexToComplexFFTImageFilter.h
+++ b/include/itkVkComplexToComplexFFTImageFilter.h
@@ -19,6 +19,7 @@
 #ifndef itkVkComplexToComplexFFTImageFilter_h
 #define itkVkComplexToComplexFFTImageFilter_h
 
+#include "itkFFTImageFilterFactory.h"
 #include "itkVkCommon.h"
 #include "itkComplexToComplexFFTImageFilter.h"
 
@@ -40,22 +41,25 @@ namespace itk
  * \ingroup ITKFFT
  * \ingroup VkFFTBackend
  */
-template <typename TImage>
-class VkComplexToComplexFFTImageFilter : public ComplexToComplexFFTImageFilter<TImage>
+template <typename TInputImage, typename TOutputImage = TInputImage>
+class VkComplexToComplexFFTImageFilter : public ComplexToComplexFFTImageFilter<TInputImage, TOutputImage>
 {
 public:
   ITK_DISALLOW_COPY_AND_MOVE(VkComplexToComplexFFTImageFilter);
 
-  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,
+  using InputImageType = TInputImage;
+  using OutputImageType = TOutputImage;
+  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(TImage::ImageDimension >= 1 && TImage::ImageDimension <= 3, "Unsupported image dimension");
+  static_assert(std::is_same<typename TOutputImage::PixelType, std::complex<float>>::value ||
+                  std::is_same<typename TOutputImage::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 = VkComplexToComplexFFTImageFilter;
-  using Superclass = ComplexToComplexFFTImageFilter<TImage>;
+  using Superclass = ComplexToComplexFFTImageFilter<TInputImage>;
   using Pointer = SmartPointer<Self>;
   using ConstPointer = SmartPointer<const Self>;
   using InputPixelType = typename InputImageType::PixelType;
@@ -96,6 +100,17 @@ class VkComplexToComplexFFTImageFilter : public ComplexToComplexFFTImageFilter<T
   VkCommon m_VkCommon{};
 };
 
+// Describe whether input/output are real- or complex-valued
+// for factory registration
+template <>
+struct FFTImageFilterTraits<VkComplexToComplexFFTImageFilter>
+{
+  template <typename TUnderlying>
+  using InputPixelType = std::complex<TUnderlying>;
+  template <typename TUnderlying>
+  using OutputPixelType = std::complex<TUnderlying>;
+};
+
 } // namespace itk
 
 #ifndef ITK_MANUAL_INSTANTIATION
diff --git a/include/itkVkComplexToComplexFFTImageFilter.hxx b/include/itkVkComplexToComplexFFTImageFilter.hxx
index 85a9d0ca..04b42a91 100644
--- a/include/itkVkComplexToComplexFFTImageFilter.hxx
+++ b/include/itkVkComplexToComplexFFTImageFilter.hxx
@@ -29,13 +29,13 @@
 namespace itk
 {
 
-template <typename TImage>
-VkComplexToComplexFFTImageFilter<TImage>::VkComplexToComplexFFTImageFilter()
+template <typename TInputImage, typename TOutputImage>
+VkComplexToComplexFFTImageFilter<TInputImage, TOutputImage>::VkComplexToComplexFFTImageFilter()
 {}
 
-template <typename TImage>
+template <typename TInputImage, typename TOutputImage>
 void
-VkComplexToComplexFFTImageFilter<TImage>::GenerateData()
+VkComplexToComplexFFTImageFilter<TInputImage, TOutputImage>::GenerateData()
 {
   // get pointers to the input and output
   const InputImageType * const input{ this->GetInput() };
@@ -105,17 +105,17 @@ VkComplexToComplexFFTImageFilter<TImage>::GenerateData()
   }
 }
 
-template <typename TImage>
+template <typename TInputImage, typename TOutputImage>
 void
-VkComplexToComplexFFTImageFilter<TImage>::PrintSelf(std::ostream & os, Indent indent) const
+VkComplexToComplexFFTImageFilter<TInputImage, TOutputImage>::PrintSelf(std::ostream & os, Indent indent) const
 {
   Superclass::PrintSelf(os, indent);
   os << indent << "DeviceID: " << m_DeviceID << std::endl;
 }
 
-template <typename TImage>
-typename VkComplexToComplexFFTImageFilter<TImage>::SizeValueType
-VkComplexToComplexFFTImageFilter<TImage>::GetSizeGreatestPrimeFactor() const
+template <typename TInputImage, typename TOutputImage>
+typename VkComplexToComplexFFTImageFilter<TInputImage, TOutputImage>::SizeValueType
+VkComplexToComplexFFTImageFilter<TInputImage, TOutputImage>::GetSizeGreatestPrimeFactor() const
 {
   return m_VkCommon.GetGreatestPrimeFactor();
 }
diff --git a/include/itkVkForward1DFFTImageFilter.h b/include/itkVkForward1DFFTImageFilter.h
index 3ab78d75..7c975cba 100644
--- a/include/itkVkForward1DFFTImageFilter.h
+++ b/include/itkVkForward1DFFTImageFilter.h
@@ -18,6 +18,7 @@
 #ifndef itkVkForward1DFFTImageFilter_h
 #define itkVkForward1DFFTImageFilter_h
 
+#include "itkFFTImageFilterFactory.h"
 #include "itkForward1DFFTImageFilter.h"
 #include "itkVkCommon.h"
 
@@ -45,19 +46,22 @@ namespace itk
  * \sa VkGlobalConfiguration
  * \sa Forward1DFFTImageFilter
  */
-template <typename TInputImage>
+template <typename TInputImage,
+          typename TOutputImage = Image<std::complex<typename TInputImage::PixelType>, TInputImage::ImageDimension>>
 class VkForward1DFFTImageFilter
-  : public Forward1DFFTImageFilter<TInputImage,
-                                 Image<std::complex<typename TInputImage::PixelType>, TInputImage::ImageDimension>>
+  : public Forward1DFFTImageFilter<TInputImage, TOutputImage>
 {
 public:
   ITK_DISALLOW_COPY_AND_MOVE(VkForward1DFFTImageFilter);
 
   using InputImageType = TInputImage;
-  using OutputImageType = Image<std::complex<typename TInputImage::PixelType>, TInputImage::ImageDimension>;
+  using OutputImageType = TOutputImage;
   static_assert(std::is_same<typename TInputImage::PixelType, float>::value ||
                   std::is_same<typename TInputImage::PixelType, double>::value,
                 "Unsupported pixel type");
+  static_assert(std::is_same<typename TOutputImage::PixelType, std::complex<float>>::value ||
+                  std::is_same<typename TOutputImage::PixelType, std::complex<double>>::value,
+                "Unsupported pixel type");
   static_assert(TInputImage::ImageDimension >= 1 && TInputImage::ImageDimension <= 3, "Unsupported image dimension");
 
   /** Standard class type aliases. */
@@ -104,6 +108,17 @@ class VkForward1DFFTImageFilter
   VkCommon m_VkCommon{};
 };
 
+// Describe whether input/output are real- or complex-valued
+// for factory registration
+template <>
+struct FFTImageFilterTraits<VkForward1DFFTImageFilter>
+{
+  template <typename TUnderlying>
+  using InputPixelType = TUnderlying;
+  template <typename TUnderlying>
+  using OutputPixelType = std::complex<TUnderlying>;
+};
+
 } // namespace itk
 
 #ifndef ITK_MANUAL_INSTANTIATION
diff --git a/include/itkVkForward1DFFTImageFilter.hxx b/include/itkVkForward1DFFTImageFilter.hxx
index 968a3b85..4580584f 100644
--- a/include/itkVkForward1DFFTImageFilter.hxx
+++ b/include/itkVkForward1DFFTImageFilter.hxx
@@ -29,9 +29,9 @@
 
 namespace itk
 {
-template <typename TInputImage>
+template <typename TInputImage, typename TOutputImage>
 void
-VkForward1DFFTImageFilter<TInputImage>::GenerateData()
+VkForward1DFFTImageFilter<TInputImage, TOutputImage>::GenerateData()
 {
   // get pointers to the input and output
   const InputImageType * const input{ this->GetInput() };
@@ -103,17 +103,17 @@ VkForward1DFFTImageFilter<TInputImage>::GenerateData()
   }
 }
 
-template <typename TInputImage>
+template <typename TInputImage, typename TOutputImage>
 void
-VkForward1DFFTImageFilter<TInputImage>::PrintSelf(std::ostream & os, Indent indent) const
+VkForward1DFFTImageFilter<TInputImage, TOutputImage>::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
+template <typename TInputImage, typename TOutputImage>
+typename VkForward1DFFTImageFilter<TInputImage, TOutputImage>::SizeValueType
+VkForward1DFFTImageFilter<TInputImage, TOutputImage>::GetSizeGreatestPrimeFactor() const
 {
   return m_VkCommon.GetGreatestPrimeFactor();
 }
diff --git a/include/itkVkForwardFFTImageFilter.h b/include/itkVkForwardFFTImageFilter.h
index 34f3d0e4..b4ae9826 100644
--- a/include/itkVkForwardFFTImageFilter.h
+++ b/include/itkVkForwardFFTImageFilter.h
@@ -18,7 +18,9 @@
 #ifndef itkVkForwardFFTImageFilter_h
 #define itkVkForwardFFTImageFilter_h
 
+#include "itkFFTImageFilterFactory.h"
 #include "itkForwardFFTImageFilter.h"
+#include "itkImage.h"
 #include "itkVkCommon.h"
 
 namespace itk
@@ -42,19 +44,21 @@ namespace itk
  * \sa VkGlobalConfiguration
  * \sa ForwardFFTImageFilter
  */
-template <typename TInputImage>
-class VkForwardFFTImageFilter
-  : public ForwardFFTImageFilter<TInputImage,
-                                 Image<std::complex<typename TInputImage::PixelType>, TInputImage::ImageDimension>>
+template <typename TInputImage,
+          typename TOutputImage = Image<std::complex<typename TInputImage::PixelType>, TInputImage::ImageDimension>>
+class VkForwardFFTImageFilter : public ForwardFFTImageFilter<TInputImage, TOutputImage>
 {
 public:
   ITK_DISALLOW_COPY_AND_MOVE(VkForwardFFTImageFilter);
 
   using InputImageType = TInputImage;
-  using OutputImageType = Image<std::complex<typename TInputImage::PixelType>, TInputImage::ImageDimension>;
+  using OutputImageType = TOutputImage;
   static_assert(std::is_same<typename TInputImage::PixelType, float>::value ||
                   std::is_same<typename TInputImage::PixelType, double>::value,
                 "Unsupported pixel type");
+  static_assert(std::is_same<typename TOutputImage::PixelType, std::complex<float>>::value ||
+                  std::is_same<typename TOutputImage::PixelType, std::complex<double>>::value,
+                "Unsupported pixel type");
   static_assert(TInputImage::ImageDimension >= 1 && TInputImage::ImageDimension <= 3, "Unsupported image dimension");
 
   /** Standard class type aliases. */
@@ -101,6 +105,17 @@ class VkForwardFFTImageFilter
   VkCommon m_VkCommon{};
 };
 
+// Describe whether input/output are real- or complex-valued
+// for factory registration
+template <>
+struct FFTImageFilterTraits<VkForwardFFTImageFilter>
+{
+  template <typename TUnderlying>
+  using InputPixelType = TUnderlying;
+  template <typename TUnderlying>
+  using OutputPixelType = std::complex<TUnderlying>;
+};
+
 } // namespace itk
 
 #ifndef ITK_MANUAL_INSTANTIATION
diff --git a/include/itkVkForwardFFTImageFilter.hxx b/include/itkVkForwardFFTImageFilter.hxx
index bea1150a..78b816cb 100644
--- a/include/itkVkForwardFFTImageFilter.hxx
+++ b/include/itkVkForwardFFTImageFilter.hxx
@@ -30,13 +30,13 @@
 namespace itk
 {
 
-template <typename TInputImage>
-VkForwardFFTImageFilter<TInputImage>::VkForwardFFTImageFilter()
+template <typename TInputImage, typename TOutputImage>
+VkForwardFFTImageFilter<TInputImage, TOutputImage>::VkForwardFFTImageFilter()
 {}
 
-template <typename TInputImage>
+template <typename TInputImage, typename TOutputImage>
 void
-VkForwardFFTImageFilter<TInputImage>::GenerateData()
+VkForwardFFTImageFilter<TInputImage, TOutputImage>::GenerateData()
 {
   // get pointers to the input and output
   const InputImageType * const input{ this->GetInput() };
@@ -101,17 +101,17 @@ VkForwardFFTImageFilter<TInputImage>::GenerateData()
   }
 }
 
-template <typename TInputImage>
+template <typename TInputImage, typename TOutputImage>
 void
-VkForwardFFTImageFilter<TInputImage>::PrintSelf(std::ostream & os, Indent indent) const
+VkForwardFFTImageFilter<TInputImage, TOutputImage>::PrintSelf(std::ostream & os, Indent indent) const
 {
   Superclass::PrintSelf(os, indent);
   os << indent << "DeviceID: " << m_DeviceID << std::endl;
 }
 
-template <typename TInputImage>
-typename VkForwardFFTImageFilter<TInputImage>::SizeValueType
-VkForwardFFTImageFilter<TInputImage>::GetSizeGreatestPrimeFactor() const
+template <typename TInputImage, typename TOutputImage>
+typename VkForwardFFTImageFilter<TInputImage, TOutputImage>::SizeValueType
+VkForwardFFTImageFilter<TInputImage, TOutputImage>::GetSizeGreatestPrimeFactor() const
 {
   return m_VkCommon.GetGreatestPrimeFactor();
 }
diff --git a/include/itkVkHalfHermitianToRealInverseFFTImageFilter.h b/include/itkVkHalfHermitianToRealInverseFFTImageFilter.h
index 906056a2..8aaff86d 100644
--- a/include/itkVkHalfHermitianToRealInverseFFTImageFilter.h
+++ b/include/itkVkHalfHermitianToRealInverseFFTImageFilter.h
@@ -18,7 +18,9 @@
 #ifndef itkVkHalfHermitianToRealInverseFFTImageFilter_h
 #define itkVkHalfHermitianToRealInverseFFTImageFilter_h
 
+#include "itkFFTImageFilterFactory.h"
 #include "itkHalfHermitianToRealInverseFFTImageFilter.h"
+#include "itkImage.h"
 #include "itkVkCommon.h"
 
 namespace itk
@@ -42,20 +44,23 @@ namespace itk
  * \sa VkGlobalConfiguration
  * \sa HalfHermitianToRealInverseFFTImageFilter
  */
-template <typename TInputImage>
+template <typename TInputImage,
+          typename TOutputImage = Image<typename TInputImage::PixelType::value_type, TInputImage::ImageDimension>>
 class VkHalfHermitianToRealInverseFFTImageFilter
   : public HalfHermitianToRealInverseFFTImageFilter<
-      TInputImage,
-      Image<typename TInputImage::PixelType::value_type, TInputImage::ImageDimension>>
+      TInputImage, TOutputImage>
 {
 public:
   ITK_DISALLOW_COPY_AND_MOVE(VkHalfHermitianToRealInverseFFTImageFilter);
 
   using InputImageType = TInputImage;
-  using OutputImageType = Image<typename TInputImage::PixelType::value_type, TInputImage::ImageDimension>;
+  using OutputImageType = TOutputImage;
   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(std::is_same<typename TOutputImage::PixelType, float>::value ||
+                  std::is_same<typename TOutputImage::PixelType, double>::value,
+                "Unsupported pixel type");
   static_assert(TInputImage::ImageDimension >= 1 && TInputImage::ImageDimension <= 3, "Unsupported image dimension");
 
   /** Standard class type aliases. */
@@ -107,6 +112,17 @@ class VkHalfHermitianToRealInverseFFTImageFilter
   VkCommon m_VkCommon{};
 };
 
+// Describe whether input/output are real- or complex-valued
+// for factory registration
+template <>
+struct FFTImageFilterTraits<VkHalfHermitianToRealInverseFFTImageFilter>
+{
+  template <typename TUnderlying>
+  using InputPixelType = std::complex<TUnderlying>;
+  template <typename TUnderlying>
+  using OutputPixelType = TUnderlying;
+};
+
 } // namespace itk
 
 #ifndef ITK_MANUAL_INSTANTIATION
diff --git a/include/itkVkHalfHermitianToRealInverseFFTImageFilter.hxx b/include/itkVkHalfHermitianToRealInverseFFTImageFilter.hxx
index 2da53ef0..ddf7c463 100644
--- a/include/itkVkHalfHermitianToRealInverseFFTImageFilter.hxx
+++ b/include/itkVkHalfHermitianToRealInverseFFTImageFilter.hxx
@@ -30,13 +30,13 @@
 namespace itk
 {
 
-template <typename TInputImage>
-VkHalfHermitianToRealInverseFFTImageFilter<TInputImage>::VkHalfHermitianToRealInverseFFTImageFilter()
+template <typename TInputImage, typename TOutputImage>
+VkHalfHermitianToRealInverseFFTImageFilter<TInputImage, TOutputImage>::VkHalfHermitianToRealInverseFFTImageFilter()
 {}
 
-template <typename TInputImage>
+template <typename TInputImage, typename TOutputImage>
 void
-VkHalfHermitianToRealInverseFFTImageFilter<TInputImage>::GenerateData()
+VkHalfHermitianToRealInverseFFTImageFilter<TInputImage, TOutputImage>::GenerateData()
 {
   // get pointers to the input and output
   const InputImageType * const input{ this->GetInput() };
@@ -104,17 +104,17 @@ VkHalfHermitianToRealInverseFFTImageFilter<TInputImage>::GenerateData()
   }
 }
 
-template <typename TInputImage>
+template <typename TInputImage, typename TOutputImage>
 void
-VkHalfHermitianToRealInverseFFTImageFilter<TInputImage>::PrintSelf(std::ostream & os, Indent indent) const
+VkHalfHermitianToRealInverseFFTImageFilter<TInputImage, TOutputImage>::PrintSelf(std::ostream & os, Indent indent) const
 {
   Superclass::PrintSelf(os, indent);
   os << indent << "DeviceID: " << m_DeviceID << std::endl;
 }
 
-template <typename TInputImage>
-typename VkHalfHermitianToRealInverseFFTImageFilter<TInputImage>::SizeValueType
-VkHalfHermitianToRealInverseFFTImageFilter<TInputImage>::GetSizeGreatestPrimeFactor() const
+template <typename TInputImage, typename TOutputImage>
+typename VkHalfHermitianToRealInverseFFTImageFilter<TInputImage, TOutputImage>::SizeValueType
+VkHalfHermitianToRealInverseFFTImageFilter<TInputImage, TOutputImage>::GetSizeGreatestPrimeFactor() const
 {
   return m_VkCommon.GetGreatestPrimeFactor();
 }
diff --git a/include/itkVkInverse1DFFTImageFilter.h b/include/itkVkInverse1DFFTImageFilter.h
index ea3e9fbb..d80583f0 100644
--- a/include/itkVkInverse1DFFTImageFilter.h
+++ b/include/itkVkInverse1DFFTImageFilter.h
@@ -18,6 +18,8 @@
 #ifndef itkVkInverse1DFFTImageFilter_h
 #define itkVkInverse1DFFTImageFilter_h
 
+#include "itkFFTImageFilterFactory.h"
+#include "itkImage.h"
 #include "itkInverse1DFFTImageFilter.h"
 #include "itkVkCommon.h"
 
@@ -33,8 +35,8 @@ namespace itk
  *
  * 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 
+ *
+ * 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
@@ -45,19 +47,21 @@ namespace itk
  * \sa VkGlobalConfiguration
  * \sa Inverse1DFFTImageFilter
  */
-template <typename TInputImage>
-class VkInverse1DFFTImageFilter
-  : public Inverse1DFFTImageFilter<TInputImage,
-                                 Image<typename TInputImage::PixelType::value_type, TInputImage::ImageDimension>>
+template <typename TInputImage,
+          typename TOutputImage = Image<typename TInputImage::PixelType::value_type, TInputImage::ImageDimension>>
+class VkInverse1DFFTImageFilter : public Inverse1DFFTImageFilter<TInputImage, TOutputImage>
 {
 public:
   ITK_DISALLOW_COPY_AND_MOVE(VkInverse1DFFTImageFilter);
 
   using InputImageType = TInputImage;
-  using OutputImageType = Image<typename TInputImage::PixelType::value_type, TInputImage::ImageDimension>;
+  using OutputImageType = TOutputImage;
   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(std::is_same<typename TOutputImage::PixelType, float>::value ||
+                  std::is_same<typename TOutputImage::PixelType, double>::value,
+                "Unsupported pixel type");
   static_assert(TInputImage::ImageDimension >= 1 && TInputImage::ImageDimension <= 3, "Unsupported image dimension");
 
   /** Standard class type aliases. */
@@ -104,6 +108,17 @@ class VkInverse1DFFTImageFilter
   VkCommon m_VkCommon{};
 };
 
+// Describe whether input/output are real- or complex-valued
+// for factory registration
+template <>
+struct FFTImageFilterTraits<VkInverse1DFFTImageFilter>
+{
+  template <typename TUnderlying>
+  using InputPixelType = std::complex<TUnderlying>;
+  template <typename TUnderlying>
+  using OutputPixelType = TUnderlying;
+};
+
 } // namespace itk
 
 #ifndef ITK_MANUAL_INSTANTIATION
diff --git a/include/itkVkInverse1DFFTImageFilter.hxx b/include/itkVkInverse1DFFTImageFilter.hxx
index 76c80a93..71d06cfd 100644
--- a/include/itkVkInverse1DFFTImageFilter.hxx
+++ b/include/itkVkInverse1DFFTImageFilter.hxx
@@ -29,9 +29,9 @@
 
 namespace itk
 {
-template <typename TInputImage>
+template <typename TInputImage, typename TOutputImage>
 void
-VkInverse1DFFTImageFilter<TInputImage>::GenerateData()
+VkInverse1DFFTImageFilter<TInputImage, TOutputImage>::GenerateData()
 {
   // get pointers to the input and output
   const InputImageType * const input{ this->GetInput() };
@@ -103,17 +103,17 @@ VkInverse1DFFTImageFilter<TInputImage>::GenerateData()
   }
 }
 
-template <typename TInputImage>
+template <typename TInputImage, typename TOutputImage>
 void
-VkInverse1DFFTImageFilter<TInputImage>::PrintSelf(std::ostream & os, Indent indent) const
+VkInverse1DFFTImageFilter<TInputImage, TOutputImage>::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
+template <typename TInputImage, typename TOutputImage>
+typename VkInverse1DFFTImageFilter<TInputImage, TOutputImage>::SizeValueType
+VkInverse1DFFTImageFilter<TInputImage, TOutputImage>::GetSizeGreatestPrimeFactor() const
 {
   return m_VkCommon.GetGreatestPrimeFactor();
 }
diff --git a/include/itkVkInverseFFTImageFilter.h b/include/itkVkInverseFFTImageFilter.h
index 1a4160d0..5b5a3ef4 100644
--- a/include/itkVkInverseFFTImageFilter.h
+++ b/include/itkVkInverseFFTImageFilter.h
@@ -18,6 +18,8 @@
 #ifndef itkVkInverseFFTImageFilter_h
 #define itkVkInverseFFTImageFilter_h
 
+#include "itkFFTImageFilterFactory.h"
+#include "itkImage.h"
 #include "itkInverseFFTImageFilter.h"
 #include "itkVkCommon.h"
 
@@ -42,19 +44,23 @@ namespace itk
  * \sa VkGlobalConfiguration
  * \sa InverseFFTImageFilter
  */
-template <typename TInputImage>
-class VkInverseFFTImageFilter
-  : public InverseFFTImageFilter<TInputImage,
-                                 Image<typename TInputImage::PixelType::value_type, TInputImage::ImageDimension>>
+template <typename TInputImage,
+          typename TOutputImage = Image<typename TInputImage::PixelType::value_type, TInputImage::ImageDimension>>
+    class VkInverseFFTImageFilter
+  : public InverseFFTImageFilter<TInputImage,TOutputImage>
+                                 
 {
 public:
   ITK_DISALLOW_COPY_AND_MOVE(VkInverseFFTImageFilter);
 
   using InputImageType = TInputImage;
-  using OutputImageType = Image<typename TInputImage::PixelType::value_type, TInputImage::ImageDimension>;
+  using OutputImageType = TOutputImage;
   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(std::is_same<typename TOutputImage::PixelType, float>::value ||
+                  std::is_same<typename TOutputImage::PixelType, double>::value,
+                "Unsupported pixel type");
   static_assert(TInputImage::ImageDimension >= 1 && TInputImage::ImageDimension <= 3, "Unsupported image dimension");
 
   /** Standard class type aliases. */
@@ -101,6 +107,17 @@ class VkInverseFFTImageFilter
   VkCommon m_VkCommon{};
 };
 
+// Describe whether input/output are real- or complex-valued
+// for factory registration
+template <>
+struct FFTImageFilterTraits<VkInverseFFTImageFilter>
+{
+  template <typename TUnderlying>
+  using InputPixelType = std::complex<TUnderlying>;
+  template <typename TUnderlying>
+  using OutputPixelType = TUnderlying;
+};
+
 } // namespace itk
 
 #ifndef ITK_MANUAL_INSTANTIATION
diff --git a/include/itkVkInverseFFTImageFilter.hxx b/include/itkVkInverseFFTImageFilter.hxx
index cc261203..9c1d47fe 100644
--- a/include/itkVkInverseFFTImageFilter.hxx
+++ b/include/itkVkInverseFFTImageFilter.hxx
@@ -30,13 +30,13 @@
 namespace itk
 {
 
-template <typename TInputImage>
-VkInverseFFTImageFilter<TInputImage>::VkInverseFFTImageFilter()
+template <typename TInputImage, typename TOutputImage>
+VkInverseFFTImageFilter<TInputImage,TOutputImage>::VkInverseFFTImageFilter()
 {}
 
-template <typename TInputImage>
+template <typename TInputImage, typename TOutputImage>
 void
-VkInverseFFTImageFilter<TInputImage>::GenerateData()
+VkInverseFFTImageFilter<TInputImage,TOutputImage>::GenerateData()
 {
   // get pointers to the input and output
   const InputImageType * const input{ this->GetInput() };
@@ -101,17 +101,17 @@ VkInverseFFTImageFilter<TInputImage>::GenerateData()
   }
 }
 
-template <typename TInputImage>
+template <typename TInputImage, typename TOutputImage>
 void
-VkInverseFFTImageFilter<TInputImage>::PrintSelf(std::ostream & os, Indent indent) const
+VkInverseFFTImageFilter<TInputImage,TOutputImage>::PrintSelf(std::ostream & os, Indent indent) const
 {
   Superclass::PrintSelf(os, indent);
   os << indent << "DeviceID: " << m_DeviceID << std::endl;
 }
 
-template <typename TInputImage>
-typename VkInverseFFTImageFilter<TInputImage>::SizeValueType
-VkInverseFFTImageFilter<TInputImage>::GetSizeGreatestPrimeFactor() const
+template <typename TInputImage, typename TOutputImage>
+typename VkInverseFFTImageFilter<TInputImage,TOutputImage>::SizeValueType
+VkInverseFFTImageFilter<TInputImage,TOutputImage>::GetSizeGreatestPrimeFactor() const
 {
   return m_VkCommon.GetGreatestPrimeFactor();
 }
diff --git a/include/itkVkRealToHalfHermitianForwardFFTImageFilter.h b/include/itkVkRealToHalfHermitianForwardFFTImageFilter.h
index b42ff8dd..76882223 100644
--- a/include/itkVkRealToHalfHermitianForwardFFTImageFilter.h
+++ b/include/itkVkRealToHalfHermitianForwardFFTImageFilter.h
@@ -18,6 +18,8 @@
 #ifndef itkVkRealToHalfHermitianForwardFFTImageFilter_h
 #define itkVkRealToHalfHermitianForwardFFTImageFilter_h
 
+#include "itkFFTImageFilterFactory.h"
+#include "itkImage.h"
 #include "itkRealToHalfHermitianForwardFFTImageFilter.h"
 #include "itkVkCommon.h"
 
@@ -42,20 +44,22 @@ namespace itk
  * \sa VkGlobalConfiguration
  * \sa RealToHalfHermitianForwardFFTImageFilter
  */
-template <typename TInputImage>
+template <typename TInputImage,
+          typename TOutputImage = Image<std::complex<typename TInputImage::PixelType>, TInputImage::ImageDimension>>
 class VkRealToHalfHermitianForwardFFTImageFilter
-  : public RealToHalfHermitianForwardFFTImageFilter<
-      TInputImage,
-      Image<std::complex<typename TInputImage::PixelType>, TInputImage::ImageDimension>>
+  : public RealToHalfHermitianForwardFFTImageFilter<TInputImage, TOutputImage>
 {
 public:
   ITK_DISALLOW_COPY_AND_MOVE(VkRealToHalfHermitianForwardFFTImageFilter);
 
   using InputImageType = TInputImage;
-  using OutputImageType = Image<std::complex<typename TInputImage::PixelType>, TInputImage::ImageDimension>;
+  using OutputImageType = TOutputImage;
   static_assert(std::is_same<typename TInputImage::PixelType, float>::value ||
                   std::is_same<typename TInputImage::PixelType, double>::value,
                 "Unsupported pixel type");
+  static_assert(std::is_same<typename TOutputImage::PixelType, std::complex<float>>::value ||
+                  std::is_same<typename TOutputImage::PixelType, std::complex<double>>::value,
+                "Unsupported pixel type");
   static_assert(TInputImage::ImageDimension >= 1 && TInputImage::ImageDimension <= 3, "Unsupported image dimension");
 
   /** Standard class type aliases. */
@@ -102,6 +106,17 @@ class VkRealToHalfHermitianForwardFFTImageFilter
   VkCommon m_VkCommon{};
 };
 
+// Describe whether input/output are real- or complex-valued
+// for factory registration
+template <>
+struct FFTImageFilterTraits<VkRealToHalfHermitianForwardFFTImageFilter>
+{
+  template <typename TUnderlying>
+  using InputPixelType = TUnderlying;
+  template <typename TUnderlying>
+  using OutputPixelType = std::complex<TUnderlying>;
+};
+
 } // namespace itk
 
 #ifndef ITK_MANUAL_INSTANTIATION
diff --git a/include/itkVkRealToHalfHermitianForwardFFTImageFilter.hxx b/include/itkVkRealToHalfHermitianForwardFFTImageFilter.hxx
index 2a65bc63..b8cd018d 100644
--- a/include/itkVkRealToHalfHermitianForwardFFTImageFilter.hxx
+++ b/include/itkVkRealToHalfHermitianForwardFFTImageFilter.hxx
@@ -30,13 +30,13 @@
 namespace itk
 {
 
-template <typename TInputImage>
-VkRealToHalfHermitianForwardFFTImageFilter<TInputImage>::VkRealToHalfHermitianForwardFFTImageFilter()
+template <typename TInputImage, typename TOutputImage>
+VkRealToHalfHermitianForwardFFTImageFilter<TInputImage, TOutputImage>::VkRealToHalfHermitianForwardFFTImageFilter()
 {}
 
-template <typename TInputImage>
+template <typename TInputImage, typename TOutputImage>
 void
-VkRealToHalfHermitianForwardFFTImageFilter<TInputImage>::GenerateData()
+VkRealToHalfHermitianForwardFFTImageFilter<TInputImage, TOutputImage>::GenerateData()
 {
   // get pointers to the input and output
   const InputImageType * const input{ this->GetInput() };
@@ -101,17 +101,17 @@ VkRealToHalfHermitianForwardFFTImageFilter<TInputImage>::GenerateData()
   }
 }
 
-template <typename TInputImage>
+template <typename TInputImage, typename TOutputImage>
 void
-VkRealToHalfHermitianForwardFFTImageFilter<TInputImage>::PrintSelf(std::ostream & os, Indent indent) const
+VkRealToHalfHermitianForwardFFTImageFilter<TInputImage, TOutputImage>::PrintSelf(std::ostream & os, Indent indent) const
 {
   Superclass::PrintSelf(os, indent);
   os << indent << "DeviceID: " << m_DeviceID << std::endl;
 }
 
-template <typename TInputImage>
-typename VkRealToHalfHermitianForwardFFTImageFilter<TInputImage>::SizeValueType
-VkRealToHalfHermitianForwardFFTImageFilter<TInputImage>::GetSizeGreatestPrimeFactor() const
+template <typename TInputImage, typename TOutputImage>
+typename VkRealToHalfHermitianForwardFFTImageFilter<TInputImage, TOutputImage>::SizeValueType
+VkRealToHalfHermitianForwardFFTImageFilter<TInputImage, TOutputImage>::GetSizeGreatestPrimeFactor() const
 {
   return m_VkCommon.GetGreatestPrimeFactor();
 }
diff --git a/setup.py b/setup.py
index c5adb9c0..9ea8047a 100644
--- a/setup.py
+++ b/setup.py
@@ -12,7 +12,7 @@
 
 setup(
     name="itk-vkfft",
-    version="0.1.1",
+    version="0.1.2",
     author="Insight Software Consortium",
     author_email="itk+community@discourse.itk.org",
     packages=["itk"],
@@ -42,5 +42,5 @@
     license="Apache",
     keywords="ITK InsightToolkit",
     url=r"https://itk.org/",
-    install_requires=[r"itk>=5.3rc01"],
+    install_requires=[r"itk>=5.3rc03"],
 )
diff --git a/wrapping/itkVkComplexToComplex1DFFTImageFilter.wrap b/wrapping/itkVkComplexToComplex1DFFTImageFilter.wrap
new file mode 100644
index 00000000..ef00b73a
--- /dev/null
+++ b/wrapping/itkVkComplexToComplex1DFFTImageFilter.wrap
@@ -0,0 +1,9 @@
+itk_wrap_class("itk::VkComplexToComplex1DFFTImageFilter" POINTER)
+  if(ITK_WRAP_COMPLEX_FLOAT)
+    itk_wrap_image_filter(CF 1 1;2;3)
+  endif()
+
+  if(ITK_WRAP_COMPLEX_DOUBLE)
+    itk_wrap_image_filter(CD 1 1;2;3)
+  endif()
+itk_end_wrap_class()
diff --git a/wrapping/itkVkComplexToComplexFFTImageFilter.wrap b/wrapping/itkVkComplexToComplexFFTImageFilter.wrap
index b9e5d110..dd47c7bb 100644
--- a/wrapping/itkVkComplexToComplexFFTImageFilter.wrap
+++ b/wrapping/itkVkComplexToComplexFFTImageFilter.wrap
@@ -1,4 +1,9 @@
 itk_wrap_class("itk::VkComplexToComplexFFTImageFilter" POINTER)
-  itk_wrap_image_filter(CF 1 1;2;3)
-  itk_wrap_image_filter(CD 1 1;2;3)
+  if(ITK_WRAP_COMPLEX_FLOAT)
+    itk_wrap_image_filter(CF 1 1;2;3)
+  endif()
+
+  if(ITK_WRAP_COMPLEX_DOUBLE)
+    itk_wrap_image_filter(CD 1 1;2;3)
+  endif()
 itk_end_wrap_class()
diff --git a/wrapping/itkVkForward1DFFTImageFilter.wrap b/wrapping/itkVkForward1DFFTImageFilter.wrap
new file mode 100644
index 00000000..776792c9
--- /dev/null
+++ b/wrapping/itkVkForward1DFFTImageFilter.wrap
@@ -0,0 +1,9 @@
+itk_wrap_class("itk::VkForward1DFFTImageFilter" POINTER)
+  if(ITK_WRAP_COMPLEX_FLOAT)
+    itk_wrap_image_filter(F 1 1;2;3)
+  endif()
+
+  if(ITK_WRAP_COMPLEX_DOUBLE)
+    itk_wrap_image_filter(D 1 1;2;3)
+  endif()
+itk_end_wrap_class()
diff --git a/wrapping/itkVkForwardFFTImageFilter.wrap b/wrapping/itkVkForwardFFTImageFilter.wrap
index 22bbbc1b..1ac8b513 100644
--- a/wrapping/itkVkForwardFFTImageFilter.wrap
+++ b/wrapping/itkVkForwardFFTImageFilter.wrap
@@ -1,4 +1,9 @@
 itk_wrap_class("itk::VkForwardFFTImageFilter" POINTER)
-  itk_wrap_image_filter(F 1 1;2;3)
-  itk_wrap_image_filter(D 1 1;2;3)
+  if(ITK_WRAP_COMPLEX_FLOAT)
+    itk_wrap_image_filter(F 1 1;2;3)
+  endif()
+
+  if(ITK_WRAP_COMPLEX_DOUBLE)
+    itk_wrap_image_filter(D 1 1;2;3)
+  endif()
 itk_end_wrap_class()
diff --git a/wrapping/itkVkHalfHermitianToRealInverseFFTImageFilter.wrap b/wrapping/itkVkHalfHermitianToRealInverseFFTImageFilter.wrap
index 9c4e9583..a09ef50b 100644
--- a/wrapping/itkVkHalfHermitianToRealInverseFFTImageFilter.wrap
+++ b/wrapping/itkVkHalfHermitianToRealInverseFFTImageFilter.wrap
@@ -1,4 +1,9 @@
 itk_wrap_class("itk::VkHalfHermitianToRealInverseFFTImageFilter" POINTER)
-  itk_wrap_image_filter(CF 1 1;2;3)
-  itk_wrap_image_filter(CD 1 1;2;3)
+  if(ITK_WRAP_COMPLEX_FLOAT)
+    itk_wrap_image_filter(CF 1 1;2;3)
+  endif()
+
+  if(ITK_WRAP_COMPLEX_DOUBLE)
+    itk_wrap_image_filter(CD 1 1;2;3)
+  endif()
 itk_end_wrap_class()
diff --git a/wrapping/itkVkInverse1DFFTImageFilter.wrap b/wrapping/itkVkInverse1DFFTImageFilter.wrap
new file mode 100644
index 00000000..93b59fe7
--- /dev/null
+++ b/wrapping/itkVkInverse1DFFTImageFilter.wrap
@@ -0,0 +1,9 @@
+itk_wrap_class("itk::VkInverse1DFFTImageFilter" POINTER)
+  if(ITK_WRAP_COMPLEX_FLOAT)
+    itk_wrap_image_filter(CF 1 1;2;3)
+  endif()
+
+  if(ITK_WRAP_COMPLEX_DOUBLE)
+    itk_wrap_image_filter(CD 1 1;2;3)
+  endif()
+itk_end_wrap_class()
diff --git a/wrapping/itkVkInverseFFTImageFilter.wrap b/wrapping/itkVkInverseFFTImageFilter.wrap
index 4313f4b4..b33e8519 100644
--- a/wrapping/itkVkInverseFFTImageFilter.wrap
+++ b/wrapping/itkVkInverseFFTImageFilter.wrap
@@ -1,4 +1,9 @@
 itk_wrap_class("itk::VkInverseFFTImageFilter" POINTER)
-  itk_wrap_image_filter(CF 1 1;2;3)
-  itk_wrap_image_filter(CD 1 1;2;3)
+  if(ITK_WRAP_COMPLEX_FLOAT)
+    itk_wrap_image_filter(CF 1 1;2;3)
+  endif()
+
+  if(ITK_WRAP_COMPLEX_DOUBLE)
+    itk_wrap_image_filter(CD 1 1;2;3)
+  endif()
 itk_end_wrap_class()
diff --git a/wrapping/itkVkRealToHalfHermitianForwardFFTImageFilter.wrap b/wrapping/itkVkRealToHalfHermitianForwardFFTImageFilter.wrap
index 049242f0..5486dffc 100644
--- a/wrapping/itkVkRealToHalfHermitianForwardFFTImageFilter.wrap
+++ b/wrapping/itkVkRealToHalfHermitianForwardFFTImageFilter.wrap
@@ -1,4 +1,9 @@
 itk_wrap_class("itk::VkRealToHalfHermitianForwardFFTImageFilter" POINTER)
-  itk_wrap_image_filter(F 1 1;2;3)
-  itk_wrap_image_filter(D 1 1;2;3)
+  if(ITK_WRAP_COMPLEX_FLOAT)
+    itk_wrap_image_filter(F 1 1;2;3)
+  endif()
+
+  if(ITK_WRAP_COMPLEX_DOUBLE)
+    itk_wrap_image_filter(D 1 1;2;3)
+  endif()
 itk_end_wrap_class()


Test patch:
diff --git a/.github/workflows/build-test-package.yml b/.github/workflows/build-test-package.yml
index 9a610fdf..1d1ee903 100644
--- a/.github/workflows/build-test-package.yml
+++ b/.github/workflows/build-test-package.yml
@@ -17,7 +17,7 @@ jobs:
             opencl-headers-git-tag: "v2021.04.29"
             opencl-version: 120
             vkfft-backend: 3
-            itk-git-tag: "b1a0a91614dc091f9a17c1e4727095c80e9c05ea"
+            itk-git-tag: "ddef776afb46a6e135ba3c9f58a0e6e7e4a000be"
             cmake-build-type: "MinSizeRel"
           - os: windows-2019
             c-compiler: "cl.exe"
@@ -26,7 +26,7 @@ jobs:
             opencl-headers-git-tag: "v2021.04.29"
             opencl-version: 120
             vkfft-backend: 3
-            itk-git-tag: "b1a0a91614dc091f9a17c1e4727095c80e9c05ea"
+            itk-git-tag: "ddef776afb46a6e135ba3c9f58a0e6e7e4a000be"
             cmake-build-type: "Release"
           - os: macos-10.15
             c-compiler: "clang"
@@ -35,7 +35,7 @@ jobs:
             opencl-headers-git-tag: "v2021.04.29"
             opencl-version: 120
             vkfft-backend: 3
-            itk-git-tag: "b1a0a91614dc091f9a17c1e4727095c80e9c05ea"
+            itk-git-tag: "ddef776afb46a6e135ba3c9f58a0e6e7e4a000be"
             cmake-build-type: "MinSizeRel"
 
     steps:
@@ -99,7 +99,7 @@ jobs:
         cd ..
         mkdir OpenCL-SDK-build
         cd OpenCL-SDK-build
-        cmake -DCMAKE_C_COMPILER:FILEPATH="${{ matrix.c-compiler }}" -DCMAKE_CXX_COMPILER="${{ matrix.cxx-compiler }}" -DBUILD_SHARED_LIBS:BOOL=ON -DCMAKE_BUILD_TYPE:STRING=${{ matrix.cmake-build-type }} -GNinja ../OpenCL-SDK
+        cmake -DCMAKE_C_COMPILER:FILEPATH="${{ matrix.c-compiler }}" -DCMAKE_CXX_COMPILER="${{ matrix.cxx-compiler }}" -DBUILD_SHARED_LIBS:BOOL=ON -DCMAKE_BUILD_TYPE:STRING=${{ matrix.cmake-build-type }} -DOPENCL_SDK_BUILD_SAMPLES:BOOL=OFF -GNinja ../OpenCL-SDK
         sudo cmake --build . --target install
       shell: bash
 
@@ -216,9 +216,9 @@ jobs:
     strategy:
       max-parallel: 2
       matrix:
-        python-version-minor: [7, 8, 9]
+        python-version-minor: [7, 8, 9, 10]
         include:
-          - itk-python-git-tag: "v5.3rc01"
+          - itk-python-git-tag: "v5.3rc03"
             c-compiler: "cl.exe"
             cxx-compiler: "cl.exe"
             opencl-icd-loader-git-tag: "v2021.04.29"
@@ -300,7 +300,7 @@ jobs:
       matrix:
         python-version: [37, 38, 39]
         include:
-          - itk-python-git-tag: "v5.3rc01"
+          - itk-python-git-tag: "v5.3rc03"
 
     steps:
     - uses: actions/checkout@v2
@@ -332,7 +332,7 @@ jobs:
       max-parallel: 2
       matrix:
         include:
-          - itk-python-git-tag: "v5.3rc01"
+          - itk-python-git-tag: "v5.3rc03"
 
     steps:
     - uses: actions/checkout@v2
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index a9818c62..960d069f 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -4,6 +4,7 @@ set(VkFFTBackendTests
   itkVkComplexToComplexFFTImageFilterTest.cxx
   itkVkComplexToComplex1DFFTImageFilterBaselineTest.cxx
   itkVkComplexToComplex1DFFTImageFilterSizesTest.cxx
+  itkVkFFTImageFilterFactoryTest.cxx
   itkVkForwardInverseFFTImageFilterTest.cxx
   itkVkForwardInverse1DFFTImageFilterTest.cxx
   itkVkForward1DFFTImageFilterBaselineTest.cxx
@@ -82,3 +83,8 @@ itk_add_test(NAME itkVkForwardInverse1DFFTImageFilterTest
  itk_add_test(NAME itkVkHalfHermitianFFTImageFilterTest
    COMMAND VkFFTBackendTestDriver itkVkHalfHermitianFFTImageFilterTest
    )
+
+itk_add_test(NAME itkVkFFTImageFilterFactoryTest
+  COMMAND VkFFTBackendTestDriver
+  itkVkFFTImageFilterFactoryTest
+   )
diff --git a/test/itkVkFFTImageFilterFactoryTest.cxx b/test/itkVkFFTImageFilterFactoryTest.cxx
new file mode 100644
index 00000000..ab3470ce
--- /dev/null
+++ b/test/itkVkFFTImageFilterFactoryTest.cxx
@@ -0,0 +1,67 @@
+/*=========================================================================
+ *
+ *  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 "itkComplexToComplex1DFFTImageFilter.h"
+#include "itkVkComplexToComplex1DFFTImageFilter.h"
+#include "itkVnlComplexToComplex1DFFTImageFilter.h"
+
+#include "itkFFTImageFilterFactory.h"
+#include "itkTestingMacros.h"
+
+// Verify FFT interface classes can be instantiated with
+// VkFFT backends through ITK object factory override methods
+
+int
+itkVkFFTImageFilterFactoryTest(int argc, char * argv[])
+{
+  using PixelType = double;
+  const unsigned int Dimension = 2;
+  using ComplexImageType = itk::Image<std::complex<PixelType>, Dimension>;
+  using FFTBaseType = itk::ComplexToComplex1DFFTImageFilter<ComplexImageType>;
+  using FFTDefaultSubclassType = itk::VnlComplexToComplex1DFFTImageFilter<ComplexImageType>;
+  using FFTVkSubclassType = itk::VkComplexToComplex1DFFTImageFilter<ComplexImageType>;
+
+  // Verify default is non-accelerated implementation
+  typename FFTBaseType::Pointer fft = FFTBaseType::New();
+  FFTDefaultSubclassType *      vnlFFT = dynamic_cast<FFTDefaultSubclassType *>(fft.GetPointer());
+  ITK_TEST_EXPECT_TRUE(vnlFFT != nullptr);
+  ITK_EXERCISE_BASIC_OBJECT_METHODS(vnlFFT, VnlComplexToComplex1DFFTImageFilter, ComplexToComplex1DFFTImageFilter);
+
+  // Register factory and verify override
+  using FactoryType = itk::FFTImageFilterFactory<itk::VkComplexToComplex1DFFTImageFilter>;
+  typename FactoryType::Pointer factory = FactoryType::New();
+  itk::ObjectFactoryBase::RegisterFactory(factory, itk::ObjectFactoryEnums::InsertionPosition::INSERT_AT_FRONT);
+
+  fft = FFTBaseType::New();
+  FFTVkSubclassType * vkFFT = dynamic_cast<FFTVkSubclassType *>(fft.GetPointer());
+  ITK_TEST_EXPECT_TRUE(vkFFT != nullptr);
+  ITK_EXERCISE_BASIC_OBJECT_METHODS(vkFFT, VkComplexToComplex1DFFTImageFilter, ComplexToComplex1DFFTImageFilter);
+
+  // Verify factory can be removed
+  itk::ObjectFactoryBase::UnRegisterFactory(factory);
+
+  fft = FFTBaseType::New();
+  vnlFFT = dynamic_cast<FFTDefaultSubclassType *>(fft.GetPointer());
+  ITK_TEST_EXPECT_TRUE(vnlFFT != nullptr);
+  ITK_EXERCISE_BASIC_OBJECT_METHODS(vnlFFT, VnlComplexToComplex1DFFTImageFilter, ComplexToComplex1DFFTImageFilter);
+
+  return EXIT_SUCCESS;
+}