Skip to content

Commit

Permalink
Merge pull request #22 from HarryHeres/version/0.2.0
Browse files Browse the repository at this point in the history
Removing version branch
  • Loading branch information
HarryHeres authored Oct 12, 2024
2 parents 2c03139 + c6e8ff4 commit 5527c25
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 44 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +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.
At the moment, this module specializes on the *humerus* bone, but the use case is not limited to it.

## Special thanks
## Special thanks 🙏
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))
I would also 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:**
Expand All @@ -26,7 +26,7 @@ After a successful installation, the module will be available in the **Morphing*
## How to use
For guidance on how to use the module, please refer to this [Guide](./docs/how-to-use.md).

## Contributors
## Contributors ❤️
A huge thank you to all of the contributors!

<a href="https://github.com/HarryHeres/SlicerBoneMorphing/graphs/contributors">
Expand Down
23 changes: 15 additions & 8 deletions SlicerBoneMorphing/Resources/UI/SlicerBoneMorphing.ui
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
<item>
<widget class="ctkCollapsibleButton" name="visualizationCollapsibleButton">
<property name="text">
<string>Visualization</string>
<string>Options</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QCheckBox" name="visualizationVisualizeCheckBox">
<widget class="QCheckBox" name="optionsVisualizeCheckBox">
<property name="toolTip">
<string>If checked, the pipeline steps will be visualized using Open3D</string>
</property>
Expand All @@ -30,38 +30,45 @@
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="visualizationModelColorGroupBox">
<property name="title">
<string>Coloring</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1">
<widget class="ctkColorPickerButton" name="visualizationSourceModelColorPickerButton"/>
<widget class="ctkColorPickerButton" name="optionsSourceModelColorPickerButton"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="visualizationSourceModelColorLabel">
<widget class="QLabel" name="optionsSourceModelColorLabel">
<property name="text">
<string>Source model color</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<widget class="QLabel" name="optionsTargetModelColorLabel">
<property name="text">
<string>Target model color</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="ctkColorPickerButton" name="visualizationTargetModelColorPickerButton"/>
<widget class="ctkColorPickerButton" name="optionsTargetModelColorPickerButton"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="optionsImportRegistrationModelCheckBox">
<property name="text">
<string>Import rigid registration model into the scene</string>
</property>
</widget>
</item>
<item>
<widget class="ctkCollapsibleButton" name="inputSectionCollapsibleButton">
<property name="text">
Expand Down Expand Up @@ -1397,7 +1404,7 @@
</hints>
</connection>
<connection>
<sender>visualizationVisualizeCheckBox</sender>
<sender>optionsVisualizeCheckBox</sender>
<signal>toggled(bool)</signal>
<receiver>visualizationModelColorGroupBox</receiver>
<slot>setVisible(bool)</slot>
Expand Down
2 changes: 1 addition & 1 deletion SlicerBoneMorphing/SlicerBoneMorphing.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ 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.
Version: 0.2.0-rc.1
Version: 0.2.0-rc.2
"""

self.parent.acknowledgementText = """
Expand Down
16 changes: 9 additions & 7 deletions SlicerBoneMorphing/src/logic/Constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@

BCPD_MULTIPLE_VALUES_SEPARATOR = ","

VISUALIZATION_KEY = "visualization"
VISUALIZATION_KEY_SHOULD_VISUALIZE = "sv"
VISUALIZATION_KEY_SOURCE_MODEL_COLOR = "smc"
VISUALIZATION_KEY_TARGET_MODEL_COLOR = "tmc"

VISUALIZATION_DEFAULT_VALUE_SOURCE_MODEL_COLOR = qt.QColor(255, 0, 0, 0)
VISUALIZATION_DEFAULT_VALUE_TARGET_MODEL_COLOR = qt.QColor(0, 255, 0, 0)
### OPTIONS ###
OPTIONS_KEY = "options"
OPTIONS_KEY_VISUALIZE_RESULTS = "vr"
OPTIONS_KEY_SOURCE_MODEL_COLOR = "smc"
OPTIONS_KEY_TARGET_MODEL_COLOR = "tmc"
OPTIONS_KEY_IMPORT_REGISTRATION_MODEL = "irm"

OPTIONS_DEFAULT_VALUE_SOURCE_MODEL_COLOR = qt.QColor(255, 0, 0, 0)
OPTIONS_DEFAULT_VALUE_TARGET_MODEL_COLOR = qt.QColor(0, 255, 0, 0)

### PREPROCESSING PARAMETERS ###
PREPROCESSING_KEY = "preprocessing"
Expand Down
47 changes: 33 additions & 14 deletions SlicerBoneMorphing/src/logic/SlicerBoneMorphingLogic.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import src.logic.Constants as const
import tempfile
import subprocess
from subprocess import CalledProcessError
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
import slicer
from slicer.ScriptedLoadableModule import ScriptedLoadableModuleLogic
from slicer import vtkMRMLModelNode
import slicer.util as su
Expand All @@ -17,7 +19,13 @@
except ModuleNotFoundError:
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
if (platform == 'darwin'):
# Must be pin-pointed directly due to defaulting to the 'universal' wheel, which does not work under Rosetta
# Submitted at https://github.com/isl-org/Open3D/issues/6994
su.pip_install('https://github.com/isl-org/Open3D/releases/download/v0.18.0/open3d-0.18.0-cp39-cp39-macosx_11_0_x86_64.whl')
else:
su.pip_install('open3d')

import open3d as o3d
else:
print("Open3D is not installed, but is required")
Expand All @@ -38,7 +46,7 @@ class SlicerBoneMorphingLogic(ScriptedLoadableModuleLogic):
def __init__(self, parent=None):
ScriptedLoadableModuleLogic.__init__(self, parent)

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):
def __visualize(self, source, target, window_name: str = "", source_color=const.OPTIONS_DEFAULT_VALUE_TARGET_MODEL_COLOR, target_color=const.OPTIONS_KEY_TARGET_MODEL_COLOR):
models = []

if (source is not None):
Expand Down Expand Up @@ -81,24 +89,29 @@ def generate_model(
return const.EXIT_FAILURE, None

source_mesh.transform(result_icp.transformation)
options_params = parameters[const.OPTIONS_KEY]

visualization_params = parameters[const.VISUALIZATION_KEY]
if (options_params[const.OPTIONS_KEY_IMPORT_REGISTRATION_MODEL] is True):
registration_result = self.__convert_mesh_to_vtk_polydata(source_mesh)
registration_node = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLModelNode', "_".join([target_model.GetName(), "rigid_registration_result"]))
registration_node.SetAndObservePolyData(registration_result)
registration_node.CreateDefaultDisplayNodes()

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])
if (options_params[const.OPTIONS_KEY_VISUALIZE_RESULTS] is True):
self.__visualize(source_mesh, target_mesh, "Preprocessed models", options_params[const.OPTIONS_KEY_SOURCE_MODEL_COLOR], options_params[const.OPTIONS_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

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])
if (options_params[const.OPTIONS_KEY_VISUALIZE_RESULTS] is True):
self.__visualize(deformed, None, "Reconstructed model", options_params[const.OPTIONS_KEY_SOURCE_MODEL_COLOR], options_params[const.OPTIONS_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])
if (options_params[const.OPTIONS_KEY_VISUALIZE_RESULTS] is True):
self.__visualize(deformed, None, "Postprocessed model", options_params[const.OPTIONS_KEY_SOURCE_MODEL_COLOR], options_params[const.OPTIONS_KEY_TARGET_MODEL_COLOR])

return const.EXIT_OK, generated_polydata

Expand Down Expand Up @@ -375,11 +388,17 @@ def __deformable_registration(
cmd += f' -o {output_path}'
print("BCPD: " + cmd)

subprocess.run(cmd,
shell=True,
check=True,
text=True,
capture_output=True)
try:
subprocess.run(cmd,
shell=True,
check=True,
text=True,
capture_output=True)
except CalledProcessError as e:
print("BCPD subprocess returned with error (code {}): {}".format(e.returncode, e.output))
print("Process output: {}".format(e.output))
print("Errors: {}".format(e.stderr))
return None

try:
bcpdResult = np.loadtxt(output_path + "y.interpolated.txt")
Expand Down
19 changes: 11 additions & 8 deletions SlicerBoneMorphing/src/widget/SlicerBoneMorphingWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ def __setup_ui(self) -> None:
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)
## Options ##
self.__ui.optionsVisualizeCheckBox.setChecked(False)
self.__ui.optionsSourceModelColorPickerButton.setColor(const.OPTIONS_DEFAULT_VALUE_SOURCE_MODEL_COLOR)
self.__ui.optionsTargetModelColorPickerButton.setColor(const.OPTIONS_DEFAULT_VALUE_TARGET_MODEL_COLOR)
self.__ui.optionsImportRegistrationModelCheckBox.setChecked(False)

## Preprocessing parameters ##
self.__ui.preprocessingDownsamplingVoxelSizeDoubleSpinBox.value = const.PREPROCESSING_DEFAULT_VALUE_DOWNSAMPLING_VOXEL_SIZE
Expand Down Expand Up @@ -157,18 +159,19 @@ def __parse_parameters(self) -> dict:
Parsing parameters from the UI user option elements
"""
params = {}
params[const.VISUALIZATION_KEY] = self.__parse_parameters_visualization()
params[const.OPTIONS_KEY] = self.__parse_parameters_options()
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:
def __parse_parameters_options(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
params[const.OPTIONS_KEY_VISUALIZE_RESULTS] = self.__ui.optionsVisualizeCheckBox.checked
params[const.OPTIONS_KEY_SOURCE_MODEL_COLOR] = self.__ui.optionsSourceModelColorPickerButton.color
params[const.OPTIONS_KEY_TARGET_MODEL_COLOR] = self.__ui.optionsTargetModelColorPickerButton.color
params[const.OPTIONS_KEY_IMPORT_REGISTRATION_MODEL] = self.__ui.optionsImportRegistrationModelCheckBox.checked

return params

Expand Down
5 changes: 3 additions & 2 deletions docs/how-to-use.md
Original file line number Diff line number Diff line change
@@ -1,6 +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.
### Options
Here, you have some pretty self-explanatory options, e.g. visualization of each step of the pipeline.

### Input section
- Source = The mean model, i.e. a full humerus
Expand Down Expand Up @@ -60,3 +60,4 @@ For these, we let you modify the following parameters:
- If set to 0, no smoothing is applied

After the whole process is done, the generated mesh is imported back into the current Slicer scene.

0 comments on commit 5527c25

Please sign in to comment.