From 214f74a34af57d9a28da3c5dcdb99076ff9da2c0 Mon Sep 17 00:00:00 2001 From: HarryHeres Date: Wed, 17 Jul 2024 10:19:36 +0200 Subject: [PATCH] Added alert before installing Open3D --- .../Resources/UI/SlicerBoneMorphing.ui | 2676 ++++++++--------- SlicerBoneMorphing/SlicerBoneMorphing.py | 31 +- .../src/logic/SlicerBoneMorphingLogic.py | 108 +- .../src/widget/SlicerBoneMorphingWidget.py | 210 +- tox.ini | 15 + 5 files changed, 1526 insertions(+), 1514 deletions(-) create mode 100644 tox.ini diff --git a/SlicerBoneMorphing/Resources/UI/SlicerBoneMorphing.ui b/SlicerBoneMorphing/Resources/UI/SlicerBoneMorphing.ui index 2bb85a1..5f9b7fd 100644 --- a/SlicerBoneMorphing/Resources/UI/SlicerBoneMorphing.ui +++ b/SlicerBoneMorphing/Resources/UI/SlicerBoneMorphing.ui @@ -1,1341 +1,1341 @@ - 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 - - - 100.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 + 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 + + + +
diff --git a/SlicerBoneMorphing/SlicerBoneMorphing.py b/SlicerBoneMorphing/SlicerBoneMorphing.py index 29673fc..2f001bb 100644 --- a/SlicerBoneMorphing/SlicerBoneMorphing.py +++ b/SlicerBoneMorphing/SlicerBoneMorphing.py @@ -1,22 +1,23 @@ from slicer.ScriptedLoadableModule import ScriptedLoadableModule from src.widget.SlicerBoneMorphingWidget import SlicerBoneMorphingWidget + class SlicerBoneMorphing(ScriptedLoadableModule): - """Uses ScriptedLoadableModule base class, available at: - https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py - """ + """Uses ScriptedLoadableModule base class, available at: + https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py + """ + + def __init__(self, parent=None): + ScriptedLoadableModule.__init__(self, parent) + self.parent.title = "Slicer Bone Morphing" + self.parent.categories = ["Morphing"] + self.parent.dependencies = [] + self.parent.contributors = ["Jan Heres, Eva C. Herbst (ETH Zurich), Arthur Porto (Louisiana State University)"] - def __init__(self, parent): - ScriptedLoadableModule.__init__(self, parent) - self.parent.title = "Slicer Bone Morphing" - self.parent.categories = ["Morphing"] - self.parent.dependencies = [] - self.parent.contributors = ["Jan Heres (University of West Bohemia), Eva C. Herbst (ETH Zurich), Arthur Porto (Louisiana State University)"] - self.parent.helpText = """ + 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. - """ - self.parent.acknowledgementText = """ + Please, start with importing your model and checking out the options on the left side afterwards.""" + + self.parent.acknowledgementText = """ Credits: Jan Heres - I would love to thank my awesome colleagues Eva C. Herbst and Arthur Porto for their priceless contributions to this project! - """ + I would love to thank my awesome colleagues Eva C. Herbst and Arthur Porto for their priceless contributions to this project!""" diff --git a/SlicerBoneMorphing/src/logic/SlicerBoneMorphingLogic.py b/SlicerBoneMorphing/src/logic/SlicerBoneMorphingLogic.py index fb6abce..9657c29 100644 --- a/SlicerBoneMorphing/src/logic/SlicerBoneMorphingLogic.py +++ b/SlicerBoneMorphing/src/logic/SlicerBoneMorphingLogic.py @@ -1,25 +1,27 @@ +import src.logic.Constants as const +import tempfile +import subprocess +from vtk.util.numpy_support import vtk_to_numpy +import vtk +import glob +import os +import numpy as np from sys import platform from typing import Tuple from slicer.ScriptedLoadableModule import ScriptedLoadableModuleLogic -import slicer from slicer import vtkMRMLModelNode +import slicer.util as su try: import open3d as o3d except ModuleNotFoundError: - print("Module Open3D is not installed. Installing...") - slicer.util.pip_install('open3d===0.16.0') # Version fix because of silicon based Macs - -import open3d as o3d -import numpy as np -import os -import glob -import vtk -from vtk.util.numpy_support import vtk_to_numpy -import subprocess -import tempfile + print("Module Open3D not found") + if su.confirmOkCancelDisplay(text="This module requires the 'open3d' Python package. Click OK to install it now.") is True: + su.pip_install('open3d===0.16.0') # Version fix because of silicon based Macs + import open3d as o3d + else: + print("Open3D is not installed, but is required") -from .Constants import * # NOTE: Path is relative to the main module class BCPD_EXEC = os.path.dirname(os.path.abspath(__file__)) + "/../../Resources/BCPD/exec/" @@ -36,7 +38,7 @@ class SlicerBoneMorphingLogic(ScriptedLoadableModuleLogic): - def __init__(self, parent): + def __init__(self, parent=None): ScriptedLoadableModuleLogic.__init__(self, parent) def __visualize(self, source, target): @@ -45,11 +47,11 @@ def __visualize(self, source, target): """ models = [] - if (source != None): + if (source is not None): source.paint_uniform_color(SOURCE_VISUALIZATION_COLOR) models.append(source) - if (target != None): + if (target is not None): target.paint_uniform_color(TARGET_VISUALIZATION_COLOR) models.append(target) @@ -78,32 +80,32 @@ def generate_model( - mergedPolydata: generatedPolydata that had been merged with the targetModel """ - if (source_model == VALUE_NODE_NOT_SELECTED or target_model == VALUE_NODE_NOT_SELECTED): + 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 EXIT_FAILURE, None, None + return const.EXIT_FAILURE, None, None source_mesh = self.__convert_model_to_mesh(source_model) target_mesh = self.__convert_model_to_mesh(target_model) - err, result_icp = self.__preprocess_model(source_mesh, target_mesh, parameters[PREPROCESSING_KEY]) - if err == EXIT_FAILURE: + err, result_icp = self.__preprocess_model(source_mesh, target_mesh, parameters[const.const.PREPROCESSING_KEY]) + if err == const.EXIT_FAILURE: print("Cannot continue with generating. Aborting...") - return EXIT_FAILURE, None, None + return const.EXIT_FAILURE, None, None source_mesh.transform(result_icp.transformation) # self.__visualize(source_mesh, target_mesh) # BCPD stage - deformed = self.__deformable_registration(source_mesh, target_mesh, parameters[BCPD_KEY]) - if (deformed == None): - return EXIT_FAILURE, None, None + deformed = self.__deformable_registration(source_mesh, target_mesh, parameters[const.const.BCPD_KEY]) + if (deformed is not None): + return const.EXIT_FAILURE, None, None # self.__visualize(deformed, None) - generated_polydata, merged_polydata = self.__postprocess_meshes(deformed, target_mesh, parameters[POSTPROCESSING_KEY]) + generated_polydata, merged_polydata = self.__postprocess_meshes(deformed, target_mesh, parameters[const.const.POSTPROCESSING_KEY]) - return EXIT_OK, generated_polydata, merged_polydata + return const.EXIT_OK, generated_polydata, merged_polydata def __convert_mesh_to_vtk_polydata(self, mesh: o3d.geometry.TriangleMesh) -> vtk.vtkPolyData: """ @@ -217,44 +219,44 @@ def __preprocess_model( source_pcd_downsampled, source_pcd_fpfh = self.__preprocess_point_cloud( source_pcd, - parameters[PREPROCESSING_KEY_DOWNSAMPLING_DISTANCE_THRESHOLD], - parameters[PREPROCESSING_KEY_NORMALS_ESTIMATION_RADIUS], - parameters[PREPROCESSING_KEY_FPFH_ESTIMATION_RADIUS], - parameters[PREPROCESSING_KEY_MAX_NN_NORMALS], - parameters[PREPROCESSING_KEY_MAX_NN_FPFH] + parameters[const.PREPROCESSING_KEY_DOWNSAMPLING_DISTANCE_THRESHOLD], + parameters[const.PREPROCESSING_KEY_NORMALS_ESTIMATION_RADIUS], + parameters[const.PREPROCESSING_KEY_FPFH_ESTIMATION_RADIUS], + parameters[const.PREPROCESSING_KEY_MAX_NN_NORMALS], + parameters[const.PREPROCESSING_KEY_MAX_NN_FPFH] ) target_pcd_downsampled, target_pcd_fpfh = self.__preprocess_point_cloud( target_pcd, - parameters[PREPROCESSING_KEY_DOWNSAMPLING_DISTANCE_THRESHOLD], - parameters[PREPROCESSING_KEY_NORMALS_ESTIMATION_RADIUS], - parameters[PREPROCESSING_KEY_FPFH_ESTIMATION_RADIUS], - parameters[PREPROCESSING_KEY_MAX_NN_NORMALS], - parameters[PREPROCESSING_KEY_MAX_NN_FPFH] + parameters[const.PREPROCESSING_KEY_DOWNSAMPLING_DISTANCE_THRESHOLD], + parameters[const.PREPROCESSING_KEY_NORMALS_ESTIMATION_RADIUS], + parameters[const.PREPROCESSING_KEY_FPFH_ESTIMATION_RADIUS], + parameters[const.PREPROCESSING_KEY_MAX_NN_NORMALS], + parameters[const.PREPROCESSING_KEY_MAX_NN_FPFH] ) try: result_ransac = self.__ransac_pcd_registration( source_pcd_downsampled, target_pcd_downsampled, source_pcd_fpfh, target_pcd_fpfh, - parameters[REGISTRATION_KEY_DISTANCE_THRESHOLD], - parameters[REGISTRATION_KEY_FITNESS_THRESHOLD], - parameters[REGISTRATION_KEY_MAX_ITERATIONS] + parameters[const.REGISTRATION_KEY_DISTANCE_THRESHOLD], + parameters[const.REGISTRATION_KEY_FITNESS_THRESHOLD], + parameters[const.REGISTRATION_KEY_MAX_ITERATIONS] ) - if result_ransac == None: + if result_ransac is not None: raise RuntimeError except RuntimeError: print("No registration fit was found using the RANSAC algorithm. Please, try adjusting the preprocessing parameters") - return EXIT_FAILURE, None + return const.EXIT_FAILURE, None result_icp = o3d.pipelines.registration.registration_icp( source_pcd_downsampled, target_pcd_downsampled, - parameters[REGISTRATION_KEY_ICP_DISTANCE_THRESHOLD], + parameters[const.REGISTRATION_KEY_ICP_DISTANCE_THRESHOLD], result_ransac.transformation, o3d.pipelines.registration.TransformationEstimationPointToPlane() ) - return EXIT_OK, result_icp + return const.EXIT_OK, result_icp def __preprocess_point_cloud( self, @@ -385,7 +387,7 @@ def __deformable_registration( cmd = f'{BCPD_EXEC} -h -x {target_path} -y {source_path}' for key in bcpd_parameters.keys(): - cmd += f' {key}{bcpd_parameters[key]}' + cmd += f' {key}{bcpd_parameters[const.key]}' cmd += f' -o {output_path}' print("BCPD: " + cmd) @@ -441,15 +443,15 @@ def __postprocess_meshes( combined.compute_vertex_normals() # Simplify mesh (smoothing and filtering) - if parameters[POSTPROCESSING_KEY_CLUSTERING_SCALING] > 1.0: - generated = generated.simplify_vertex_clustering(parameters[POSTPROCESSING_KEY_CLUSTERING_SCALING], contraction=o3d.geometry.SimplificationContraction.Average) - combined = combined.simplify_vertex_clustering(parameters[POSTPROCESSING_KEY_CLUSTERING_SCALING], contraction=o3d.geometry.SimplificationContraction.Average) - - if parameters[POSTPROCESSING_KEY_SMOOTHING_ITERATIONS] > 0: - generated = generated.filter_smooth_simple(number_of_iterations=parameters[POSTPROCESSING_KEY_SMOOTHING_ITERATIONS]) - generated = generated.filter_smooth_taubin(number_of_iterations=parameters[POSTPROCESSING_KEY_SMOOTHING_ITERATIONS]) - combined = combined.filter_smooth_simple(number_of_iterations=parameters[POSTPROCESSING_KEY_SMOOTHING_ITERATIONS]) - combined = combined.filter_smooth_taubin(number_of_iterations=parameters[POSTPROCESSING_KEY_SMOOTHING_ITERATIONS]) + if parameters[const.POSTPROCESSING_KEY_CLUSTERING_SCALING] > 1.0: + generated = generated.simplify_vertex_clustering(parameters[const.POSTPROCESSING_KEY_CLUSTERING_SCALING], contraction=o3d.geometry.SimplificationContraction.Average) + combined = combined.simplify_vertex_clustering(parameters[const.POSTPROCESSING_KEY_CLUSTERING_SCALING], contraction=o3d.geometry.SimplificationContraction.Average) + + if parameters[const.POSTPROCESSING_KEY_SMOOTHING_ITERATIONS] > 0: + generated = generated.filter_smooth_simple(number_of_iterations=parameters[const.POSTPROCESSING_KEY_SMOOTHING_ITERATIONS]) + generated = generated.filter_smooth_taubin(number_of_iterations=parameters[const.POSTPROCESSING_KEY_SMOOTHING_ITERATIONS]) + combined = combined.filter_smooth_simple(number_of_iterations=parameters[const.POSTPROCESSING_KEY_SMOOTHING_ITERATIONS]) + combined = combined.filter_smooth_taubin(number_of_iterations=parameters[const.POSTPROCESSING_KEY_SMOOTHING_ITERATIONS]) generated_polydata = self.__convert_mesh_to_vtk_polydata(generated) merged_polydata = self.__convert_mesh_to_vtk_polydata(combined) diff --git a/SlicerBoneMorphing/src/widget/SlicerBoneMorphingWidget.py b/SlicerBoneMorphing/src/widget/SlicerBoneMorphingWidget.py index a6a22fe..b382a9b 100644 --- a/SlicerBoneMorphing/src/widget/SlicerBoneMorphingWidget.py +++ b/SlicerBoneMorphing/src/widget/SlicerBoneMorphingWidget.py @@ -1,40 +1,31 @@ -from src.logic.Constants import * -import ctk -import slicer -from slicer.ScriptedLoadableModule import ScriptedLoadableModuleWidget -from src.logic.SlicerBoneMorphingLogic import SlicerBoneMorphingLogic - +import src.logic.Constants as const from src.logic.Enums import BcpdAccelerationMode, BcpdKernelType, BcpdNormalizationOptions, BcpdStandardKernel from qt import QComboBox from enum import Enum - import os +import slicer +import slicer.util as su + +from slicer.ScriptedLoadableModule import ScriptedLoadableModuleWidget class SlicerBoneMorphingWidget(ScriptedLoadableModuleWidget): - def __init__(self, parent): + def __init__(self, parent=None): """Called when the application opens the module the first time and the widget is initialized.""" ScriptedLoadableModuleWidget.__init__(self, parent) self.__bcpd_options = {} def setup(self) -> None: - """Called when the application opens the module the first time and the widget is initialized.""" ScriptedLoadableModuleWidget.setup(self) - # Load widget from .ui file (created by Qt Designer) - self.__uiWidget = slicer.util.loadUI(self.resourcePath("UI/SlicerBoneMorphing.ui")) + self.__uiWidget = su.loadUI(self.resourcePath("UI/SlicerBoneMorphing.ui")) self.layout.addWidget(self.__uiWidget) - self.__ui = slicer.util.childWidgetVariables(self.__uiWidget) - self.__logic = SlicerBoneMorphingLogic(self) - + self.__ui = su.childWidgetVariables(self.__uiWidget) + self.__logic = None self.__setup_ui() self.__reset_parameters_to_default() def __setup_ui(self) -> None: - """ - Method that sets up all UI elements and their dependencies - """ - self.__ui.sourceNodeSelectionBox.setMRMLScene(slicer.mrmlScene) self.__ui.targetNodeSelectionBox.setMRMLScene(slicer.mrmlScene) @@ -60,61 +51,61 @@ def __setup_ui(self) -> None: def __reset_parameters_to_default(self) -> None: ## Preprocessing parameters ## - self.__ui.preprocessingDownsamplingDistanceThresholdDoubleSpinBox.value = PREPROCESSING_DEFAULT_VALUE_DOWNSAMPLING_DISTANCE_THRESHOLD - self.__ui.preprocessingNormalsEstimationRadiusDoubleSpinBox.value = PREPROCESSING_DEFAULT_VALUE_RADIUS_NORMAL_SCALE - self.__ui.preprocessingNormalsEstimationMaxNeighboursSpinBox.value = PREPROCESSING_DEFAULT_VALUE_MAX_NN_NORMALS - self.__ui.preprocessingFpfhRadiusDoubleSpinBox.value = PREPROCESSING_DEFAULT_VALUE_RADIUS_FEATURE_SCALE - self.__ui.preprocessingFpfhMaxNeighboursSpinBox.value = PREPROCESSING_DEFAULT_VALUE_MAX_NN_FPFH + self.__ui.preprocessingDownsamplingDistanceThresholdDoubleSpinBox.value = const.PREPROCESSING_DEFAULT_VALUE_DOWNSAMPLING_DISTANCE_THRESHOLD + 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 + self.__ui.preprocessingFpfhMaxNeighboursSpinBox.value = const.PREPROCESSING_DEFAULT_VALUE_MAX_NN_FPFH ## Registration parameters ## - self.__ui.registrationMaxIterationsSpinBox.value = REGISTRATION_DEFAULT_VALUE_MAX_ITERATIONS - self.__ui.registrationDistanceThresholdDoubleSpinBox.value = REGISTRATION_DEFAULT_VALUE_DISTANCE_THRESHOLD - self.__ui.registrationFitnessThresholdDoubleSpinBox.value = REGISTRATION_DEFAULT_VALUE_FITNESS_THRESHOLD - self.__ui.registrationIcpDistanceThresholdDoubleSpinBox.value = REGISTRATION_DEFAULT_VALUE_ICP_DISTANCE_THRESHOLD + self.__ui.registrationMaxIterationsSpinBox.value = const.REGISTRATION_DEFAULT_VALUE_MAX_ITERATIONS + self.__ui.registrationDistanceThresholdDoubleSpinBox.value = const.REGISTRATION_DEFAULT_VALUE_DISTANCE_THRESHOLD + self.__ui.registrationFitnessThresholdDoubleSpinBox.value = const.REGISTRATION_DEFAULT_VALUE_FITNESS_THRESHOLD + self.__ui.registrationIcpDistanceThresholdDoubleSpinBox.value = const.REGISTRATION_DEFAULT_VALUE_ICP_DISTANCE_THRESHOLD ## Tuning parameters ## - self.__ui.bcpdOmegaDoubleSpinBox.value = BCPD_DEFAULT_VALUE_OMEGA - self.__ui.bcpdLambdaDoubleSpinBox.value = BCPD_DEFAULT_VALUE_LAMBDA - self.__ui.bcpdBetaDoubleSpinBox.value = BCPD_DEFAULT_VALUE_BETA - self.__ui.bcpdGammaDoubleSpinBox.value = BCPD_DEFAULT_VALUE_GAMMA - self.__ui.bcpdKappaDoubleSpinBox.value = BCPD_DEFAULT_VALUE_KAPPA + self.__ui.bcpdOmegaDoubleSpinBox.value = const.BCPD_DEFAULT_VALUE_OMEGA + self.__ui.bcpdLambdaDoubleSpinBox.value = const.BCPD_DEFAULT_VALUE_LAMBDA + self.__ui.bcpdBetaDoubleSpinBox.value = const.BCPD_DEFAULT_VALUE_BETA + self.__ui.bcpdGammaDoubleSpinBox.value = const.BCPD_DEFAULT_VALUE_GAMMA + self.__ui.bcpdKappaDoubleSpinBox.value = const.BCPD_DEFAULT_VALUE_KAPPA ## Kernel parameters ## - self.__ui.bcpdKernelTypeComboBox.setCurrentIndex(BCPD_DEFAULT_VALUE_KERNEL_TYPE) - self.__ui.bcpdStandardKernelComboBox.setCurrentIndex(BCPD_DEFAULT_VALUE_STANDARD_KERNEL_TYPE) - self.__ui.bcpdGeodesicKernelTauDoubleSpinBox.value = BCPD_DEFAULT_VALUE_TAU + self.__ui.bcpdKernelTypeComboBox.setCurrentIndex(const.BCPD_DEFAULT_VALUE_KERNEL_TYPE) + self.__ui.bcpdStandardKernelComboBox.setCurrentIndex(const.BCPD_DEFAULT_VALUE_STANDARD_KERNEL_TYPE) + self.__ui.bcpdGeodesicKernelTauDoubleSpinBox.value = const.BCPD_DEFAULT_VALUE_TAU self.__ui.bcpdGeodesicKernelInputMeshCheckBox.checked = False - self.__ui.bcpdGeodesicKernelInputMeshLineEdit.text = BCPD_DEFAULT_VALUE_INPUT_MESH_PATH - self.__ui.bcpdGeodesicKernelNeighboursSpinBox.value = BCPD_DEFAULT_VALUE_KERNEL_NEIGBOURS - self.__ui.bcpdGeodesicKernelRadiusDoubleSpinBox.value = BCPD_DEFAULT_VALUE_KERNEL_NEIGHBOUR_RADIUS - self.__ui.bcpdGeodesicKernelBetaDoubleSpinBox.value = BCPD_DEFAULT_VALUE_KERNEL_BETA - self.__ui.bcpdGeodesicKernelKTildeDoubleSpinBox.value = BCPD_DEFAULT_VALUE_KERNEL_K_TILDE - self.__ui.bcpdGeodesicKernelEpsilonDoubleSpinBox.value = BCPD_DEFAULT_VALUE_KERNEL_EPSILON + self.__ui.bcpdGeodesicKernelInputMeshLineEdit.text = const.BCPD_DEFAULT_VALUE_INPUT_MESH_PATH + self.__ui.bcpdGeodesicKernelNeighboursSpinBox.value = const.BCPD_DEFAULT_VALUE_KERNEL_NEIGBOURS + self.__ui.bcpdGeodesicKernelRadiusDoubleSpinBox.value = const.BCPD_DEFAULT_VALUE_KERNEL_NEIGHBOUR_RADIUS + self.__ui.bcpdGeodesicKernelBetaDoubleSpinBox.value = const.BCPD_DEFAULT_VALUE_KERNEL_BETA + self.__ui.bcpdGeodesicKernelKTildeDoubleSpinBox.value = const.BCPD_DEFAULT_VALUE_KERNEL_K_TILDE + self.__ui.bcpdGeodesicKernelEpsilonDoubleSpinBox.value = const.BCPD_DEFAULT_VALUE_KERNEL_EPSILON ## Acceleration parameters ## - self.__ui.bcpdAccelerationModeComboBox.setCurrentIndex(BCPD_DEFAULT_VALUE_ACCELERATION_MODE) + self.__ui.bcpdAccelerationModeComboBox.setCurrentIndex(const.BCPD_DEFAULT_VALUE_ACCELERATION_MODE) self.__ui.bcpdAccelerationAutomaticVbiCheckBox.checked = True self.__ui.bcpdAccelerationAutomaticPlusPlusCheckBox.checked = True - self.__ui.bcpdAccelerationManualNystormGSpinBox.value = BCPD_DEFAULT_VALUE_ACCELERATION_NYSTORM_SAMPLES_G - self.__ui.bcpdAccelerationManualNystormJSpinBox.value = BCPD_DEFAULT_VALUE_ACCELERATION_NYSTORM_SAMPLES_J - self.__ui.bcpdAccelerationManualNystormRSpinBox.value = BCPD_DEFAULT_VALUE_ACCELERATION_NYSTORM_SAMPLES_R - self.__ui.bcpdAccelerationManualKdTreeScaleDoubleSpinBox.value = BCPD_DEFAULT_VALUE_ACCELERATION_KD_TREE_SCALE - self.__ui.bcpdAccelerationManualKdTreeRadiusDoubleSpinBox.value = BCPD_DEFAULT_VALUE_ACCELERATION_KD_TREE_RADIUS - self.__ui.bcpdAccelerationManualKdTreeThresholdDoubleSpinBox.value = BCPD_DEFAULT_VALUE_ACCELERATION_KD_TREE_SIGMA_THRESHOLD + self.__ui.bcpdAccelerationManualNystormGSpinBox.value = const.BCPD_DEFAULT_VALUE_ACCELERATION_NYSTORM_SAMPLES_G + self.__ui.bcpdAccelerationManualNystormJSpinBox.value = const.BCPD_DEFAULT_VALUE_ACCELERATION_NYSTORM_SAMPLES_J + self.__ui.bcpdAccelerationManualNystormRSpinBox.value = const.BCPD_DEFAULT_VALUE_ACCELERATION_NYSTORM_SAMPLES_R + self.__ui.bcpdAccelerationManualKdTreeScaleDoubleSpinBox.value = const.BCPD_DEFAULT_VALUE_ACCELERATION_KD_TREE_SCALE + self.__ui.bcpdAccelerationManualKdTreeRadiusDoubleSpinBox.value = const.BCPD_DEFAULT_VALUE_ACCELERATION_KD_TREE_RADIUS + self.__ui.bcpdAccelerationManualKdTreeThresholdDoubleSpinBox.value = const.BCPD_DEFAULT_VALUE_ACCELERATION_KD_TREE_SIGMA_THRESHOLD ## Downsampling options ## - self.__ui.bcpdDownsamplingLineEdit.text = BCPD_DEFAULT_VALUE_DOWNSAMPLING_OPTIONS + self.__ui.bcpdDownsamplingLineEdit.text = const.BCPD_DEFAULT_VALUE_DOWNSAMPLING_OPTIONS ## Convergence options ## - self.__ui.bcpdConvergenceToleranceDoubleSpinBox.value = BCPD_DEFAULT_VALUE_CONVERGENCE_TOLERANCE - self.__ui.bcpdConvergenceMaxIterationsSpinBox.value = BCPD_DEFAULT_VALUE_CONVERGENCE_MAX_ITERATIONS - self.__ui.bcpdConvergenceMinIterationsSpinBox.value = BCPD_DEFAULT_VALUE_CONVERGENCE_MIN_ITERATIONS + self.__ui.bcpdConvergenceToleranceDoubleSpinBox.value = const.BCPD_DEFAULT_VALUE_CONVERGENCE_TOLERANCE + self.__ui.bcpdConvergenceMaxIterationsSpinBox.value = const.BCPD_DEFAULT_VALUE_CONVERGENCE_MAX_ITERATIONS + self.__ui.bcpdConvergenceMinIterationsSpinBox.value = const.BCPD_DEFAULT_VALUE_CONVERGENCE_MIN_ITERATIONS ## Normalization options ## - self.__ui.bcpdNormalizationComboBox.setCurrentIndex(BCPD_DEFAULT_VALUE_NORMALIZATION_OPTIONS) + self.__ui.bcpdNormalizationComboBox.setCurrentIndex(const.BCPD_DEFAULT_VALUE_NORMALIZATION_OPTIONS) - self.__ui.postprocessingClusteringScalingDoubleSpinBox.value = POSTPROCESSING_DEFAULT_VALUE_CLUSTERING_SCALING - self.__ui.processingSmoothingIterationsSpinBox.value = POSTPROCESSING_DEFAULT_VALUE_SMOOTHING_ITERATIONS + self.__ui.postprocessingClusteringScalingDoubleSpinBox.value = const.POSTPROCESSING_DEFAULT_VALUE_CLUSTERING_SCALING + self.__ui.processingSmoothingIterationsSpinBox.value = const.POSTPROCESSING_DEFAULT_VALUE_SMOOTHING_ITERATIONS def __setup_combo_box(self, combo_box: QComboBox, enum: Enum, on_selection_changed): """ @@ -159,9 +150,9 @@ def __parse_parameters(self) -> dict: Parsing parameters from the UI user option elements """ params = {} - params[PREPROCESSING_KEY] = self.__parse_parameters_preprocessing() - params[BCPD_KEY] = self.__parse_parameters_bcpd() - params[POSTPROCESSING_KEY] = self.__parse_parameters_postprocessing() + 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 @@ -169,17 +160,17 @@ def __parse_parameters_preprocessing(self) -> dict: params = {} # Preprocessing - params[PREPROCESSING_KEY_DOWNSAMPLING_DISTANCE_THRESHOLD] = self.__ui.preprocessingDownsamplingDistanceThresholdDoubleSpinBox.value - params[PREPROCESSING_KEY_NORMALS_ESTIMATION_RADIUS] = self.__ui.preprocessingNormalsEstimationRadiusDoubleSpinBox.value - params[PREPROCESSING_KEY_MAX_NN_NORMALS] = self.__ui.preprocessingNormalsEstimationMaxNeighboursSpinBox.value - params[PREPROCESSING_KEY_FPFH_ESTIMATION_RADIUS] = self.__ui.preprocessingFpfhRadiusDoubleSpinBox.value - params[PREPROCESSING_KEY_MAX_NN_FPFH] = self.__ui.preprocessingFpfhMaxNeighboursSpinBox.value + params[const.PREPROCESSING_KEY_DOWNSAMPLING_DISTANCE_THRESHOLD] = self.__ui.preprocessingDownsamplingDistanceThresholdDoubleSpinBox.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 + params[const.PREPROCESSING_KEY_MAX_NN_FPFH] = self.__ui.preprocessingFpfhMaxNeighboursSpinBox.value # Registration - params[REGISTRATION_KEY_MAX_ITERATIONS] = self.__ui.registrationMaxIterationsSpinBox.value - params[REGISTRATION_KEY_DISTANCE_THRESHOLD] = self.__ui.registrationDistanceThresholdDoubleSpinBox.value - params[REGISTRATION_KEY_FITNESS_THRESHOLD] = self.__ui.registrationFitnessThresholdDoubleSpinBox.value - params[REGISTRATION_KEY_ICP_DISTANCE_THRESHOLD] = self.__ui.registrationIcpDistanceThresholdDoubleSpinBox.value + params[const.REGISTRATION_KEY_MAX_ITERATIONS] = self.__ui.registrationMaxIterationsSpinBox.value + params[const.REGISTRATION_KEY_DISTANCE_THRESHOLD] = self.__ui.registrationDistanceThresholdDoubleSpinBox.value + params[const.REGISTRATION_KEY_FITNESS_THRESHOLD] = self.__ui.registrationFitnessThresholdDoubleSpinBox.value + params[const.REGISTRATION_KEY_ICP_DISTANCE_THRESHOLD] = self.__ui.registrationIcpDistanceThresholdDoubleSpinBox.value return params @@ -190,16 +181,16 @@ def __parse_parameters_bcpd(self) -> dict: params = {} ## Tuning parameters ## - params[BCPD_VALUE_KEY_OMEGA] = self.__ui.bcpdOmegaDoubleSpinBox.value - params[BCPD_VALUE_KEY_LAMBDA] = self.__ui.bcpdLambdaDoubleSpinBox.value - params[BCPD_VALUE_KEY_BETA] = self.__ui.bcpdBetaDoubleSpinBox.value - params[BCPD_VALUE_KEY_GAMMA] = self.__ui.bcpdGammaDoubleSpinBox.value + params[const.BCPD_VALUE_KEY_OMEGA] = self.__ui.bcpdOmegaDoubleSpinBox.value + params[const.BCPD_VALUE_KEY_LAMBDA] = self.__ui.bcpdLambdaDoubleSpinBox.value + params[const.BCPD_VALUE_KEY_BETA] = self.__ui.bcpdBetaDoubleSpinBox.value + params[const.BCPD_VALUE_KEY_GAMMA] = self.__ui.bcpdGammaDoubleSpinBox.value kappa = self.__ui.bcpdKappaDoubleSpinBox.value - if kappa < BCPD_MAX_VALUE_KAPPA: # Setting it to BCPD_MAX_VALUE_KAPPA will behave as "infinity" - params[BCPD_VALUE_KEY_KAPPA] = kappa + if kappa < const.BCPD_MAX_VALUE_KAPPA: # Setting it to BCPD_MAX_VALUE_KAPPA will behave as "infinity" + params[const.BCPD_VALUE_KEY_KAPPA] = kappa - # if self.ui.bcpdAdvancedParametersCheckBox.checked == True: + # if self.ui.bcpdAdvancedParametersCheckBox.checked is True: self.__parse_advanced_parameters(params) return params @@ -214,84 +205,87 @@ def __parse_advanced_parameters(self, params: dict) -> None: if (kernel_type == BcpdKernelType.STANDARD.value): selected_kernel = self.__ui.bcpdStandardKernelComboBox.currentIndex # Default kernel is Gauss, which does not need to be specified - if selected_kernel != BCPD_DEFAULT_VALUE_STANDARD_KERNEL_TYPE: + if selected_kernel != const.BCPD_DEFAULT_VALUE_STANDARD_KERNEL_TYPE: kernel_params += str(selected_kernel) else: # Geodesic Kernel - kernel_params += "geodesic" + BCPD_MULTIPLE_VALUES_SEPARATOR + kernel_params += "geodesic" + const.BCPD_MULTIPLE_VALUES_SEPARATOR tau = self.__ui.bcpdGeodesicKernelTauDoubleSpinBox.value - kernel_params += str(tau) + BCPD_MULTIPLE_VALUES_SEPARATOR + kernel_params += str(tau) + const.BCPD_MULTIPLE_VALUES_SEPARATOR - if self.__ui.bcpdGeodesicKernelInputMeshCheckBox.checked == True: + if self.__ui.bcpdGeodesicKernelInputMeshCheckBox.checked is True: input_mesh_path = self.__ui.bcpdGeodesicKernelInputMeshLineEdit.text if not os.path.exists(input_mesh_path): print("File '" + input_mesh_path + "' does not exist. Cancelling process...") return kernel_params += input_mesh_path else: - kernel_params += str(self.__ui.bcpdGeodesicKernelNeighboursSpinBox.value) + BCPD_MULTIPLE_VALUES_SEPARATOR + kernel_params += str(self.__ui.bcpdGeodesicKernelNeighboursSpinBox.value) + const.BCPD_MULTIPLE_VALUES_SEPARATOR kernel_params += str(self.__ui.bcpdGeodesicKernelRadiusDoubleSpinBox.value) if kernel_params != "": - params[BCPD_VALUE_KEY_KERNEL] = kernel_params + params[const.BCPD_VALUE_KEY_KERNEL] = kernel_params ## Acceleration settings ## if self.__ui.bcpdAccelerationModeComboBox.currentIndex == BcpdAccelerationMode.AUTOMATIC.value: - if self.__ui.bcpdAccelerationAutomaticVbiCheckBox.checked == True: - params[BCPD_VALUE_KEY_NYSTORM_G] = 70 - params[BCPD_VALUE_KEY_NYSTORM_P] = 300 + if self.__ui.bcpdAccelerationAutomaticVbiCheckBox.checked is True: + params[const.BCPD_VALUE_KEY_NYSTORM_G] = 70 + params[const.BCPD_VALUE_KEY_NYSTORM_P] = 300 # Option switch without a value - params[BCPD_VALUE_KEY_KD_TREE] = "" - params[BCPD_VALUE_KEY_KD_TREE_SCALE] = 7 - params[BCPD_VALUE_KEY_KD_TREE_RADIUS] = 0.15 + params[const.BCPD_VALUE_KEY_KD_TREE] = "" + params[const.BCPD_VALUE_KEY_KD_TREE_SCALE] = 7 + params[const.BCPD_VALUE_KEY_KD_TREE_RADIUS] = 0.15 - if self.__ui.bcpdAccelerationAutomaticPlusPlusCheckBox.checked == True: - params[BCPD_VALUE_KEY_DOWNSAMPLING] = "B,10000,0.08" + if self.__ui.bcpdAccelerationAutomaticPlusPlusCheckBox.checked is True: + params[const.BCPD_VALUE_KEY_DOWNSAMPLING] = "B,10000,0.08" else: # Manual acceleration - if self.__ui.bcpdAccelerationManualNystormGroupBox.checked == True: - params[BCPD_VALUE_KEY_NYSTORM_G] = self.__ui.bcpdAccelerationManualNystormGSpinBox.value - params[BCPD_VALUE_KEY_NYSTORM_P] = self.__ui.bcpdAccelerationManualNystormJSpinBox.value - params[BCPD_VALUE_KEY_NYSTORM_R] = self.__ui.bcpdAccelerationManualNystormRSpinBox.value + if self.__ui.bcpdAccelerationManualNystormGroupBox.checked is True: + params[const.BCPD_VALUE_KEY_NYSTORM_G] = self.__ui.bcpdAccelerationManualNystormGSpinBox.value + params[const.BCPD_VALUE_KEY_NYSTORM_P] = self.__ui.bcpdAccelerationManualNystormJSpinBox.value + params[const.BCPD_VALUE_KEY_NYSTORM_R] = self.__ui.bcpdAccelerationManualNystormRSpinBox.value - if self.__ui.bcpdAccelerationManualKdTreeGroupBox.checked == True: + if self.__ui.bcpdAccelerationManualKdTreeGroupBox.checked is True: # Option switch without a value - params[BCPD_VALUE_KEY_KD_TREE] = "" - params[BCPD_VALUE_KEY_KD_TREE_SCALE] = self.__ui.bcpdAccelerationManualKdTreeScaleDoubleSpinBox.value - params[BCPD_VALUE_KEY_KD_TREE_RADIUS] = self.__ui.bcpdAccelerationManualKdTreeRadiusDoubleSpinBox.value - params[BCPD_VALUE_KEY_KD_TREE_THRESHOLD] = self.__ui.bcpdAccelerationManualKdTreeThresholdDoubleSpinBox.value + params[const.BCPD_VALUE_KEY_KD_TREE] = "" + params[const.BCPD_VALUE_KEY_KD_TREE_SCALE] = self.__ui.bcpdAccelerationManualKdTreeScaleDoubleSpinBox.value + params[const.BCPD_VALUE_KEY_KD_TREE_RADIUS] = self.__ui.bcpdAccelerationManualKdTreeRadiusDoubleSpinBox.value + params[const.BCPD_VALUE_KEY_KD_TREE_THRESHOLD] = self.__ui.bcpdAccelerationManualKdTreeThresholdDoubleSpinBox.value ## Downsampling settings ## - - if params.get(BCPD_VALUE_KEY_DOWNSAMPLING) is None: - params[BCPD_VALUE_KEY_DOWNSAMPLING] = self.__ui.bcpdDownsamplingLineEdit.text + if params.get(const.BCPD_VALUE_KEY_DOWNSAMPLING) is None: + params[const.BCPD_VALUE_KEY_DOWNSAMPLING] = self.__ui.bcpdDownsamplingLineEdit.text ## Convergence options ## - params[BCPD_VALUE_KEY_CONVERGENCE_TOLERANCE] = self.__ui.bcpdConvergenceToleranceDoubleSpinBox.value - params[BCPD_VALUE_KEY_CONVERGENCE_MIN_ITERATIONS] = self.__ui.bcpdConvergenceMinIterationsSpinBox.value - params[BCPD_VALUE_KEY_CONVERGENCE_MAX_ITERATIONS] = self.__ui.bcpdConvergenceMaxIterationsSpinBox.value + params[const.BCPD_VALUE_KEY_CONVERGENCE_TOLERANCE] = self.__ui.bcpdConvergenceToleranceDoubleSpinBox.value + params[const.BCPD_VALUE_KEY_CONVERGENCE_MIN_ITERATIONS] = self.__ui.bcpdConvergenceMinIterationsSpinBox.value + params[const.BCPD_VALUE_KEY_CONVERGENCE_MAX_ITERATIONS] = self.__ui.bcpdConvergenceMaxIterationsSpinBox.value ## Normalization options ## - params[BCPD_VALUE_KEY_NORMALIZATION] = self.__ui.bcpdNormalizationComboBox.currentText.lower() + params[const.BCPD_VALUE_KEY_NORMALIZATION] = self.__ui.bcpdNormalizationComboBox.currentText.lower() def __parse_parameters_postprocessing(self) -> dict: params = {} - params[POSTPROCESSING_KEY_CLUSTERING_SCALING] = self.__ui.postprocessingClusteringScalingDoubleSpinBox.value - params[POSTPROCESSING_KEY_SMOOTHING_ITERATIONS] = self.__ui.processingSmoothingIterationsSpinBox.value + params[const.POSTPROCESSING_KEY_CLUSTERING_SCALING] = self.__ui.postprocessingClusteringScalingDoubleSpinBox.value + params[const.POSTPROCESSING_KEY_SMOOTHING_ITERATIONS] = self.__ui.processingSmoothingIterationsSpinBox.value return params def __generate_model(self) -> None: """ - Generate button callback. Calls the Logic's generate_model method and adds the results into the scene + Generate button callback. Calls the Logic's generate_model method and adds the results into the slicer.mrmlScene """ + from src.logic.SlicerBoneMorphingLogic import SlicerBoneMorphingLogic + + if self.__logic is None: + self.__logic = SlicerBoneMorphingLogic() params = self.__parse_parameters() err, generated_polydata, merged_polydata = self.__logic.generate_model( self.__ui.sourceNodeSelectionBox.currentNode(), self.__ui.targetNodeSelectionBox.currentNode(), params) - if (err == EXIT_OK): + if (err == const.EXIT_OK): model_node = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLModelNode', 'BCPD generated') model_node.SetAndObservePolyData(generated_polydata) model_node.CreateDefaultDisplayNodes() diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..dec93a6 --- /dev/null +++ b/tox.ini @@ -0,0 +1,15 @@ +[flake8] +# it's not a bug that we aren't using all of hacking, ignore: +# H101: Use TODO(NAME) +# H202: assertRaises Exception too broad +# H233: Python 3.x incompatible use of print operator +# H301: one import per line +# H306: imports not in alphabetical order (time, os) +# H401: docstring should not start with a space +# H403: multi line docstrings should end on a new line +# H404: multi line docstring should start without a leading new line +# H405: multi line docstring summary not separated with an empty line +# H501: Do not use self.__dict__ for string formatting +# E501: Line too long +# E266: Too much trailing ### in block comments +extend-ignore = E501, E266