Skip to content

Commit

Permalink
Merge pull request #1 from HarryHeres/develop
Browse files Browse the repository at this point in the history
Version 0.1.0
  • Loading branch information
HarryHeres authored Nov 7, 2023
2 parents bf51d56 + a0a81fe commit c822c10
Show file tree
Hide file tree
Showing 33 changed files with 2,496 additions and 2 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ __pycache__/
*$py.class

# C extensions
*.so
build/*.so

# Distribution / packaging
.Python
Expand Down Expand Up @@ -158,3 +158,4 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.vscode
27 changes: 27 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
cmake_minimum_required(VERSION 3.16.3...3.19.7 FATAL_ERROR)

project(SlicerBoneMorphing)

#-----------------------------------------------------------------------------
# Extension meta-information
set(EXTENSION_HOMEPAGE "https://www.slicer.org/wiki/Documentation/Nightly/Extensions/SlicerBoneMorphing")
set(EXTENSION_CATEGORY "Morphing")
set(EXTENSION_CONTRIBUTORS "Jan Heres (West Bohemian University)")
set(EXTENSION_DESCRIPTION "This extensions allows the user to generate and morph bone meshes based on its CT scans")
set(EXTENSION_ICONURL "https://www.example.com/Slicer/Extensions/SlicerBoneMorphing.png")
set(EXTENSION_SCREENSHOTURLS "https://www.example.com/Slicer/Extensions/SlicerBoneMorphing/Screenshots/1.png")
set(EXTENSION_DEPENDS "NA") # Specified as a list or "NA" if no dependencies

#-----------------------------------------------------------------------------
# Extension dependencies
find_package(Slicer REQUIRED)
include(${Slicer_USE_FILE})

#-----------------------------------------------------------------------------
# Extension modules
add_subdirectory(SlicerBoneMorphing)
## NEXT_MODULE

#-----------------------------------------------------------------------------
include(${Slicer_EXTENSION_GENERATE_CONFIG})
include(${Slicer_EXTENSION_CPACK})
126 changes: 125 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,126 @@
# SlicerBoneMorphing
Extentsion for 3D Slicer for bone mesh morphing.
<!-- TODO: Explain in more depth -->
Extension for 3D Slicer for bone mesh morphing.

At the moment, this module specializes for the *humerus* bone.

## Special thanks <3
Special thanks goes to my wonder colleague Eva C. Herbst (@evaherbst) and Arthur Port (@agporto) for creating the initial idea and their huge help during the development of this module!
Also, I would like to thank O. Hirose (@ohirose) for the research on BCPD/GBCPD and it's implementation (can be found [here](https://github.com/ohirose/bcpd))


## Installation
**Supported platforms:**
- Linux (x86_64)
- Windows (x86_64)
- MacOS (both x86_64 and ARM; Slicer runs through Rosetta on ARM-based Macs)


Steps:
- Download the latest ZIP package from Releases
- Extract the ZIP contents to your desired folder
- Open up 3D Slicer, go to Edit -> Application Settings
- In the modules section, add the extracted contents' path to "Additional Module Paths"
- Restart 3D Slicer

> **DISCLAIMER! After restarting, the installation process will begin. If there are any Python modules not available in Slicer, they will be installed, so the startup will take SIGNIFICANTLY MORE amount of time. Do not be scared, this is intended behaviour.**
## Usage
After a successful install, the module will be available in the **Morphing** section.
When switching to the module, you should be greeted with the following UI:

<p align="center">
<img src="docs/assets/ui.png" width="400px" height="900px">
</p>

The UI consists of **4** main sections
- Input
- Preprocessing
- Generation
- Postprocessing

## Architecture
<!-- TODO: Create a workflow diagram -->
To be added.

## Module sections

### Input section
This section is self-explanatory. Here, you choose two input models:
- Source = Source for the generation; This is the model that represents the
- Target = Model which is non-complete => Needs its missing portions generated

### Preprocessing section

#### <ins>Point cloud preprocessing</ins>
Before the generation process, we usually want to preprocess the model.
Reasons could be to remove unwanted **outliers** or to smooth out the models.
First of all, the model is converted to a *point cloud* to be able to effectively perform some preprocessing steps.

Then, a downsampling is performed. For this, you can configure the threshold for downsampling by the following parameter:
- **Downsampling distance threshold**

After the downsampling, we compute the normals of the point cloud. It uses a radius for which the normals are calculated and maximum number of neighbours. This can be adjusted with the following parameter:
- **Normals estimation radius**
- **Normals estimation max neighbours**

Also, we want calculate a *Fast point feature histogram* to somehow encode the local geometric properties. This method takes in two following parameters:
- **FPFH search radius** - Radius in which the FPFH is calculated in
- **FPFH max neighbours** - Maximum number of neighbours taken into account

#### <ins>Registration section</ins>
At this moment, we have our point clouds preprocessed and ready for the next step, which is the "registration" section.
Here we try to define and calculate how and how much we need to adjust the source mesh to match the target one.

For this, we will use the downsampled point clouds with their corresponding FPFHs from the previous step.
The concrete method we use is called **RANSAC**. It uses a process called "repeated random sub-sampling" to mitigate the effect of outliers and rotational differences as much as possible.
The behaviour of this algorithm can be adjusted by the following parameters:
- **Max iterations**
- **Distance threshold** - same meaning as in previous steps
- **Fitness threshold** - the lowest fitness between the point clouds to be accepted. The lower, the higher chance of finding a good fit. The higher, higher the chance that either *max iterations* are reached

The result of the *RANSAC* algorithm is a bit "raw". To get the best possible fit, we perform the **ICP registration algorithm** upon the result.
This can be tuned by the following parameter:
- **ICP Distance threshold** - same meaning as in previous steps

### Generation section
Since we now have a preprocessed meshes and with defined transformations from the *source* to the *target*, we can proceed to the **generation section**.

For this purpose, we use a method called [Bayesian coherent point drift](https://github.com/ohirose/bcpd). It falls into the *non-rigid registration* category of algorithms, which actually performs the deformation of the mesh to increase the fit of the source.
It takes in both meshes and deforms the source into the target, similarly as we've already done in the [Registration section](#preprocessing-section).
Due to the problem that BCPD allows for "unrealistic" deformations, we have done the pre-registration steps, which lets us mitigate the chance of getting into unrealistic deformations.

Now, BCPD allows for very fine adjustments of its behaviours using lots of different parameters. For the exact description of their effects, please refer to the documentation [here](https://github.com/ohirose/bcpd/blob/master/README.md).

> **Note: You do NOT have to perform any kind of installation process, the BCPD and its geodesic variant are already pre-built and preconfigured for immediate using in this module.**
**Not implemented options:**
- Terminal output
- File output

### Postprocessing section
After our models have been merged successfully, we still want to apply a slight amount of postprocessing to reach the most optimal results.
We are basically using a bit of **filtering and smoothing** to the meshes.
For these, we let you modify the following parameters:
- **Clustering scaling** - Scaled size of voxel for within vertices that are clustered together (additionally refer to [here](http://www.open3d.org/docs/0.7.0/python_api/open3d.geometry.simplify_vertex_clustering.html)
- **Smoothing iterations** - Number of iterations of mesh smoothing

After the whole process is done, both the generated mesh (source transformed into target, standalone) and the merged mesh (generated meshes merged with the target; "combined model") are import back into the current Slicer scene.

<p align="center">
<img src="docs/assets/results.png" width="400px" height="50px">
</p>

## FAQ
To be added.

## Troubleshooting
To be added.

## Contributors
<!-- These people are AWESOME! -->

<a href="https://github.com/HarryHeres/SlicerBoneMorphing/graphs/contributors">
<img src="https://contrib.rocks/image?repo=HarryHeres/SlicerBoneMorphing" />
</a>

Binary file added SlicerBoneMorphing.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions SlicerBoneMorphing/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#-----------------------------------------------------------------------------
set(MODULE_NAME SlicerBoneMorphing)

#-----------------------------------------------------------------------------
set(MODULE_PYTHON_SCRIPTS
${MODULE_NAME}.py
)

set(MODULE_PYTHON_RESOURCES
Resources/Icons/${MODULE_NAME}.png
Resources/UI/${MODULE_NAME}.ui
)

#-----------------------------------------------------------------------------
slicerMacroBuildScriptedModule(
NAME ${MODULE_NAME}
SCRIPTS ${MODULE_PYTHON_SCRIPTS}
RESOURCES ${MODULE_PYTHON_RESOURCES}
WITH_GENERIC_TESTS
)

#-----------------------------------------------------------------------------
if(BUILD_TESTING)

# Register the unittest subclass in the main script as a ctest.
# Note that the test will also be available at runtime.
slicer_add_python_unittest(SCRIPT ${MODULE_NAME}.py)

# Additional build-time testing
add_subdirectory(testing)
endif()
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Loading

0 comments on commit c822c10

Please sign in to comment.