From 07a6e1f25228f9a5476c46c1b4c8e361f238cc71 Mon Sep 17 00:00:00 2001 From: ncullen93 Date: Sat, 18 May 2024 17:28:36 +0200 Subject: [PATCH 1/4] update docs --- CONTRIBUTING.md | 117 ++++++++++++++++++++++++++++++++++++++++-------- README.md | 36 ++++++++------- 2 files changed, 119 insertions(+), 34 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 22efdf0a..fbb3f238 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,10 +1,48 @@ -# ANTsPy Contributors Guide +# Guide to contributing to ANTsPy -This guide tells you everything you need to know to set up the development workflow that will allow you to contribute code to antspy. +ANTsPy is a Python library that primarily wraps the ANTs C++ library, but also contains a lot of custom C++ and Python code. There are a decent amount of moving parts to get to familiar with before being able to contribute, but we've done our best to make the process easy. -## Installing ANTsPy Development Environment +This guide tells you everything you need to know about ANTsPy to add or change any code in the project. The guide is composed of the following sections. -To start developing, you need to install a development copy of ANTsPy. This follows the same steps as developing any python package. +- Project structure +- Setting up a dev environment +- Wrapping ANTs functions +- Adding C++ / ITK code +- Adding Python code +- Running tests + +The first two sections and the last section should be read by everyone, but the other sections can be skipped depending on your goal. + +## Project structure + +The ANTsPy project consists of multiple folders which are listed and explained here. + +- **.github/** : contains all GitHub actions +- **ants/** : contains the Python code for the library +- **data/** : contains any data (images) included with the installed library +- **docs/** : contains the structure for building the library documentation +- **scripts/** : contains scripts to build / clone ITK and ANTs during installation +- **src/** : contains the C++ code for the library +- **tests/** : contains all tests +- **tutorials/** contains all .md and .ipynb tutorials + +If you are adding code to the library, the three folders you'll care about most are `ants/` (to add Python code), `src/` (to add C++ code), and `tests/` (to add tests). + +### Nanobind + +The C++ code is wrapped using [nanobind](https://nanobind.readthedocs.io/en/latest/). It is basically an updated version of pybind11 that makes it easy to call C++ functions from Python. Having a basic understanding of nanobind can help in some scenarios, but it's not strictly necessary. + +The `CMakeLists.txt` file and the `src/main.cpp` file contains most of the information for determining how nanobind wraps and builds the C++ files in the project. + +### Scikit-build + +The library is built using [scikit-build](https://scikit-build.readthedocs.io/en/latest/), which is a modern alternative to `setup.py` files for projects that include C++ code. + +The `pyproject.toml` file is the central location for steering the build process. If you need to change the way the library is built, that's the best place to start. + +## Setting up a dev environment + +To start developing, you need to build a development copy of ANTsPy. This process is the same as developing for any python package. ```bash git clone https://github.com/ANTsX/ANTsPy.git @@ -12,11 +50,11 @@ cd ANTsPy python -m pip install -v -e . ``` -Notice the `-v` flag to have a verbose output so you can follow the build process that can take ~45 minutes. Then there is also the `-e` flag that will build the antspy package in such a way that any changes to the python code will be automatically detected when you restart your python terminal without having to build the package again. +Notice the `-v` flag to have a verbose output so you can follow the build process that can take 30 - 45 minutes. Then there is also the `-e` flag that will build the library in such a way that any changes to the Python code will be automatically detected when you restart your python terminal without having to build the package again. -Any changes to C++ code will require you to run that last line (`python -m pip install -v -e .`) again to rebuild the compiled libraries. +Any changes to C++ code will require you to run that last line (`python -m pip install -v -e .`) again to rebuild the compiled libraries. However, it should not take more than a couple of minutes if you've only made minor changes or additions. -## What happens when I install ANTsPy? +### What happens when you install ANTsPy When you run `python -m pip install .` or `python -m pip install -e .` to install antspy from source, the CMakeLists.txt file is run. Refer there if you want to change any part of the install process. Briefly, it performs the following steps: @@ -25,7 +63,7 @@ When you run `python -m pip install .` or `python -m pip install -e .` to instal 3. The C++ files from the `src` directory are used to build the antspy library files 4. The antspy python package is built as normal -## Wrapping core ANTs functions +## Wrapping ANTs functions Wrapping an ANTs function is easy since pybind11 implicitly casts between python and C++ standard types, allowing you to directly interface with C++ code. Here's an example: @@ -77,7 +115,7 @@ The general workflow for wrapping a library calls involves the following steps: - pass those raw arguments through the function `process_arguments(args)` - pass those processed arguments into the library call (e.g. `lib.Atropos(processed_args)`). -## Writing custom code for antspy +## Add C++ / ITK code You can write any kind of custom code to process antspy images. The underlying image is ITK so the AntsImage class holds a pointer to the underlying ITK object in the in the property `self.pointer`. @@ -167,22 +205,20 @@ Finally, we create a wrapper function in python file `get_origin.py`. Notice tha ```python -from ants import lib # use relative import e.g. "from .. import lib" in package code +from ants.decorators import image_method +from ants.internal import get_lib_fn +@image_method def get_origin(img): - idim = img.dimension - ptype = img.pixeltype - - # call function - NOTE how we pass in `img.pointer`, not `img` directly - origin = lib.getOrigin(img.pointer) + libfn = get_lib_fn('getOrigin') + origin = libfn(img.pointer) - # return as tuple return tuple(origin) ``` And that's it! For more other return types, you should refer to the nanobind docs. -## Wrapping an ITK image for antspy +### Wrapping an ITK image In the previous section, we saw how easy it is to cast from AntsImage to ITK Image by calling `antsImage.ptr`. It is also easy to go the other way and wrap an ITK image as an AntsImage. @@ -232,7 +268,52 @@ AntsImage someFunction( AntsImage antsImage ) } ``` -## Running Tests +## Adding Python code + +If you want to add custom Python code that calls other ANTsPy functions or the wrapped code, there are a few things to know. The `label_clusters` function provides a good example to show how to do so. + +```python +import ants +from ants.internal import get_lib_fn, process_arguments +from ants.decorators import image_method + +@image_method +def label_clusters(image, min_cluster_size=50, min_thresh=1e-6, max_thresh=1, fully_connected=False): + """ + This will give a unique ID to each connected + component 1 through N of size > min_cluster_size + """ + dim = image.dimension + clust = ants.threshold_image(image, min_thresh, max_thresh) + temp = int(fully_connected) + args = [dim, clust, clust, min_cluster_size, temp] + processed_args = process_arguments(args) + libfn = get_lib_fn('LabelClustersUniquely') + libfn(processed_args) + return clust +``` + +First, notice the imports at the top. You generally need three imports. First, you need to import the library so that all other internal functions (such as `ants.threshold_image`) are available. + +```python +import ants +``` + +Next, you need import a few functions from `ants.internal` that let you get a function from the compiled C++ library (`get_lib_fn`) and that let you combined arguments into the format ANTs expects (`process_arguments`). Note that `process_arguments` is only needed if you are called a wrapped ANTs function. + +```python +from ants.internal import get_lib_fn, process_arguments +``` + +Finally, you should import `image_method` from `ants.decorators`. This decorator lets you attach a function to the ANTsImage class so that the function can be chained to the image. This is why you can call `image.dosomething()` instead of only `ants.dosomething(image)`. + +```python +from ants.decorators import image_method +``` + +With those three imports, you can call any internal Python function or any C++ function (wrapped or custom). + +## Running tests All tests can be executed by running the following command from the main directory: diff --git a/README.md b/README.md index b5d520c5..4d75f915 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,16 @@
-The ANTsPy library wraps the well-established C++ biomedical image processing framework [ANTs](https://github.com/antsx/ants). It includes blazing-fast reading and writing of medical images, algorithms for registration, segmentation, and statistical learning, as well as functions to create publication-ready visualizations. +The ANTsPy library wraps the well-established C++ biomedical image processing framework [ANTs](https://github.com/antsx/ants). It includes blazing-fast reading and writing of medical images, algorithms for registration, segmentation, and statistical learning, as well as functions to create publication-ready visualizations. -If you are looking to train deep learning models on your medical images, you might be interested in [antspynet](https://github.com/antsx/antspy) which provides tools for training and visualizing deep learning models. ANTsPy and ANTsPyNet seamlessly integrate with the greater Python community, particularly deep learning libraries, scikit-learn, and numpy. +If you are looking to train deep learning models on medical imaging datasets, you might be interested in [ANTsPyNet](https://github.com/antsx/antspy) which provides tools for training and visualizing deep learning models.
## Installation +### Pre-compiled binaries + The easiest way to install ANTsPy is via the latest pre-compiled binaries from PyPI. ```bash @@ -28,8 +30,12 @@ pip install antspyx ``` Because of limited storage space, pip binaries are not available for every combination of python -version and platform. If we do not have releases for your platform, you can check the -[Github Releases page](https://github.com/antsx/antspy/releases) or build from source: +version and platform. If we do not have releases for your platform on PyPI, you can check the +[Releases](https://github.com/antsx/antspy/releases) page for archived binaries. + +### Building from source + +In some scenarios, it can make sense to build from source. In general, you can build ANTsPy as you would any other Python package. ``` git clone https://github.com/antsx/antspy @@ -38,7 +44,7 @@ python -m pip install . ``` Further details about installing ANTsPy or building it from source can be found in the -[installation tutorial](https://github.com/antsx/antspy/blob/master/tutorials/Installation.md). +[Installation Tutorial](https://github.com/antsx/antspy/blob/master/tutorials/Installation.md).
@@ -48,7 +54,7 @@ Here is an example of reading in an image, using various utility functions such ```python import ants -img = ants.image_read(get_data("r16")) +img = ants.image_read(ants.get_data("r16")) img = ants.resample_image(img, (64,64), 1, 0 ) mask = ants.get_mask(img) segs1 = ants.atropos(a=img, m='[0.2,1x1]', c='[2,0]', i='kmeans[3]', x=mask) @@ -58,23 +64,21 @@ segs1 = ants.atropos(a=img, m='[0.2,1x1]', c='[2,0]', i='kmeans[3]', x=mask) ## Tutorials -Resources for learning about ANTsPy can be found in the [tutorials](https://github.com/ANTsX/ANTsPy/tree/master/tutorials) folder. An overview of the available tutorials is presented below. - -- [Basic overview](https://github.com/ANTsX/ANTsPy/blob/master/tutorials/tutorial_5min.md) - -- [Composite registrations](https://github.com/ANTsX/ANTsPy/blob/master/tutorials/concatenateRegistrations.ipynb) - -- [Multi-metric registration](https://github.com/ANTsX/ANTsPy/blob/master/tutorials/concatenateRegistration/MultiMetricRegistration.ipynb) +Resources for learning about ANTsPy can be found in the [tutorials](https://github.com/ANTsX/ANTsPy/tree/master/tutorials) folder. A selection of especially useful tutorials is presented below. -- [Image math operations](https://github.com/ANTsX/ANTsPy/blob/master/tutorials/iMath_help.ipynb) +- Basic overview [[Link](https://github.com/ANTsX/ANTsPy/blob/master/tutorials/tutorial_5min.md)] +- Composite registrations [[Link](https://github.com/ANTsX/ANTsPy/blob/master/tutorials/concatenateRegistrations.ipynb)] +- Multi-metric registration [[Link](https://github.com/ANTsX/ANTsPy/blob/master/tutorials/concatenateRegistration/MultiMetricRegistration.ipynb)] +- Image math operations [[Link](https://github.com/ANTsX/ANTsPy/blob/master/tutorials/iMath_help.ipynb)] +- Wrapping ITK code [[Link](https://github.com/ANTsX/ANTsPy/blob/master/tutorials/UsingITK.ipynb)] -- [Wrapping ITK code](https://github.com/ANTsX/ANTsPy/blob/master/tutorials/UsingITK.ipynb) +More tutorials can be found in the [ANTs](https://github.com/ANTsX/ANTs) repository.
## Contributing -If you have a question, feature request, or bug report the best way to get help is by posting an issue on the GitHub page. We welcome and are thankful for new contributions and ideas. If you want to add code, the best way to get started is by reading the [contributors guide](https://github.com/ANTsX/ANTsPy/blob/master/CONTRIBUTING.md) that runs through the structure of the project and how we go about wrapping ITK and ANTs code in C++. +If you have a question, feature request, or bug report the best way to get help is by posting an issue on the GitHub page. We welcome any new contributions and ideas. If you want to add code, the best way to get started is by reading the [contributors guide](https://github.com/ANTsX/ANTsPy/blob/master/CONTRIBUTING.md) that runs through the structure of the project and how we go about wrapping ITK and ANTs code in C++.
From dba4ebd637c45d65704e3461520b465b31ae40eb Mon Sep 17 00:00:00 2001 From: ncullen93 Date: Sat, 18 May 2024 17:33:10 +0200 Subject: [PATCH 2/4] update docs --- CONTRIBUTING.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fbb3f238..b6182d6d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -164,7 +164,7 @@ std::vector getOrigin( AntsImage antsImage ) typedef typename ImageType::Pointer ImagePointerType; ImagePointerType itkImage = antsImage.ptr; - // do everything else as normal with ITK Imaeg + // do everything else as normal with ITK Image typename ImageType::PointType origin = image->GetOrigin(); unsigned int ndim = ImageType::GetImageDimension(); @@ -184,6 +184,7 @@ void getOrigin(nb::module_ &m) m.def("getOrigin", &getOrigin>); m.def("getOrigin", &getOrigin>); m.def("getOrigin", &getOrigin>); + // ... } ``` @@ -216,7 +217,7 @@ def get_origin(img): return tuple(origin) ``` -And that's it! For more other return types, you should refer to the nanobind docs. +And that's it! More details about how to write Python code for ANTsPy is presented below. For other return types, consult the nanobind docs. However, most C++ types will be automatically converted to the corresponding Python types - both arguments and return values. ### Wrapping an ITK image From 725148738f15c33cb58832e42a176903fa5948a5 Mon Sep 17 00:00:00 2001 From: ncullen93 Date: Sat, 18 May 2024 17:34:12 +0200 Subject: [PATCH 3/4] update docs --- CONTRIBUTING.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b6182d6d..c25231e5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,6 +13,8 @@ This guide tells you everything you need to know about ANTsPy to add or change a The first two sections and the last section should be read by everyone, but the other sections can be skipped depending on your goal. +
+ ## Project structure The ANTsPy project consists of multiple folders which are listed and explained here. @@ -40,6 +42,8 @@ The library is built using [scikit-build](https://scikit-build.readthedocs.io/en The `pyproject.toml` file is the central location for steering the build process. If you need to change the way the library is built, that's the best place to start. +
+ ## Setting up a dev environment To start developing, you need to build a development copy of ANTsPy. This process is the same as developing for any python package. @@ -63,6 +67,8 @@ When you run `python -m pip install .` or `python -m pip install -e .` to instal 3. The C++ files from the `src` directory are used to build the antspy library files 4. The antspy python package is built as normal +
+ ## Wrapping ANTs functions Wrapping an ANTs function is easy since pybind11 implicitly casts between python and C++ standard types, allowing you to directly interface with C++ code. Here's an example: @@ -115,6 +121,8 @@ The general workflow for wrapping a library calls involves the following steps: - pass those raw arguments through the function `process_arguments(args)` - pass those processed arguments into the library call (e.g. `lib.Atropos(processed_args)`). +
+ ## Add C++ / ITK code You can write any kind of custom code to process antspy images. The underlying image is ITK so the AntsImage class holds a pointer to the underlying ITK object in the in the property `self.pointer`. @@ -137,7 +145,7 @@ Now, say you wrapped this code and wanted to call it from python. You wouldn't p the Python ANTsImage object directly, you would pass in the `self.pointer` attribute which contains the ITK image pointer. -### Example 1 - getOrigin +### Example - getOrigin Let's do a full example where we get the origin of a Python AntsImage from the underlying ITK image. @@ -269,6 +277,8 @@ AntsImage someFunction( AntsImage antsImage ) } ``` +
+ ## Adding Python code If you want to add custom Python code that calls other ANTsPy functions or the wrapped code, there are a few things to know. The `label_clusters` function provides a good example to show how to do so. @@ -314,6 +324,8 @@ from ants.decorators import image_method With those three imports, you can call any internal Python function or any C++ function (wrapped or custom). +
+ ## Running tests All tests can be executed by running the following command from the main directory: From 2f429ad9eff7f8018f1fce5f44bd208fcda52735 Mon Sep 17 00:00:00 2001 From: ncullen93 Date: Sat, 18 May 2024 17:34:58 +0200 Subject: [PATCH 4/4] fix typo --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c25231e5..41bd002d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -123,7 +123,7 @@ The general workflow for wrapping a library calls involves the following steps:
-## Add C++ / ITK code +## Adding C++ / ITK code You can write any kind of custom code to process antspy images. The underlying image is ITK so the AntsImage class holds a pointer to the underlying ITK object in the in the property `self.pointer`.