From a1016f7987a5e59a907974484f41e809d4c42fdc Mon Sep 17 00:00:00 2001 From: HarryHeres Date: Wed, 17 Jul 2024 12:56:51 +0200 Subject: [PATCH 1/8] Adding Codeowners --- .github/CODEOWNERS | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..bf9deb7 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +@harryheres diff --git a/CHANGELOG.md b/CHANGELOG.md index 3716905..fe3803d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Changelog - Alert when installing Open3D (#3) - Formatting settings for flake8 +- Codeowners ### Changed - Naming of the generated model based on the selected target node From 84f6db566fa92d1a7ffbb751385e3c46a2d8f93c Mon Sep 17 00:00:00 2001 From: HarryHeres Date: Wed, 17 Jul 2024 13:00:33 +0200 Subject: [PATCH 2/8] Updating CODEOWNERS --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bf9deb7..67d045a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -@harryheres +@HarryHeres From 6dd5345033a839d9c8ce0a7d3d1323de4befed6b Mon Sep 17 00:00:00 2001 From: HarryHeres Date: Wed, 17 Jul 2024 15:21:04 +0200 Subject: [PATCH 3/8] Updated documentation and made changes to the code to fully reflect the docs. --- README.md | 88 +- .../Resources/UI/SlicerBoneMorphing.ui | 2679 +++++++++-------- SlicerBoneMorphing/src/logic/Constants.py | 7 +- SlicerBoneMorphing/src/logic/Enums.py | 12 +- .../src/logic/SlicerBoneMorphingLogic.py | 59 +- .../src/widget/SlicerBoneMorphingWidget.py | 4 +- docs/dev_docs.md | 4 + docs/how-to-use.md | 64 + 8 files changed, 1452 insertions(+), 1465 deletions(-) create mode 100644 docs/dev_docs.md create mode 100644 docs/how-to-use.md diff --git a/README.md b/README.md index 89e4682..485eb70 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,11 @@ # SlicerBoneMorphing - Extension for 3D Slicer for bone mesh morphing. At the moment, this module specializes for the *humerus* bone, but the use case is not limited to it. ## Special thanks -Special thanks goes to my wonderful colleagues Eva C. Herbst (@evaherbst) and Arthur Porto (@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)) +Special thanks goes to my wonderful colleagues [Eva C. Herbst](https://github.com/evaherbst) and [Arthur Porto](https://github.com/agporto) for creating the initial idea and their huge help during the development of this module! +Also, I would like to thank [O. Hirose](https://github.com/ohirose) for the research on BCPD/GBCPD and it's implementation (can be found [here](https://github.com/ohirose/bcpd)) ## Installation **Supported platforms:** @@ -15,97 +14,22 @@ Also, I would like to thank O. Hirose (@ohirose) for the research on BCPD/GBCPD - MacOS (both x86_64 and ARM; Slicer runs through Rosetta on ARM-based Macs) Steps: -- Download the latest ZIP package from Releases +- Download the latest ZIP package from [Releases](https://github.com/HarryHeres/SlicerBoneMorphing/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. +After a successful installation, the module will be available in the **Morphing** section. When switching to the module, you should be greeted with the following UI:

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

- -

- -## Module sections - -### Input section -This section is self-explanatory. Here, you choose two input models: -- Source = The mean model, i.e. a full humerus -- Target = Partial model to be reconstructed - -### Preprocessing section - -Before the generation process, we want to preprocess the model. -First of all is the option of downsampling. For this, you can configure the threshold for downsampling by the following parameter: -- **Downsampling distance threshold** - - If set to 0.0, no downsampling is performed - -After the downsampling, we compute the normals of the point cloud. -The computation needs a radius for which the normals are calculated and maximum number of neighbours. -These can be adjusted with the following parameters: -- **Normals estimation radius** - maximum radius in which points are considered neighbouring -- **Normals estimation max neighbours** - maximum number of neighbours taken into account - -Also, we need to calculate a *(Fast) point feature histogram* in order to encode the local geometric properties of the models. -This method uses the following parameters: -- **FPFH search radius** - maximum radius in which points are considered neighbouring -- **FPFH max neighbours** - maximum number of neighbours taken into account - -#### Registration -At this moment we have our models preprocessed and ready for the next step, which is the registration. -Here we calculate the rigid alignment of the models in order to pre-align them. -The concrete method we use is called **RANSAC** (random sample consensus). -The behaviour of this algorithm can be adjusted by the following parameters: -- **Max iterations** -- **Distance threshold** - maximum distance in which points are considered neighbouring -- **Fitness threshold** - the lowest fitness between the models to be accepted. - -The computed fit by the RANSAC algorithm is a bit "raw". To improve it further, we perform the **ICP** (Iterative closest points) algorithm. -This can be tuned by the following parameter: -- **ICP Distance threshold** - maximum distance in which points are considered neighbouring - -### Reconstruction section -Since we now have a preprocessed meshes and with defined transformations from the *source* to the *target*, we can proceed to the **reconstruction section**. -For the reconstruction we use the **BCPD** (Bayesian coherent point drift) algorithm. -Now, the BCPD allows for very fine adjustments of its behaviour using lots of different parameters. -For the exact description of their effects, please refer to the official 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 use in this module.** - -**Not implemented options:** -- Terminal output -- File output - -### Postprocessing section -After the model is reconstructed, we include a postprocessing section to slightly modify the result, if necessary. -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)) - - If set to 1.0, no scaling is performed -- **Smoothing iterations** - Number of iterations of mesh smoothing - - If set to 0, no smoothing is applied - -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. - -

- -

+## How to use +For guidance on how to use the module, please refer to this [Guide](./docs/how-to-use.md). ## Contributors A huge thank you to all of the contributors! diff --git a/SlicerBoneMorphing/Resources/UI/SlicerBoneMorphing.ui b/SlicerBoneMorphing/Resources/UI/SlicerBoneMorphing.ui index 5f9b7fd..e9e0715 100644 --- a/SlicerBoneMorphing/Resources/UI/SlicerBoneMorphing.ui +++ b/SlicerBoneMorphing/Resources/UI/SlicerBoneMorphing.ui @@ -1,1341 +1,1344 @@ - UI - - - - 0 - 0 - 564 - 1257 - - - - - - - Input - - - true - - - false - - - - - - <html><head/><body><p>Select a node that represents the reference (mean) model</p></body></html> - - - Source: - - - - - - - false - - - Select a node that represents the reference model - - - - vtkMRMLModelNode - - - - true - - - - - - - - - - - - - <html><head/><body><p>Select a node that represents the target (impartial) model</p></body></html> - - - Target: - - - - - - - Select a node that represents the target model (the one to be deformed) - - - - vtkMRMLModelNode - - - - true - - - - - - - - - - - - - - - - Preprocessing parameters - - - true - - - false - - - - - - Point cloud preprocessing - - - - - - Maximum distance in which points are considered neighbouring - - - Downsampling distance threshold: - - - - - - - The higher the number, the lower the distance threshold for the RANSAC registration - - - 100.000000000000000 - - - 55.000000000000000 - - - - - - - Normals estimation radius: - - - - - - - 100.000000000000000 - - - 4.000000000000000 - - - - - - - Normals estimation max neighbours: - - - - - - - 1 - - - 1000 - - - 100 - - - - - - - FPFH search radius: - - - - - - - 100.000000000000000 - - - 10.000000000000000 - - - - - - - FPFH max neighbours: - - - - - - - 1 - - - 1000 - - - 30 - - - - - - - - - - Registration - - - - - - Distance threshold: - - - - - - - 100.000000000000000 - - - 1.000000000000000 - - - - - - - The higher the number, the longer the registration process will take, but higher the chance to converge to a fit solution - - - Max iterations: - - - - - - - The higher the number, the longer the registration process will take, but higher the chance to converge to a fit solution - - - 1 - - - 30 - - - - - - - Fitness threshold: - - - - - - - 8 - - - 1.000000000000000 - - - 0.000100000000000 - - - 0.999000000000000 - - - - - - - ICP Distance threshold: - - - - - - - 100.000000000000000 - - - 1.000000000000000 - - - - - - - - - - - - - BCPD parameters - - - false - - - true - - - - - - Tuning parameters - - - true - - - false - - - - - - Omega. Outlier probability in (0,1). - - - Outlier probability (omega): - - - - - - - Omega. Outlier probability in (0,1). - - - - - - 3 - - - 0.001000000000000 - - - 0.999000000000000 - - - 0.001000000000000 - - - 0.100000000000000 - - - - - - - Lambda. Positive. It controls the expected length of deformation vectors. Smaller is longer. - - - Deformation vector length (lambda): - - - - - - - 3 - - - 100000.000000000000000 - - - 10.000000000000000 - - - - - - - Beta. Positive. It controls the range where deformation vectors are smoothed. - - - Deformation smoothing range (beta): - - - - - - - 3 - - - 100.000000000000000 - - - 10.000000000000000 - - - - - - - Gamma. Positive. It defines the randomness of the point matching at the beginning of the optimization. - - - Point matching randomness (gamma): - - - - - - - Gamma. Positive. It defines the randomness of the point matching at the beginning of the optimization. - - - 3 - - - 100.000000000000000 - - - 0.100000000000000 - - - 0.100000000000000 - - - - - - - Kappa. Positive. It controls the randomness of mixing coefficients. - - - Mixing coefficient randomness (kappa): - - - - - - - Kappa. Positive. It controls the randomness of mixing coefficients. Value 1000 is considered "infinity" - - - 3 - - - 1000.000000000000000 - - - 1000.000000000000000 - - - - - - - - - - false - - - Show more granular controls over the BCPD algorithm. DISCLAIMER: USE WITH CAUTION! - - - Show advanced controls - - - - - - - Advanced controls - - - - - - Kernel parameters - - - true - - - false - - - true - - - - - - Geodesic Kernel - - - - - - Tau. The rate controlling the balance between geodesic and Gaussian kernels. - - - Kernel balance rate (tau): - - - - - - - I have an input mesh - - - - - - - The file that defines a triangle mesh. - - - Input mesh path: - - - - - - - The number of neighbors for each node, required for k-NN graph construction. - - - kNN neighbours - - - - - - - The number of neighbors for each node, required for k-NN graph construction. - - - 10 - - - - - - - The file that defines a triangle mesh. - - - Absolute path to the input mesh file - - - - - - - The radius that defines neighbors for each node, required for k-NN graph construction. - - - kNN neighbor radius - - - - - - - The radius that defines neighbors for each node, required for k-NN graph construction. - - - 3 - - - 100.000000000000000 - - - 10.000000000000000 - - - - - - - Beta. Positive. Gaussian function's width. - - - Gaussian function width (beta): - - - - - - - Beta. Positive. Gaussian function's width. - - - 3 - - - 100.000000000000000 - - - 1.000000000000000 - - - - - - - K tilde. Positive. Rank constraint on G. - - - Rank constraint (K tilde): - - - - - - - K tilde. Positive. Rank constraint on G. - - - 3 - - - 100.000000000000000 - - - 1.000000000000000 - - - - - - - Epsilon. Positive. Acceptable condition number of G. - - - Acceptable condition number (epsilon): - - - - - - - Epsilon. Positive. Acceptable condition number of G. - - - 3 - - - 100.000000000000000 - - - 1.000000000000000 - - - - - - - 100.000000000000000 - - - 1.000000000000000 - - - - - - - - - - Standard kernel - - - - - - Kernel type: - - - - - - - - - - - - - Kernel type: - - - - - - - - - - - - - Acceleration - - - true - - - false - - - - - - Mode: - - - - - - - Manual acceleration methods. For optimal convergence, use both Nystorm and KD Tree search - - - Manual acceleration - - - false - - - - - - Nystorm method - - - true - - - true - - - - - - Samples for computing G - - - - - - - 1000000 - - - 70 - - - - - - - Samples for computing J - - - - - - - 1000000 - - - 300 - - - - - - - Random number seed for the Nystrom method. Reproducibility is guaranteed if the same number is specified. - - - Randomness seed - - - - - - - Random number seed for the Nystrom method. Reproducibility is guaranteed if the same number is specified. - - - 1 - - - 1000000 - - - - - - - - - - KD Tree Search - - - true - - - true - - - - - - Scale factor of sigma that defines areas to search for neighbors. - - - Scale factor: - - - - - - - Scale factor of sigma that defines areas to search for neighbors. - - - 3 - - - 100.000000000000000 - - - 1.000000000000000 - - - 7.000000000000000 - - - - - - - Maximum radius to search for neighbors. - - - Maximum radius - - - - - - - Maximum radius to search for neighbors. - - - 0.150000000000000 - - - - - - - The value of sigma at which the KD tree search is turned on. - - - Sigma threshold: - - - - - - - The value of sigma at which the KD tree search is turned on. - - - 3 - - - 100.000000000000000 - - - 0.200000000000000 - - - - - - - - - - - - - Automatic acceleration - - - - - - Variational Bazes Inference acceleration - - - VBI acceleration - - - true - - - - - - - Downsampling and deformation vector interpolation - - - BCPD++ acceleration - - - true - - - - - - - - - - - - - - - - Note: Downsampling automaticallz activates the deformation vecttor interpolation - - - Downsampling - - - true - - - false - - - true - - - - - - Downsampling options. For syntax, please check the documentation - - - Options: - - - - - - - Downsampling options. For syntax, please check the documentation - - - B,5000,0.08 - - - - - - - - - - Convergence - - - true - - - false - - - - - - Tolerance: - - - - - - - 8 - - - 1.000000000000000 - - - 0.000100000000000 - - - 0.000100000000000 - - - - - - - Max number of iterations: - - - - - - - 1 - - - 1000000 - - - 1000 - - - - - - - 1 - - - 1000 - - - 30 - - - - - - - Min number of iterations: - - - - - - - - - - Normalization - - - false - - - - - - Please, refer to the documentation for the explanation of choices - - - Options: - - - - - - - Please, refer to the documentation for the explanation of choices - - - - - - - - - - - - - - - - Postprocessing parameters - - - false - - - - - - Clustering scaling: - - - - - - - 0.000000000000000 - - - 1.000000000000000 - - - - - - - Smoothing iterations: - - - - - - - 0 - - - 1000 - - - - - - - - - - Reset parameters to default - - - - - - - true - - - Generate - - - - - - - - ctkCollapsibleButton - QWidget -
ctkCollapsibleButton.h
- 1 -
- - ctkCollapsibleGroupBox - QGroupBox -
ctkCollapsibleGroupBox.h
- 1 -
- - qMRMLNodeComboBox - QWidget -
qMRMLNodeComboBox.h
-
- - qMRMLWidget - QWidget -
qMRMLWidget.h
- 1 -
-
- - - - UI - mrmlSceneChanged(vtkMRMLScene*) - sourceNodeSelectionBox - setMRMLScene(vtkMRMLScene*) - - - 272 - 237 - - - 335 - 57 - - - - - bcpdAdvancedParametersCheckBox - toggled(bool) - bcpdAdvancedControlsGroupBox - setVisible(bool) - - - 114 - 577 - - - 274 - 1065 - - - - - bcpdGeodesicKernelInputMeshCheckBox - toggled(bool) - bcpdGeodesicKernelInputMeshLabel - setVisible(bool) - - - 156 - 818 - - - 132 - 847 - - - - - bcpdGeodesicKernelInputMeshCheckBox - toggled(bool) - bcpdGeodesicKernelInputMeshLineEdit - setVisible(bool) - - - 156 - 818 - - - 611 - 847 - - - - - bcpdGeodesicKernelInputMeshCheckBox - toggled(bool) - bcpdGeodesicKernelNeighboursLabel - setHidden(bool) - - - 156 - 818 - - - 130 - 878 - - - - - bcpdGeodesicKernelInputMeshCheckBox - toggled(bool) - bcpdGeodesicKernelNeighboursSpinBox - setHidden(bool) - - - 156 - 818 - - - 611 - 878 - - - - - bcpdGeodesicKernelInputMeshCheckBox - toggled(bool) - bcpdGeodesicKernelRadiusLabel - setHidden(bool) - - - 156 - 818 - - - 145 - 910 - - - - - bcpdGeodesicKernelInputMeshCheckBox - toggled(bool) - bcpdGeodesicKernelRadiusDoubleSpinBox - setHidden(bool) - - - 156 - 818 - - - 611 - 910 - - - - - bcpdAccelerationAutomaticPlusPlusCheckBox - toggled(bool) - bcpdDownsamplingCollapsibleGroupBox - setHidden(bool) - - - 134 - 1207 - - - 102 - 1145 - - - - + UI + + + + 0 + 0 + 638 + 1158 + + + + + + + Input + + + true + + + false + + + + + + <html><head/><body><p>Select a node that represents the reference (mean) model</p></body></html> + + + Source: + + + + + + + false + + + Select a node that represents the reference model + + + + vtkMRMLModelNode + + + + true + + + + + + + + + + + + + <html><head/><body><p>Select a node that represents the target (impartial) model</p></body></html> + + + Target: + + + + + + + Select a node that represents the target model (the one to be deformed) + + + + vtkMRMLModelNode + + + + true + + + + + + + + + + + + + + + + Preprocessing parameters + + + true + + + false + + + + + + Point cloud preprocessing + + + + + + Maximum distance in which points are considered neighbouring + + + Downsampling voxel size + + + + + + + The higher the number, the lower the distance threshold for the RANSAC registration + + + 100.000000000000000 + + + 0.000000000000000 + + + + + + + Normals estimation radius: + + + + + + + 100.000000000000000 + + + 4.000000000000000 + + + + + + + Normals estimation max neighbours: + + + + + + + 1 + + + 1000 + + + 100 + + + + + + + FPFH search radius: + + + + + + + 100.000000000000000 + + + 10.000000000000000 + + + + + + + FPFH max neighbours: + + + + + + + 1 + + + 1000 + + + 30 + + + + + + + + + + Registration + + + + + + Distance threshold: + + + + + + + 100.000000000000000 + + + 1.000000000000000 + + + + + + + The higher the number, the longer the registration process will take, but higher the chance to converge to a fit solution + + + Max iterations: + + + + + + + The higher the number, the longer the registration process will take, but higher the chance to converge to a fit solution + + + 1 + + + 1000000 + + + 100000 + + + + + + + Fitness threshold: + + + + + + + 8 + + + 1.000000000000000 + + + 0.000100000000000 + + + 0.999000000000000 + + + + + + + ICP Distance threshold: + + + + + + + 100.000000000000000 + + + 1.000000000000000 + + + + + + + + + + + + + BCPD parameters + + + false + + + true + + + + + + Tuning parameters + + + true + + + false + + + + + + Omega. Outlier probability in (0,1). + + + Outlier probability (omega): + + + + + + + Omega. Outlier probability in (0,1). + + + + + + 3 + + + 0.001000000000000 + + + 0.999000000000000 + + + 0.001000000000000 + + + 0.100000000000000 + + + + + + + Lambda. Positive. It controls the expected length of deformation vectors. Smaller is longer. + + + Deformation vector length (lambda): + + + + + + + 3 + + + 100000.000000000000000 + + + 10.000000000000000 + + + + + + + Beta. Positive. It controls the range where deformation vectors are smoothed. + + + Deformation smoothing range (beta): + + + + + + + 3 + + + 100.000000000000000 + + + 10.000000000000000 + + + + + + + Gamma. Positive. It defines the randomness of the point matching at the beginning of the optimization. + + + Point matching randomness (gamma): + + + + + + + Gamma. Positive. It defines the randomness of the point matching at the beginning of the optimization. + + + 3 + + + 100.000000000000000 + + + 0.100000000000000 + + + 0.100000000000000 + + + + + + + Kappa. Positive. It controls the randomness of mixing coefficients. + + + Mixing coefficient randomness (kappa): + + + + + + + Kappa. Positive. It controls the randomness of mixing coefficients. Value 1000 is considered "infinity" + + + 3 + + + 1000.000000000000000 + + + 1000.000000000000000 + + + + + + + + + + false + + + Show more granular controls over the BCPD algorithm. DISCLAIMER: USE WITH CAUTION! + + + Show advanced controls + + + + + + + Advanced controls + + + + + + Kernel parameters + + + true + + + false + + + true + + + + + + Geodesic Kernel + + + + + + Tau. The rate controlling the balance between geodesic and Gaussian kernels. + + + Kernel balance rate (tau): + + + + + + + I have an input mesh + + + + + + + The file that defines a triangle mesh. + + + Input mesh path: + + + + + + + The number of neighbors for each node, required for k-NN graph construction. + + + kNN neighbours + + + + + + + The number of neighbors for each node, required for k-NN graph construction. + + + 10 + + + + + + + The file that defines a triangle mesh. + + + Absolute path to the input mesh file + + + + + + + The radius that defines neighbors for each node, required for k-NN graph construction. + + + kNN neighbor radius + + + + + + + The radius that defines neighbors for each node, required for k-NN graph construction. + + + 3 + + + 100.000000000000000 + + + 10.000000000000000 + + + + + + + Beta. Positive. Gaussian function's width. + + + Gaussian function width (beta): + + + + + + + Beta. Positive. Gaussian function's width. + + + 3 + + + 100.000000000000000 + + + 1.000000000000000 + + + + + + + K tilde. Positive. Rank constraint on G. + + + Rank constraint (K tilde): + + + + + + + K tilde. Positive. Rank constraint on G. + + + 3 + + + 100.000000000000000 + + + 1.000000000000000 + + + + + + + Epsilon. Positive. Acceptable condition number of G. + + + Acceptable condition number (epsilon): + + + + + + + Epsilon. Positive. Acceptable condition number of G. + + + 3 + + + 100.000000000000000 + + + 1.000000000000000 + + + + + + + 100.000000000000000 + + + 1.000000000000000 + + + + + + + + + + Standard kernel + + + + + + Kernel type: + + + + + + + + + + + + + Kernel type: + + + + + + + + + + + + + Acceleration + + + true + + + false + + + + + + Mode: + + + + + + + Manual acceleration methods. For optimal convergence, use both Nystorm and KD Tree search + + + Manual acceleration + + + false + + + + + + Nystorm method + + + true + + + true + + + + + + Samples for computing G + + + + + + + 1000000 + + + 70 + + + + + + + Samples for computing J + + + + + + + 1000000 + + + 300 + + + + + + + Random number seed for the Nystrom method. Reproducibility is guaranteed if the same number is specified. + + + Randomness seed + + + + + + + Random number seed for the Nystrom method. Reproducibility is guaranteed if the same number is specified. + + + 1 + + + 1000000 + + + + + + + + + + KD Tree Search + + + true + + + true + + + + + + Scale factor of sigma that defines areas to search for neighbors. + + + Scale factor: + + + + + + + Scale factor of sigma that defines areas to search for neighbors. + + + 3 + + + 100.000000000000000 + + + 1.000000000000000 + + + 7.000000000000000 + + + + + + + Maximum radius to search for neighbors. + + + Maximum radius + + + + + + + Maximum radius to search for neighbors. + + + 0.150000000000000 + + + + + + + The value of sigma at which the KD tree search is turned on. + + + Sigma threshold: + + + + + + + The value of sigma at which the KD tree search is turned on. + + + 3 + + + 100.000000000000000 + + + 0.200000000000000 + + + + + + + + + + + + + Automatic acceleration + + + + + + Variational Bazes Inference acceleration + + + VBI acceleration + + + true + + + + + + + Downsampling and deformation vector interpolation + + + BCPD++ acceleration + + + true + + + + + + + + + + + + + + + + Note: Downsampling automaticallz activates the deformation vecttor interpolation + + + Downsampling + + + true + + + false + + + true + + + + + + Downsampling options. For syntax, please check the documentation + + + Options: + + + + + + + Downsampling options. For syntax, please check the documentation + + + B,5000,0.08 + + + + + + + + + + Convergence + + + true + + + false + + + + + + Tolerance: + + + + + + + 8 + + + 1.000000000000000 + + + 0.000100000000000 + + + 0.000100000000000 + + + + + + + Max number of iterations: + + + + + + + 1 + + + 1000000 + + + 1000 + + + + + + + 1 + + + 1000 + + + 30 + + + + + + + Min number of iterations: + + + + + + + + + + Normalization + + + false + + + + + + Please, refer to the documentation for the explanation of choices + + + Options: + + + + + + + Please, refer to the documentation for the explanation of choices + + + + + + + + + + + + + + + + Postprocessing parameters + + + false + + + + + + Clustering scaling: + + + + + + + 0.000000000000000 + + + 1.000000000000000 + + + + + + + Smoothing iterations: + + + + + + + 0 + + + 1000 + + + + + + + + + + Reset parameters to default + + + + + + + true + + + Generate + + + + + + + + ctkCollapsibleButton + QWidget +
ctkCollapsibleButton.h
+ 1 +
+ + ctkCollapsibleGroupBox + QGroupBox +
ctkCollapsibleGroupBox.h
+ 1 +
+ + qMRMLNodeComboBox + QWidget +
qMRMLNodeComboBox.h
+
+ + qMRMLWidget + QWidget +
qMRMLWidget.h
+ 1 +
+
+ + + + UI + mrmlSceneChanged(vtkMRMLScene*) + sourceNodeSelectionBox + setMRMLScene(vtkMRMLScene*) + + + 272 + 237 + + + 335 + 57 + + + + + bcpdAdvancedParametersCheckBox + toggled(bool) + bcpdAdvancedControlsGroupBox + setVisible(bool) + + + 114 + 577 + + + 274 + 1065 + + + + + bcpdGeodesicKernelInputMeshCheckBox + toggled(bool) + bcpdGeodesicKernelInputMeshLabel + setVisible(bool) + + + 156 + 818 + + + 132 + 847 + + + + + bcpdGeodesicKernelInputMeshCheckBox + toggled(bool) + bcpdGeodesicKernelInputMeshLineEdit + setVisible(bool) + + + 156 + 818 + + + 611 + 847 + + + + + bcpdGeodesicKernelInputMeshCheckBox + toggled(bool) + bcpdGeodesicKernelNeighboursLabel + setHidden(bool) + + + 156 + 818 + + + 130 + 878 + + + + + bcpdGeodesicKernelInputMeshCheckBox + toggled(bool) + bcpdGeodesicKernelNeighboursSpinBox + setHidden(bool) + + + 156 + 818 + + + 611 + 878 + + + + + bcpdGeodesicKernelInputMeshCheckBox + toggled(bool) + bcpdGeodesicKernelRadiusLabel + setHidden(bool) + + + 156 + 818 + + + 145 + 910 + + + + + bcpdGeodesicKernelInputMeshCheckBox + toggled(bool) + bcpdGeodesicKernelRadiusDoubleSpinBox + setHidden(bool) + + + 156 + 818 + + + 611 + 910 + + + + + bcpdAccelerationAutomaticPlusPlusCheckBox + toggled(bool) + bcpdDownsamplingCollapsibleGroupBox + setHidden(bool) + + + 134 + 1207 + + + 102 + 1145 + + + +
diff --git a/SlicerBoneMorphing/src/logic/Constants.py b/SlicerBoneMorphing/src/logic/Constants.py index b59a5a0..b57a7b0 100644 --- a/SlicerBoneMorphing/src/logic/Constants.py +++ b/SlicerBoneMorphing/src/logic/Constants.py @@ -85,13 +85,13 @@ ### PREPROCESSING PARAMETERS ### PREPROCESSING_KEY = "preprocessing" -PREPROCESSING_KEY_DOWNSAMPLING_DISTANCE_THRESHOLD = "ddt" +PREPROCESSING_KEY_DOWNSAMPLING_VOXEL_SIZE = "dvs" PREPROCESSING_KEY_NORMALS_ESTIMATION_RADIUS = "ner" PREPROCESSING_KEY_FPFH_ESTIMATION_RADIUS = "fer" PREPROCESSING_KEY_MAX_NN_NORMALS = "mnnn" PREPROCESSING_KEY_MAX_NN_FPFH = "mnf" -PREPROCESSING_DEFAULT_VALUE_DOWNSAMPLING_DISTANCE_THRESHOLD = 0.05 +PREPROCESSING_DEFAULT_VALUE_DOWNSAMPLING_VOXEL_SIZE = 0.0 PREPROCESSING_DEFAULT_VALUE_RADIUS_NORMAL_SCALE = 0.5 PREPROCESSING_DEFAULT_VALUE_RADIUS_FEATURE_SCALE = 10 PREPROCESSING_DEFAULT_VALUE_MAX_NN_NORMALS = 10 @@ -105,8 +105,9 @@ REGISTRATION_DEFAULT_VALUE_DISTANCE_THRESHOLD = 1 REGISTRATION_DEFAULT_VALUE_FITNESS_THRESHOLD = 0.999 -REGISTRATION_DEFAULT_VALUE_MAX_ITERATIONS = 30 +REGISTRATION_DEFAULT_VALUE_MAX_ITERATIONS = 100000 REGISTRATION_DEFAULT_VALUE_ICP_DISTANCE_THRESHOLD = 1 +REGISTRATION_DEFAULT_VALUE_RANSAC_CONVERGENCE_CONFIDENCE = 1.0 # Leave at 1.0 to NOT terminate early ### POSTPROCESSING PARAMETERS ### POSTPROCESSING_KEY = "postprocessing" diff --git a/SlicerBoneMorphing/src/logic/Enums.py b/SlicerBoneMorphing/src/logic/Enums.py index 751d850..957b942 100644 --- a/SlicerBoneMorphing/src/logic/Enums.py +++ b/SlicerBoneMorphing/src/logic/Enums.py @@ -1,8 +1,10 @@ from enum import Enum + class BcpdKernelType(Enum): - STANDARD = 0 - GEODESIC = 1 + STANDARD = 0 + GEODESIC = 1 + class BcpdStandardKernel(Enum): G0 = 0 @@ -10,12 +12,14 @@ class BcpdStandardKernel(Enum): G2 = 2 G3 = 3 + class BcpdNormalizationOptions(Enum): E = 0 - X = 1 + X = 1 Y = 2 N = 3 + class BcpdAccelerationMode(Enum): AUTOMATIC = 0 - MANUAL = 1 + MANUAL = 1 diff --git a/SlicerBoneMorphing/src/logic/SlicerBoneMorphingLogic.py b/SlicerBoneMorphing/src/logic/SlicerBoneMorphingLogic.py index 8a432bf..641e125 100644 --- a/SlicerBoneMorphing/src/logic/SlicerBoneMorphingLogic.py +++ b/SlicerBoneMorphing/src/logic/SlicerBoneMorphingLogic.py @@ -215,7 +215,7 @@ def __preprocess_model( source_pcd_downsampled, source_pcd_fpfh = self.__preprocess_point_cloud( source_pcd, - parameters[const.PREPROCESSING_KEY_DOWNSAMPLING_DISTANCE_THRESHOLD], + parameters[const.PREPROCESSING_KEY_DOWNSAMPLING_VOXEL_SIZE], parameters[const.PREPROCESSING_KEY_NORMALS_ESTIMATION_RADIUS], parameters[const.PREPROCESSING_KEY_FPFH_ESTIMATION_RADIUS], parameters[const.PREPROCESSING_KEY_MAX_NN_NORMALS], @@ -224,7 +224,7 @@ def __preprocess_model( target_pcd_downsampled, target_pcd_fpfh = self.__preprocess_point_cloud( target_pcd, - parameters[const.PREPROCESSING_KEY_DOWNSAMPLING_DISTANCE_THRESHOLD], + parameters[const.PREPROCESSING_KEY_DOWNSAMPLING_VOXEL_SIZE], parameters[const.PREPROCESSING_KEY_NORMALS_ESTIMATION_RADIUS], parameters[const.PREPROCESSING_KEY_FPFH_ESTIMATION_RADIUS], parameters[const.PREPROCESSING_KEY_MAX_NN_NORMALS], @@ -257,7 +257,7 @@ def __preprocess_model( def __preprocess_point_cloud( self, pcd: o3d.geometry.PointCloud, - downsampling_distance_threshold: float, + downsampling_voxel_size: float, normals_estimation_radius: float, fpfh_estimation_radius: float, max_nn_normals: int, @@ -282,8 +282,8 @@ def __preprocess_point_cloud( - [1] = FPFH ''' - if downsampling_distance_threshold > 0.0: - pcd = pcd.voxel_down_sample(downsampling_distance_threshold) + if downsampling_voxel_size > 0.0: + pcd = pcd.voxel_down_sample(downsampling_voxel_size) pcd.estimate_normals(o3d.geometry.KDTreeSearchParamHybrid(radius=normals_estimation_radius, max_nn=max_nn_normals)) @@ -318,39 +318,26 @@ def __ransac_pcd_registration( RANSAC registration result ''' - fitness = 0 - count = 0 - best_result = None - fitness_max = 1 - - while (fitness < fitness_threshold and fitness < fitness_max and count < max_iterations): - result = o3d.pipelines.registration.registration_ransac_based_on_feature_matching( - source_pcd_down, - target_pcd_down, - source_fpfh, - target_fpfh, - True, - distance_threshold, + result = o3d.pipelines.registration.registration_ransac_based_on_feature_matching( + source_pcd_down, + target_pcd_down, + source_fpfh, + target_fpfh, + True, + distance_threshold, + o3d.pipelines.registration. + TransformationEstimationPointToPoint(True), + 3, + [ o3d.pipelines.registration. - TransformationEstimationPointToPoint(True), - 3, - [ - o3d.pipelines.registration. - CorrespondenceCheckerBasedOnEdgeLength(0.9), - o3d.pipelines.registration. - CorrespondenceCheckerBasedOnDistance(distance_threshold) - ], - # NOTE: Just for earlier termination, but still needs the outer loop for proper convergence - o3d.pipelines.registration.RANSACConvergenceCriteria(100000, fitness_threshold) - ) - - if result.fitness > fitness and result.fitness < 1: - fitness = result.fitness - best_result = result - - count += 1 + CorrespondenceCheckerBasedOnEdgeLength(0.9), + o3d.pipelines.registration. + CorrespondenceCheckerBasedOnDistance(distance_threshold) + ], + o3d.pipelines.registration.RANSACConvergenceCriteria(max_iteration=max_iterations, confidence=const.REGISTRATION_DEFAULT_VALUE_RANSAC_CONVERGENCE_CONFIDENCE) + ) - return best_result + return result def __deformable_registration( self, diff --git a/SlicerBoneMorphing/src/widget/SlicerBoneMorphingWidget.py b/SlicerBoneMorphing/src/widget/SlicerBoneMorphingWidget.py index 230ee43..a95b8bf 100644 --- a/SlicerBoneMorphing/src/widget/SlicerBoneMorphingWidget.py +++ b/SlicerBoneMorphing/src/widget/SlicerBoneMorphingWidget.py @@ -51,7 +51,7 @@ def __setup_ui(self) -> None: def __reset_parameters_to_default(self) -> None: ## Preprocessing parameters ## - self.__ui.preprocessingDownsamplingDistanceThresholdDoubleSpinBox.value = const.PREPROCESSING_DEFAULT_VALUE_DOWNSAMPLING_DISTANCE_THRESHOLD + self.__ui.preprocessingDownsamplingVoxelSizeDoubleSpinBox.value = const.PREPROCESSING_DEFAULT_VALUE_DOWNSAMPLING_VOXEL_SIZE self.__ui.preprocessingNormalsEstimationRadiusDoubleSpinBox.value = const.PREPROCESSING_DEFAULT_VALUE_RADIUS_NORMAL_SCALE self.__ui.preprocessingNormalsEstimationMaxNeighboursSpinBox.value = const.PREPROCESSING_DEFAULT_VALUE_MAX_NN_NORMALS self.__ui.preprocessingFpfhRadiusDoubleSpinBox.value = const.PREPROCESSING_DEFAULT_VALUE_RADIUS_FEATURE_SCALE @@ -160,7 +160,7 @@ def __parse_parameters_preprocessing(self) -> dict: params = {} # Preprocessing - params[const.PREPROCESSING_KEY_DOWNSAMPLING_DISTANCE_THRESHOLD] = self.__ui.preprocessingDownsamplingDistanceThresholdDoubleSpinBox.value + params[const.PREPROCESSING_KEY_DOWNSAMPLING_VOXEL_SIZE] = self.__ui.preprocessingDownsamplingVoxelSizeDoubleSpinBox.value params[const.PREPROCESSING_KEY_NORMALS_ESTIMATION_RADIUS] = self.__ui.preprocessingNormalsEstimationRadiusDoubleSpinBox.value params[const.PREPROCESSING_KEY_MAX_NN_NORMALS] = self.__ui.preprocessingNormalsEstimationMaxNeighboursSpinBox.value params[const.PREPROCESSING_KEY_FPFH_ESTIMATION_RADIUS] = self.__ui.preprocessingFpfhRadiusDoubleSpinBox.value diff --git a/docs/dev_docs.md b/docs/dev_docs.md new file mode 100644 index 0000000..f88191e --- /dev/null +++ b/docs/dev_docs.md @@ -0,0 +1,4 @@ +## Architecture +

+ +

diff --git a/docs/how-to-use.md b/docs/how-to-use.md new file mode 100644 index 0000000..35a1877 --- /dev/null +++ b/docs/how-to-use.md @@ -0,0 +1,64 @@ +# Guide on how to use the module + +### Input section +- Source = The mean model, i.e. a full humerus +- Target = Partial model to be reconstructed + +### Preprocessing section +Before the generation process, it is possible to preprocess the models. +First is the option to downsample the models in order to relieve some of the computation. +You can configure the amount of downsampling by the following parameter: +- **Downsampling voxel size** + - If set to 0.0, no downsampling is performed + +After the downsampling, we compute the normals of the point cloud. +The computation needs a radius for which the normals are calculated and maximum number of neighbours. +These can be adjusted with the following parameters: +- **Normals estimation radius** - maximum radius in which points are considered neighbouring +- **Normals estimation max neighbours** - maximum number of neighbours taken into account + +Also, we need to calculate a *(Fast) point feature histogram* in order to encode the local geometric properties of the models. +This method uses the following parameters: +- **FPFH search radius** - maximum radius in which points are considered neighbouring +- **FPFH max neighbours** - maximum number of neighbours taken into account + +#### Registration +At this moment we have our models preprocessed and ready for the next step, which is the registration. +Here we calculate the rigid alignment of the models. +We use the **RANSAC** (Random Sample Consensus) for the computation. +The behaviour of this algorithm can be adjusted by the following parameters: +- **Max iterations** - maximum number of iterations of the algorithm +- **Distance threshold** - maximum distance in which points are considered neighbouring +- **Fitness threshold** - the lowest fitness between the models to be accepted. + +The computed fit by the RANSAC algorithm is a bit "raw". +To improve it further, we perform the **ICP** (Iterative closest points) algorithm. +This algorithm can be tuned by the following parameter: +- **ICP Distance threshold** - maximum corresponding points-pair distance + +### Reconstruction section +Since we now have a preprocessed meshes and with defined transformations from the *source* to the *target*, we can proceed to the **reconstruction section**. +For the reconstruction we use the **BCPD** (Bayesian coherent point drift) algorithm. +Now, the BCPD allows for very fine adjustments of its behaviour using lots of different parameters. +For the exact description of their effects, please refer to the official 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 use in this module.** + +**Not implemented options:** +- Terminal output +- File output + +### Postprocessing section +After the model is reconstructed, we a postprocessing section is included for you to be able to slightly modify the result, if necessary. +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)) + - If set to 1.0, no scaling is performed +- **Smoothing iterations** - Number of iterations of mesh smoothing + - If set to 0, no smoothing is applied + +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. + +

+ +

From dc5114f614a784317e1583fbdce01f3d34bd1d5e Mon Sep 17 00:00:00 2001 From: HarryHeres Date: Wed, 17 Jul 2024 15:25:09 +0200 Subject: [PATCH 4/8] Guide update --- docs/how-to-use.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/how-to-use.md b/docs/how-to-use.md index 35a1877..9e6ad9d 100644 --- a/docs/how-to-use.md +++ b/docs/how-to-use.md @@ -57,8 +57,4 @@ For these, we let you modify the following parameters: - **Smoothing iterations** - Number of iterations of mesh smoothing - If set to 0, no smoothing is applied -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. - -

- -

+After the whole process is done, the generated mesh is imported back into the current Slicer scene. From dadb5c9d9b279ebdc6318c0c722f7c4d9843da1e Mon Sep 17 00:00:00 2001 From: HarryHeres Date: Wed, 17 Jul 2024 15:33:37 +0200 Subject: [PATCH 5/8] Added version tag into the help text --- SlicerBoneMorphing/SlicerBoneMorphing.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SlicerBoneMorphing/SlicerBoneMorphing.py b/SlicerBoneMorphing/SlicerBoneMorphing.py index f92b054..f8a8860 100644 --- a/SlicerBoneMorphing/SlicerBoneMorphing.py +++ b/SlicerBoneMorphing/SlicerBoneMorphing.py @@ -16,7 +16,9 @@ def __init__(self, parent): self.parent.helpText = """ This module gives a user ability to recreate bone mesh models based on their partial scan. - Please, start with importing your model and checking out the options on the left side afterwards.""" + Please, start with importing your model and checking out the options on the left side afterwards. + Version: 0.2.0-rc.1 + """ self.parent.acknowledgementText = """ Credits: Jan Heres From 70f946f9210ebc21f3c4a96d5786b03d2a68b2ba Mon Sep 17 00:00:00 2001 From: HarryHeres Date: Wed, 17 Jul 2024 19:32:58 +0200 Subject: [PATCH 6/8] Added pipeline visualization settings --- .../Resources/UI/SlicerBoneMorphing.ui | 78 ++++++++++++++++++- SlicerBoneMorphing/src/logic/Constants.py | 66 ++++++++-------- .../src/logic/SlicerBoneMorphingLogic.py | 27 ++++--- .../src/widget/SlicerBoneMorphingWidget.py | 18 ++++- 4 files changed, 140 insertions(+), 49 deletions(-) diff --git a/SlicerBoneMorphing/Resources/UI/SlicerBoneMorphing.ui b/SlicerBoneMorphing/Resources/UI/SlicerBoneMorphing.ui index e9e0715..f5ef3fa 100644 --- a/SlicerBoneMorphing/Resources/UI/SlicerBoneMorphing.ui +++ b/SlicerBoneMorphing/Resources/UI/SlicerBoneMorphing.ui @@ -11,6 +11,57 @@ + + + + Visualization + + + true + + + + + + If checked, the pipeline steps will be visualized using Open3D + + + Visualize pipeline steps + + + + + + + Coloring + + + + + + + + + Source model color + + + + + + + Target model color + + + + + + + + + + + + @@ -94,7 +145,7 @@ - Preprocessing parameters + Preprocessing true @@ -307,7 +358,7 @@ - BCPD parameters + BCPD false @@ -1107,7 +1158,7 @@ - Postprocessing parameters + Postprocessing false @@ -1182,6 +1233,11 @@
ctkCollapsibleGroupBox.h
1 + + ctkColorPickerButton + QPushButton +
ctkColorPickerButton.h
+
qMRMLNodeComboBox QWidget @@ -1340,5 +1396,21 @@ + + visualizationVisualizeCheckBox + toggled(bool) + visualizationModelColorGroupBox + setVisible(bool) + + + 163 + 44 + + + 318 + 192 + + + diff --git a/SlicerBoneMorphing/src/logic/Constants.py b/SlicerBoneMorphing/src/logic/Constants.py index b57a7b0..4b8c5ac 100644 --- a/SlicerBoneMorphing/src/logic/Constants.py +++ b/SlicerBoneMorphing/src/logic/Constants.py @@ -1,17 +1,47 @@ ### RETURN VALUES ### from src.logic.Enums import BcpdAccelerationMode, BcpdKernelType, BcpdNormalizationOptions, BcpdStandardKernel +import qt EXIT_OK = 0 EXIT_FAILURE = 1 BCPD_MULTIPLE_VALUES_SEPARATOR = "," -""" - Used when one parameter have multiple values, e.g. -G [string,real,file] -""" -VALUE_NODE_NOT_SELECTED = 0 # Via Slicer documentation +VISUALIZATION_KEY = "visualization" +VISUALIZATION_KEY_SHOULD_VISUALIZE = "sv" +VISUALIZATION_KEY_SOURCE_MODEL_COLOR = "smc" +VISUALIZATION_KEY_TARGET_MODEL_COLOR = "tmc" -### BCPD PARAMETERS### +VISUALIZATION_DEFAULT_VALUE_SOURCE_MODEL_COLOR = qt.QColor(255, 0, 0, 0) +VISUALIZATION_DEFAULT_VALUE_TARGET_MODEL_COLOR = qt.QColor(0, 255, 0, 0) + +### PREPROCESSING PARAMETERS ### +PREPROCESSING_KEY = "preprocessing" +PREPROCESSING_KEY_DOWNSAMPLING_VOXEL_SIZE = "dvs" +PREPROCESSING_KEY_NORMALS_ESTIMATION_RADIUS = "ner" +PREPROCESSING_KEY_FPFH_ESTIMATION_RADIUS = "fer" +PREPROCESSING_KEY_MAX_NN_NORMALS = "mnnn" +PREPROCESSING_KEY_MAX_NN_FPFH = "mnf" + +PREPROCESSING_DEFAULT_VALUE_DOWNSAMPLING_VOXEL_SIZE = 0.0 +PREPROCESSING_DEFAULT_VALUE_RADIUS_NORMAL_SCALE = 0.5 +PREPROCESSING_DEFAULT_VALUE_RADIUS_FEATURE_SCALE = 10 +PREPROCESSING_DEFAULT_VALUE_MAX_NN_NORMALS = 10 +PREPROCESSING_DEFAULT_VALUE_MAX_NN_FPFH = 100 + +### REGISTRATION PARAMETERS ### +REGISTRATION_KEY_DISTANCE_THRESHOLD = "rdt" +REGISTRATION_KEY_FITNESS_THRESHOLD = "rft" +REGISTRATION_KEY_MAX_ITERATIONS = "rmi" +REGISTRATION_KEY_ICP_DISTANCE_THRESHOLD = "idt" + +REGISTRATION_DEFAULT_VALUE_DISTANCE_THRESHOLD = 1 +REGISTRATION_DEFAULT_VALUE_FITNESS_THRESHOLD = 0.999 +REGISTRATION_DEFAULT_VALUE_MAX_ITERATIONS = 100000 +REGISTRATION_DEFAULT_VALUE_ICP_DISTANCE_THRESHOLD = 1 +REGISTRATION_DEFAULT_VALUE_RANSAC_CONVERGENCE_CONFIDENCE = 1.0 # Leave at 1.0 to NOT terminate early + +### BCPD PARAMETERS ### BCPD_KEY = "bcpd" ## Tuning ## @@ -83,32 +113,6 @@ ## Normalization ## BCPD_DEFAULT_VALUE_NORMALIZATION_OPTIONS = BcpdNormalizationOptions.X.value -### PREPROCESSING PARAMETERS ### -PREPROCESSING_KEY = "preprocessing" -PREPROCESSING_KEY_DOWNSAMPLING_VOXEL_SIZE = "dvs" -PREPROCESSING_KEY_NORMALS_ESTIMATION_RADIUS = "ner" -PREPROCESSING_KEY_FPFH_ESTIMATION_RADIUS = "fer" -PREPROCESSING_KEY_MAX_NN_NORMALS = "mnnn" -PREPROCESSING_KEY_MAX_NN_FPFH = "mnf" - -PREPROCESSING_DEFAULT_VALUE_DOWNSAMPLING_VOXEL_SIZE = 0.0 -PREPROCESSING_DEFAULT_VALUE_RADIUS_NORMAL_SCALE = 0.5 -PREPROCESSING_DEFAULT_VALUE_RADIUS_FEATURE_SCALE = 10 -PREPROCESSING_DEFAULT_VALUE_MAX_NN_NORMALS = 10 -PREPROCESSING_DEFAULT_VALUE_MAX_NN_FPFH = 100 - -### REGISTRATION PARAMETERS ### -REGISTRATION_KEY_DISTANCE_THRESHOLD = "rdt" -REGISTRATION_KEY_FITNESS_THRESHOLD = "rft" -REGISTRATION_KEY_MAX_ITERATIONS = "rmi" -REGISTRATION_KEY_ICP_DISTANCE_THRESHOLD = "idt" - -REGISTRATION_DEFAULT_VALUE_DISTANCE_THRESHOLD = 1 -REGISTRATION_DEFAULT_VALUE_FITNESS_THRESHOLD = 0.999 -REGISTRATION_DEFAULT_VALUE_MAX_ITERATIONS = 100000 -REGISTRATION_DEFAULT_VALUE_ICP_DISTANCE_THRESHOLD = 1 -REGISTRATION_DEFAULT_VALUE_RANSAC_CONVERGENCE_CONFIDENCE = 1.0 # Leave at 1.0 to NOT terminate early - ### POSTPROCESSING PARAMETERS ### POSTPROCESSING_KEY = "postprocessing" POSTPROCESSING_KEY_CLUSTERING_SCALING = "cs" diff --git a/SlicerBoneMorphing/src/logic/SlicerBoneMorphingLogic.py b/SlicerBoneMorphing/src/logic/SlicerBoneMorphingLogic.py index 641e125..152e6f8 100644 --- a/SlicerBoneMorphing/src/logic/SlicerBoneMorphingLogic.py +++ b/SlicerBoneMorphing/src/logic/SlicerBoneMorphingLogic.py @@ -33,26 +33,23 @@ elif platform == "win32": BCPD_EXEC += "bcpd_win32.exe" -SOURCE_VISUALIZATION_COLOR = [0, 1, 0] -TARGET_VISUALIZATION_COLOR = [1, 0, 0] - class SlicerBoneMorphingLogic(ScriptedLoadableModuleLogic): def __init__(self, parent=None): ScriptedLoadableModuleLogic.__init__(self, parent) - def __visualize(self, source, target): + def __visualize(self, source, target, window_name: str = "", source_color=const.VISUALIZATION_DEFAULT_VALUE_TARGET_MODEL_COLOR, target_color=const.VISUALIZATION_KEY_TARGET_MODEL_COLOR): models = [] if (source is not None): - source.paint_uniform_color(SOURCE_VISUALIZATION_COLOR) + source.paint_uniform_color(np.array([source_color.red() / 255, source_color.green() / 255, source_color.blue() / 255])) models.append(source) if (target is not None): - target.paint_uniform_color(TARGET_VISUALIZATION_COLOR) + target.paint_uniform_color(np.array([target_color.red() / 255, target_color.green() / 255, target_color.blue() / 255])) models.append(target) - o3d.visualization.draw_geometries(models) + o3d.visualization.draw_geometries(models, window_name=window_name, mesh_show_wireframe=True, point_show_normal=True) def generate_model( self, @@ -75,11 +72,6 @@ def generate_model( - status: EXIT_OK or EXIT_FAILURE - generatedPolydata: Generated model by the BCPD """ - - if (source_model == const.VALUE_NODE_NOT_SELECTED or target_model == const.VALUE_NODE_NOT_SELECTED): - print("Input or foundation model(s) were not selected") - return const.EXIT_FAILURE, None - source_mesh = self.__convert_model_to_mesh(source_model) target_mesh = self.__convert_model_to_mesh(target_model) @@ -90,17 +82,24 @@ def generate_model( source_mesh.transform(result_icp.transformation) - # self.__visualize(source_mesh, target_mesh) + visualization_params = parameters[const.VISUALIZATION_KEY] + + if (visualization_params[const.VISUALIZATION_KEY_SHOULD_VISUALIZE] is True): + self.__visualize(source_mesh, target_mesh, "Preprocessed models", visualization_params[const.VISUALIZATION_KEY_SOURCE_MODEL_COLOR], visualization_params[const.VISUALIZATION_KEY_TARGET_MODEL_COLOR]) # BCPD stage deformed = self.__deformable_registration(source_mesh, target_mesh, parameters[const.BCPD_KEY]) if (deformed is None): return const.EXIT_FAILURE, None - # self.__visualize(deformed, None) + if (visualization_params[const.VISUALIZATION_KEY_SHOULD_VISUALIZE] is True): + self.__visualize(deformed, None, "Reconstructed model", visualization_params[const.VISUALIZATION_KEY_SOURCE_MODEL_COLOR], visualization_params[const.VISUALIZATION_KEY_TARGET_MODEL_COLOR]) generated_polydata = self.__postprocess_meshes(deformed, parameters[const.POSTPROCESSING_KEY]) + if (visualization_params[const.VISUALIZATION_KEY_SHOULD_VISUALIZE] is True): + self.__visualize(deformed, None, "Postprocessed model", visualization_params[const.VISUALIZATION_KEY_SOURCE_MODEL_COLOR], visualization_params[const.VISUALIZATION_KEY_TARGET_MODEL_COLOR]) + return const.EXIT_OK, generated_polydata def __convert_mesh_to_vtk_polydata(self, mesh: o3d.geometry.TriangleMesh) -> vtk.vtkPolyData: diff --git a/SlicerBoneMorphing/src/widget/SlicerBoneMorphingWidget.py b/SlicerBoneMorphing/src/widget/SlicerBoneMorphingWidget.py index a95b8bf..460a0b8 100644 --- a/SlicerBoneMorphing/src/widget/SlicerBoneMorphingWidget.py +++ b/SlicerBoneMorphing/src/widget/SlicerBoneMorphingWidget.py @@ -23,9 +23,10 @@ def setup(self) -> None: self.__ui = su.childWidgetVariables(self.__uiWidget) self.__logic = None self.__setup_ui() - self.__reset_parameters_to_default() def __setup_ui(self) -> None: + self.__ui.visualizationModelColorGroupBox.setVisible(False) + self.__ui.sourceNodeSelectionBox.setMRMLScene(slicer.mrmlScene) self.__ui.targetNodeSelectionBox.setMRMLScene(slicer.mrmlScene) @@ -49,7 +50,13 @@ def __setup_ui(self) -> None: self.__ui.bcpdResetParametersPushButton.clicked.connect(self.__reset_parameters_to_default) self.__ui.generateModelButton.clicked.connect(self.__generate_model) + self.__reset_parameters_to_default() + def __reset_parameters_to_default(self) -> None: + self.__ui.visualizationVisualizeCheckBox.setChecked(False) + self.__ui.visualizationSourceModelColorPickerButton.setColor(const.VISUALIZATION_DEFAULT_VALUE_SOURCE_MODEL_COLOR) + self.__ui.visualizationTargetModelColorPickerButton.setColor(const.VISUALIZATION_DEFAULT_VALUE_TARGET_MODEL_COLOR) + ## Preprocessing parameters ## self.__ui.preprocessingDownsamplingVoxelSizeDoubleSpinBox.value = const.PREPROCESSING_DEFAULT_VALUE_DOWNSAMPLING_VOXEL_SIZE self.__ui.preprocessingNormalsEstimationRadiusDoubleSpinBox.value = const.PREPROCESSING_DEFAULT_VALUE_RADIUS_NORMAL_SCALE @@ -150,12 +157,21 @@ def __parse_parameters(self) -> dict: Parsing parameters from the UI user option elements """ params = {} + params[const.VISUALIZATION_KEY] = self.__parse_parameters_visualization() params[const.PREPROCESSING_KEY] = self.__parse_parameters_preprocessing() params[const.BCPD_KEY] = self.__parse_parameters_bcpd() params[const.POSTPROCESSING_KEY] = self.__parse_parameters_postprocessing() return params + def __parse_parameters_visualization(self) -> dict: + params = {} + params[const.VISUALIZATION_KEY_SHOULD_VISUALIZE] = self.__ui.visualizationVisualizeCheckBox.checked + params[const.VISUALIZATION_KEY_SOURCE_MODEL_COLOR] = self.__ui.visualizationSourceModelColorPickerButton.color + params[const.VISUALIZATION_KEY_TARGET_MODEL_COLOR] = self.__ui.visualizationTargetModelColorPickerButton.color + + return params + def __parse_parameters_preprocessing(self) -> dict: params = {} From 69b5dee01ce46b558188c368d0b5d9f882da4e7e Mon Sep 17 00:00:00 2001 From: HarryHeres Date: Wed, 17 Jul 2024 19:42:35 +0200 Subject: [PATCH 7/8] Updating docs and CHANGELOG --- CHANGELOG.md | 2 ++ README.md | 7 +------ docs/how-to-use.md | 2 ++ 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe3803d..b6eb780 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,9 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Alert when installing Open3D (#3) - Formatting settings for flake8 - Codeowners +- Pipeline steps visualization ### Changed - Naming of the generated model based on the selected target node +- Updated documentation (#11) ### Removed - Testing STL models diff --git a/README.md b/README.md index 485eb70..202de98 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,7 @@ Steps: - Restart 3D Slicer ## Usage -After a successful installation, the module will be available in the **Morphing** section. -When switching to the module, you should be greeted with the following UI: - -

- -

+After a successful installation, the module will be available in the **Morphing** section in the Modules dropdown menu. ## How to use For guidance on how to use the module, please refer to this [Guide](./docs/how-to-use.md). diff --git a/docs/how-to-use.md b/docs/how-to-use.md index 9e6ad9d..3e7dc39 100644 --- a/docs/how-to-use.md +++ b/docs/how-to-use.md @@ -1,4 +1,6 @@ # Guide on how to use the module +### Visualization +Here, you can check whether you want to visualize results of each pipeline steps using Open3D. ### Input section - Source = The mean model, i.e. a full humerus From fd3efad7d309a0af5ecb8908e6041c78988a2b5f Mon Sep 17 00:00:00 2001 From: HarryHeres Date: Wed, 17 Jul 2024 19:43:07 +0200 Subject: [PATCH 8/8] Updating CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6eb780..c2bc530 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Alert when installing Open3D (#3) - Formatting settings for flake8 - Codeowners -- Pipeline steps visualization +- Pipeline steps visualization (#12) ### Changed - Naming of the generated model based on the selected target node