Can't compile RTK Python Wrapper - How do other ITK Modules address this exporting problem?

Hi Everyone,

I hope this is the right place to have this discussion.

I’ve been trying to compile RTK with Cuda and Python wrapping on windows for a while and the last time I tried, I was suggested to just disable the python wrapping for RTK over here. That solution works, but does not sit well with me.
There are many opened issues on RTK on this here or related to this here and here.

I’ve finally tracked down the problem @simon.rit (see below), but I’m not sure what’s the best path forward and I’m looking for suggestions form the community to understand what others have done.

For my specific case, I’m trying to build ITK/RTK on Windows as shared libraries, with cuda enabled for RTK and with python wrapping enabled.

So here’s the problem as it stands: when you try to compile it with these settings, you get multiple link errors. The very first one is this:

itkRTK-5.1.lib(itkRTK-5.1.dll) : error LNK2005: "public: class std::vector<class itk::Matrix<double,3,4>,class std::allocator<class itk::Matrix<double,3,4> > > const & __cdecl rtk::ProjectionGeometry<3>::GetMatrices(void)const " (?GetMatrices@?$ProjectionGeometry@$02@rtk@@QEBAAEBV?$vector@V?$Matrix@N$02$03@itk@@V?$allocator@V?$Matrix@N$02$03@itk@@@std@@@std@@XZ) already defined in rtkProjectionGeometryPython.cpp.obj

but you can find the rest of them posted here.

While trying to resolve it, I came to understand that __declspec(dllexport) (aka RTK_EXPORT) also exports the base classes. So in the case of RTK, rtk::ProjectionGeometry is not exported and is tagged with ITK_TEMPLATE_EXPORT, but that it’s derived classes like rtk::ThreeDCircularProjectionGeometry and rtk::Reg23ProjectionGeometry are exported.

So adding the RTK_EXPORT to rtk::ProjectionGeometry is easy enough and resolves the first issue.

The second error is this:

itkRTK-5.1.lib(itkRTK-5.1.dll) : error LNK2005: "public: class itk::CudaImage<float,3> * __cdecl itk::ImageSource<class itk::CudaImage<float,3> >::GetOutput(void)" (?GetOutput@?$ImageSource@V?$CudaImage@M$02@itk@@@itk@@QEAAPEAV?$CudaImage@M$02@2@XZ) already defined in itkImageSourceRTKPython.cpp.obj

So based on the first solution, the first obvious solution is to also to export class itk::CudaImage<float,3>, but that’s not actually viable as it will likely cause the same error, but with the itkCudaCommon library.

The other solution, would be to not export it in the first place. Looking at the RTK code base, it’s implicitly exported throughout the code base by way of exporting a derived class in which one of the template parameter is itk::CudaImage<float,3>. For example,

class RTK_EXPORT CudaConstantVolumeSource
  : public itk::CudaImageToImageFilter<itk::CudaImage<float, 3>,
                                       itk::CudaImage<float, 3>,
                                       ConstantImageSource<itk::CudaImage<float, 3>>>{
    ...
}

I assume that this is not a new problem, but I can’t find what other ITK developers have done in this situation. As far as I can tell, on public github, there’s only RTK that’s using itk::CudaImage so I don’t know what other terms I can search for.

What would be the right approach here to not force export the itk::CudaImage class and the itk::CudaImageToImageFilter? If it was me, I would just use the PIMPL idiom here to not expose those classes, but it would require extensive modifications and I’m hoping to avoid that.

So finally, is there a built-in ITK way of doing this that I’m not aware of?
If not, @simon.rit, are you ok with the proposed change of moving to a PIMPL based code structure? I’m more than happy to submit the PR, but I would prefer to get the approval first than to have it rejected later.

1 Like

Thanks for your efforts. Can you provide a CMake script to be able to reproduce the issue? For example, are you building it from ITK or externally?
I guess we would have to reproduce the problem and investigate it (maybe @LucasGandel may be able to help). Have you looked at what’s done in GPUCommon? CudaCommon is originally copy/pasted from there and adapted for Cuda…
Last point: we are almost ready to merge ENH: Remove ITKCudaCommon from the source tree by AlexyPellegrini · Pull Request #492 · RTKConsortium/RTK · GitHub which will finalize the CudaCommon module split from RTK. It would be nice to know if the problem is still there after.

I never realized CudaCommon was part only part of RTK. That would explain why I could only find references to it in RTK.

For the CMake script, I’m not sure what format would be best for it, but I’m currently trying to add support for RTK and python wrapping to the conan-center recipe.
These are the essential cmake settings that I was using/adding to the recipe and was enough to reproduce the problem.

BUILD_SHARED_LIBS=ON
Module_RTK=ON
RTK_BUILD_APPLICATIONS =ON
RTK_USE_CUDA = ON
ITK_WRAP_PYTHON = ON

For reproducing the issue, I see github actions to build python wheels for RTK, but that I don’t understand what is going on there. Presumably, you will be able to see the build issue there as well, but I don’t see any output for the windows builds with cuda and python packages.

@simon.rit
There are 2 other solutions that I was thinking of that would work better than using PIMPL, as I was starting to look in to it more, PIMPL wouldn’t work very well in this scenario.

The first one is very quick and dirty, and actually works is to just link force allow the linking:

if(WIN32)
target_link_options(RTK
    INTERFACE "/force" # for cuda rtk linking
)
endif()

This shoudn’t break any existing install base as I found this gem in our cmake files. So presumably, everyone is doing something similar on windows when RTK is compiled as a shared library or no one on windows is compiling it as a shared library.

The other option is to move everything into header-only classes and remove the need for RTK_EXPORT. It would require more effort, but I suspect that there was a reason that this was done in the first place that I just don’t see at the moment.