This morning I have been investigating python wrapping for plastimatch. It looks feasible to use the ITK wrapping method. Would this be a reasonable approach for eventually allowing python interaction within 3D Slicer? I’m a bit concerned because looking at the Slicer’s build it seems ITK itself is not wrapped and not available in Slicer python.
investigating python wrapping for plastimatch
This is good to hear. Are you thinking about creating a dedicated “pythonic” API or a one-to-one mapping with the C++ one ?
It looks feasible to use the ITK wrapping method
This is suitable for projects providing ITK filters and/or classes (or having functionality that could be wrapped in such a way).
If that makes sense, you could look at using the GitHub - InsightSoftwareConsortium/ITKModuleTemplate: A template to start an ITK Module as a starting point. See also https://blog.kitware.com/python-packages-for-itk-modules/
Would this be a reasonable approach for eventually allowing python interaction within 3D Slicer?
Since python packages distributed on http://pypi.org/ can now be installed on Linux, macOS and windows. This would work.
Slicer’s build it seems ITK itself is not wrapped
This is correct, while there is an option called Slicer_BUILD_ITKPython
, it not enabled by default.
ITK itself […] not available in Slicer python
following the recent transition of Slicer to python 3.6, running pip install itk
would allow you to use it.
suggestions
You may want to look at using GitHub - pybind/pybind11: Seamless operability between C++11 and Python along https://scikit-build.readthedocs.io
Thank you for the great feedback! This is quite new area for me, and I’m still considering the best plan.
Both pybind or scikit-build look good. Do you have any idea which would be better? I note that SimpleITK seems to use scikit-build.
Do you have any idea which would be better?
ITKModuleTemplate
If you are already familiar with ITK and crafting a Plastimatch API built around ITK data structure is relevant, this would be a sensible approach.
You also be able to leverage the input of the community and wealth of knowledge shared on the ITK forum (https://discourse.itk.org/)
The template would also give you CI and infrastructure for package distributions with no effort. @phcerdan works on a similar project to wrap the proxTV
project and make it available as GitHub - InsightSoftwareConsortium/ITKTotalVariation: External Module for Total Variation Algorithms, providing wrap for https://github.com/albarji/proxTV module
Cc: @thewtex
pybind11
While this removes the need for tools like swig
, you are still required to maintain the wrapping code. Note that I don’t have a lot of experience with it.
suggested next steps
It may be sensible to craft a document listing the functionality and API you would like to expose to Python.
It would also be nice to listing the data structure that should not be deep-copied and are expected to be shared between VTK/ITK/etc … (if any)
scikit-build.
This project streamlines the creation of python package (called wheel) for project using CMake. It is not responsible for any wrapping in itself and will be relevant for all approaches involving compilation of python modules.
Hi @gcsharp,
As @jcfr notes, the ITKModuleTemplate may be the fastest way to get started. This avoids:
- Manually maintaining binding code (we analyize the headers with CastXML to generate the required SWIG interface files).
- The CI infrastructure to generate Windows, Linux, and macOS packages.
- The configuration to build the wheel with scikit-build and the post-processing steps to make distributable wheels.
The ITK Software Guide has a section on writing the wrap interface files, but we would be happy to answer questions.
Got it. Thank you! I will do a pilot study.
I think for Slicer users interface with sitk might be more important than itk.
@gcsharp do you have any updates on this topic?
We are using Plastimatch for developing use cases in IDC, and lacking such wrapper, Dennis Bontempi put together this as a workaround: GitHub - AIM-Harvard/pyplastimatch: Dummy python plastimatch wrapper..
Regrettably, I have no progress. Would the method used in your/Paolo’s approach be good?
I will let Dennis and @PaoloZaffino respond with their thoughts. I think what would be ideal is to be able to install those wrappers with pip
. Meanwhile, maybe Plastimatch documentation can include pointers to the existing wrappers, to save effort for the next developer?
Thanks @fedorov for the mention! (I actually remember looking at this thread while deciding what to do for the dummy wrapper - so hopefully this will be able to help someone else!)
@gcsharp what I’ve implemented on the fly (the plan is to expand it a bit, to make sure at least all the “common” operations can be launched from python scripts) is really simple and quite close to what Paolo did back in the days. The only reason I decided to implement “my own” is that I feared Paolo’s version could be outdated (with respect to Plastimatch). I also wanted to make sure a couple of features useful for the use cases were there, and forking Paolo’s implementation to then customise it didn’t look as fun
Basically, it’s as simple as calling Plastimatch from python exploiting subprocess
(any library for process management will do), and adding some other handy functionalities in the meantime (e.g., logging all the operations, and a few other scripts for visualisation etc.). This not only allows to run Plastimatch functions directly from your python scripts (of course, abstracting the OS - although I must report I have tested this exclusively on Linux), but also make use of other python functionalities that can help you process a great amount of data in less time. For instance, I have used the Plastimatch wrapper to run DICOM to NRRD conversion of hundreds/thousands of patients in parallel (with the performance/time requirements scaling almost linearly with the number of cores). In some cases, I’ve done so and then applied some pre-processing on the fly (for a total of a dozen lines of code), processing in a few minutes a dataset that would have required hours otherwise (e.g., DICOM to NRRD conversion scripting everything from bash, one subject at a time, then read the NRRD files, preprocess them exploiting python, and save them back).
The multiprocessing code I’m referring to here will be made available super soon (a matter of a couple of weeks if not less, I hope!) as part of the development of the use cases Andrey mentioned. In case you don’t want to wait though, here is an idea how you could script it using the dummy PyPlastimatch wrapper:
import os
import tqdm
import multiprocessing
import utils.pyplastimatch as pypla
# pat_config is a dictionary storing basic I/O and config info for the conversion
def run_core(pat_config):
# patient subfolder where all the preprocessed data will be stored
if not os.path.exists(PAT_DIR): os.mkdir(PAT_DIR)
verbose = pat_config["verbose"]
# logfile for the plastimatch conversion
LOGFILE = os.path.join(PAT_DIR, pat + '_pypla.log')
# DICOM CT to NRRD conversion
if not os.path.exists(CT_NRRD_PATH):
convert_args_ct = {"input" : PATH_TO_DICOM_DIR,
"output-img" : PATH_TO_NRRD_CT}
# clean old log file if it exist
if os.path.exists(log_file_path_nrrd): os.remove(LOGFILE)
pypla.convert(verbose = verbose, path_to_log_file = LOGFILE, **convert_args_ct)
def main(config):
cpu_cores = config["cpu_cores"]
if use_multiprocessing:
pool = multiprocessing.Pool(processes = cpu_cores)
"""
write here the code to populate "pat_config_list", a list of dictionaries storing
the information used by the "run_core" function for the processing (e.g., paths, verbosity, etc.)
"""
for _ in tqdm.tqdm(pool.imap_unordered(run_core, pat_config_list), total = len(pat_dict_list_mp)):
pass
if __name__ == '__main__':
"""
Parse the config file for the "main" function here!
"""
(sorry if this is not a MWE - if you want to test-run your own code based on this and have problems, do reach out! As I said, such scripts and all the details will be made available as part of the on-going effort at IDC!)
P.S.
That is the plan indeed (to have it documented and available through pip)!
Hi all,
glad to hear my code “inspired” someone
I agree with @denbonte , the idea is to run the executable via the python os module.
It’s not strictly a wrapper, rather an additional layer.
My version was written for plastimatch at that time, now some commands have changed and it’s possible to have some errors (I didn’t update it).
In SlicerRT we implemented a direct bridge between the plastimatch registration algorithm and the Slicer environment. That is not an addition layer, but a real connection (Slicer infrastructure helped a lot to achieve this). After that, I think other commands have been exposed in slicer (eg DRR).
Great to hear it can be installed via pip!
Let me know if I can help, it’s always a pleasure working on plastimatch!
Best,
Paolo