diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..bafcbaf --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,43 @@ +cff-version: 1.2.0 +message: "If you use any SciKit-Surgery packages in your research, please cite it as below." +authors: +- family-names: "Olafsdottir" + given-names: "Asta" +- family-names: "Thompson" + given-names: "Stephen" + orcid: "https://orcid.org/0000-0001-7286-1326" +title: "SciKit-SurgeryGlenoid" +url: "https://github.com/SciKit-Surgery/scikit-surgery" +preferred-citation: + type: article + authors: + - family-names: "Thompson" + given-names: "Stephen" + orcid: "https://orcid.org/0000-0001-7286-1326" + - family-names: "Dowrick" + given-names: "Thomas" + orcid: "https://orcid.org/0000-0002-2712-4447" + - family-names: "Ahmad" + given-names: "Mian" + orcid: "https://orcid.org/0000-0002-4706-4146" + - family-names: "Xiao" + given-names: "Guofang" + - family-names: "Koo" + given-names: "Bongjin" + orcid: "https://orcid.org/0000-0002-3611-4988" + - family-names: "Bonmati" + given-names: "Ester" + orcid: "https://orcid.org/0000-0001-9217-5438" + - family-names: "Kahl" + given-names: "Kim" + - family-names: "Clarkson" + given-names: "Matthew" + orcid: "https://orcid.org/0000-0002-5565-1252" + doi: 10.1007/s11548-020-02180-5 + journal: "International journal of computer assisted radiology and surgery" + start: 1075 + end: 1084 + title: "SciKit-Surgery: Compact Libraries for Surgical Navigation" + volume: 15 + issue: 7 + year: 2020 diff --git a/docs/dependency_graph.dot b/docs/dependency_graph.dot index 695adb7..4372be7 100644 --- a/docs/dependency_graph.dot +++ b/docs/dependency_graph.dot @@ -3,6 +3,7 @@ digraph prof { node [style=filled]; "SciKit-SurgeryGlenoid" -> "NumPy"; "SciKit-SurgeryGlenoid" -> "SciKit-SurgeryVTK" [color="0.515 0.762 0.762"]; + "SciKit-SurgeryGlenoid" -> "SciKit-SurgeryCore" [color="0.515 0.762 0.762"]; "SciKit-SurgeryGlenoid" -> "VTK"; "SciKit-SurgeryVTK" -> "NumPy"; diff --git a/glenoidplanefitting/algorithms/colour_palette.py b/glenoidplanefitting/algorithms/colour_palette.py new file mode 100644 index 0000000..5fde8f2 --- /dev/null +++ b/glenoidplanefitting/algorithms/colour_palette.py @@ -0,0 +1,27 @@ +""" +A colour palette based on Bang's colour pallette +Wong, B. Points of view: Color blindness. Nat Methods 8, 441 (2011). +https://doi.org/10.1038/nmeth.1618 +""" + +bang_palette={ 'black' : [0,0,0], + 'orange' : [230./255., 159./255., 0], + 'sky blue' : [86./255., 180./255., 233./255.], + 'bluish green' : [0./255., 158./255., 115./255.], + 'yellow' : [240./255., 228./255., 66./255.], + 'blue' : [0./255., 114./255., 178./255.], + 'vermillion' : [213./255., 94./255., 0./255.], + 'reddish purple' : [204./255., 121./255., 167./255.] + } + +weiss_light_blue = [0.0, 0.384, 0.490] + + +def bang_list(): + """ + Returns the bang palette as a list + """ + bp_list = [] + for _key, value in bang_palette.items(): + bp_list.append(value) + return bp_list diff --git a/glenoidplanefitting/algorithms/models.py b/glenoidplanefitting/algorithms/models.py index 282973f..fff16d5 100644 --- a/glenoidplanefitting/algorithms/models.py +++ b/glenoidplanefitting/algorithms/models.py @@ -2,7 +2,7 @@ Functions to create vtk models for visualisation of results """ -from vtk import vtkPlaneSource, vtkLineSource #pylint:disable=no-name-in-module +from vtk import vtkPlaneSource, vtkLineSource, vtkSphereSource #pylint:disable=no-name-in-module def make_plane_model(plane_centre, normal_vector, resolution = 10, plane_size = 200.0): @@ -56,3 +56,19 @@ def make_vault_model(point1,point2): line.SetPoint1(point1) line.SetPoint2(point2) return line + +def make_sphere_model(point, radius = 5.0): + """ + Make a sphere source which we can use to represent a landmark + point + + :param point: the point + + :returns the vtkPointSource + """ + sphere_source = vtkSphereSource() + sphere_source.SetCenter(point) + sphere_source.SetThetaResolution(12) + sphere_source.SetPhiResolution(12) + sphere_source.SetRadius(radius) + return sphere_source diff --git a/glenoidplanefitting/ui/glenoidplanefitting_command_line.py b/glenoidplanefitting/ui/glenoidplanefitting_command_line.py index 8b5b7ba..520280c 100644 --- a/glenoidplanefitting/ui/glenoidplanefitting_command_line.py +++ b/glenoidplanefitting/ui/glenoidplanefitting_command_line.py @@ -41,7 +41,7 @@ def main(args=None): help="Landmark points file (vault)" ) - parser.add_argument("-c", "--corr_fried", + parser.add_argument("-cf", "--corr_fried", required=False, type=str, default="", @@ -62,6 +62,14 @@ def main(args=None): help="Visualise the results" ) + parser.add_argument("-c", "--config", + required=False, + type=str, + default=None, + help="A configuration file" + ) + + version_string = __version__ friendly_version_string = version_string if version_string else 'unknown' parser.add_argument( @@ -73,4 +81,4 @@ def main(args=None): run_demo(args.model, args.planes, args.fried_points, args.vault_points,args.corr_fried, args.output, - args.visualise) + args.visualise, args.config) diff --git a/glenoidplanefitting/ui/glenoidplanefitting_demo.py b/glenoidplanefitting/ui/glenoidplanefitting_demo.py index 68dbeb1..aa00e69 100644 --- a/glenoidplanefitting/ui/glenoidplanefitting_demo.py +++ b/glenoidplanefitting/ui/glenoidplanefitting_demo.py @@ -5,15 +5,18 @@ from vtk import vtkXMLPolyDataWriter #pylint:disable=no-name-in-module import numpy as np from sksurgeryvtk.models.vtk_surface_model import VTKSurfaceModel +from sksurgerycore.configuration.configuration_manager import ( + ConfigurationManager + ) from glenoidplanefitting.algorithms import plane_fitting, friedman, vault from glenoidplanefitting.widgets.visualisation import vis_planes, vis_fried, \ vis_vault from glenoidplanefitting.algorithms.models import make_plane_model, \ make_friedman_model, make_vault_model - +#pylint: disable=too-many-locals def run_demo(model_file_name, planes="", fried_points="", vault_points="", - corr_fried="", output="", visualise = False): + corr_fried="", output="", visualise = False, config_file = None): """ :param planes: File name pointing to file containing points for planes method. @@ -51,9 +54,23 @@ def run_demo(model_file_name, planes="", fried_points="", vault_points="", used as the new axial slice for picking the new landmark points for the 3D corrected Friedman method. - """ - model = VTKSurfaceModel(model_file_name, [1., 0., 0.]) + :param config_file: We can pass a configuration file, currently + focusing on visualisation parameters + """ + configuration = {} + if config_file is not None: + configurer = ConfigurationManager(config_file) + configuration = configurer.get_copy() + model_colour = configuration.get('model colour', + [0.89, 0.86, 0.79]) #bone from https://www.colorhexa.com/e3dac9 + plane_resolution = configuration.get('plane resolution', 1) + plane_size = configuration.get('plane size' , 200.0) + vary_plane_colour = configuration.get('vary plane colour', True) + point_size = configuration.get('point size', 3.0) + line_width = configuration.get('line width', 5) + + model = VTKSurfaceModel(model_file_name, model_colour) version = None if planes != "": @@ -77,7 +94,9 @@ def run_demo(model_file_name, planes="", fried_points="", vault_points="", return_meta3) if visualise: - vis_planes(model, [result, result2, result3]) + vis_planes(model, [result, result2, result3], points1, points2, + plane_resolution, plane_size, vary_plane_colour, + point_size) if fried_points != "": @@ -92,7 +111,7 @@ def run_demo(model_file_name, planes="", fried_points="", vault_points="", posterior_glenoid) if visualise: vis_fried(model, anterior_glenoid, posterior_glenoid, - glenoid_centre, result) + glenoid_centre, result, line_width = line_width) if vault_points !="": @@ -105,7 +124,7 @@ def run_demo(model_file_name, planes="", fried_points="", vault_points="", result = vault.create_vault_line(anterior_glenoid,posterior_glenoid) if visualise: vis_vault(model, anterior_glenoid, posterior_glenoid, - glenoid_centre, result) + glenoid_centre, result, line_width = line_width) if corr_fried !="": diff --git a/glenoidplanefitting/widgets/visualisation.py b/glenoidplanefitting/widgets/visualisation.py index 7d0eb51..821da02 100644 --- a/glenoidplanefitting/widgets/visualisation.py +++ b/glenoidplanefitting/widgets/visualisation.py @@ -3,18 +3,19 @@ """ import vtk from glenoidplanefitting.algorithms.models import make_plane_model, \ - make_friedman_model, make_vault_model + make_friedman_model, make_vault_model, make_sphere_model +from glenoidplanefitting.algorithms.colour_palette import bang_list -def renderer_common(bone): +def renderer_common(bone, background_colour = None): """ Initialises a vtk renderer and adds the bone model """ + if background_colour is None: + background_colour = [0.9, 0.9, 0.9] renderer = vtk.vtkRenderer() #pylint:disable=no-member + renderer.SetBackground(background_colour) - bone.ambient = 1.0 - bone.diffuse = 1.0 - bone.specular = 1.0 bone.actor.GetProperty().SetAmbient(1) bone.actor.GetProperty().SetDiffuse(1) bone.actor.GetProperty().SetSpecular(1) @@ -41,20 +42,39 @@ def render_window_common(renderer, window_name): render_window.Finalize() del render_window_interactor, render_window -def add_vtk_source(renderer, source): +def add_vtk_source(renderer, source, linewidth = 1.0, opacity = 1.0, + wireframe = False, colour = None): """ simplifies adding a vtk geometry source to a renderer :param renderer: a vtk renderer to add to :param source: a vtk geometry source """ + if colour is None: + colour = [1.0, 1.0, 1.0] mapper = vtk.vtkPolyDataMapper() #pylint:disable=no-member mapper.SetInputConnection(source.GetOutputPort()) actor = vtk.vtkActor() #pylint:disable=no-member actor.SetMapper(mapper) + actor.GetProperty().SetLineWidth(linewidth) + actor.GetProperty().SetOpacity(opacity) + if wireframe: + actor.GetProperty().SetRepresentationToWireframe() + actor.GetProperty().SetAmbientColor(colour) + actor.GetProperty().SetDiffuseColor(colour) + actor.GetProperty().SetSpecularColor(colour) + else: + actor.GetProperty().SetRepresentationToSurface() + + actor.GetProperty().SetColor(colour) + actor.GetProperty().SetEdgeColor(colour) + renderer.AddActor(actor) -def vis_planes(bone, planes): +def vis_planes(bone, planes, points1 = False, points2 = False, + resolution = 1, plane_size = 200.0, + vary_plane_colour = False, + point_size = 5.0): """ Visualisation for plane fitting methods @@ -64,13 +84,32 @@ def vis_planes(bone, planes): """ renderer = renderer_common(bone) - for plane in planes: - plane_source = make_plane_model(plane[1], plane[2]) - add_vtk_source(renderer, plane_source) + for item, plane in enumerate(planes): + colour = [1., 1., 1.] + if vary_plane_colour: + colour = bang_list()[item] + + plane_source = make_plane_model(plane[1], plane[2], resolution, + plane_size) + add_vtk_source(renderer, plane_source, opacity = 0.15, colour = colour) + add_vtk_source(renderer, plane_source, linewidth = 2, opacity = 1.0, + wireframe = True, colour = colour) + + if points1: + for point in points1: + colour = bang_list()[0] + sphere_source = make_sphere_model(point, point_size) + add_vtk_source(renderer, sphere_source, colour = colour) + + if points2: + for point in points2: + colour = bang_list()[1] + sphere_source = make_sphere_model(point, point_size) + add_vtk_source(renderer, sphere_source, colour = colour) render_window_common(renderer, "Fitted Planes") -def vis_fried(bone, cross1, cross2, glenoid1, result): +def vis_fried(bone, cross1, cross2, glenoid1, result, line_width = 5): """ Visualise the lines resulting from the friedman method. @@ -82,15 +121,27 @@ def vis_fried(bone, cross1, cross2, glenoid1, result): renderer = renderer_common(bone) glenoid_line = make_friedman_model(cross1,cross2) - add_vtk_source(renderer, glenoid_line) + colour = bang_list()[0] + add_vtk_source(renderer, glenoid_line, linewidth = line_width, + colour = colour ) + + sphere_source = make_sphere_model(cross1) + add_vtk_source(renderer, sphere_source, colour = colour) + sphere_source = make_sphere_model(cross2) + add_vtk_source(renderer, sphere_source, colour = colour) + + colour = bang_list()[1] + sphere_source = make_sphere_model(glenoid1) + add_vtk_source(renderer, sphere_source, colour = colour) friedman_line = make_friedman_model(glenoid1,result) - add_vtk_source(renderer, friedman_line) + add_vtk_source(renderer, friedman_line, linewidth = line_width, + colour = colour) render_window_common(renderer, "Friedman Lines") -def vis_vault(bone, cross1, cross2, glenoid1, result): +def vis_vault(bone, cross1, cross2, glenoid1, result, line_width = 5): """ Visualise the lines resulting from the vault method. @@ -102,9 +153,21 @@ def vis_vault(bone, cross1, cross2, glenoid1, result): renderer = renderer_common(bone) glenoid_line = make_vault_model(cross1,cross2) - add_vtk_source(renderer, glenoid_line) + colour = bang_list()[0] + add_vtk_source(renderer, glenoid_line, linewidth = line_width, + colour = colour) + + sphere_source = make_sphere_model(cross1) + add_vtk_source(renderer, sphere_source, colour = colour) + sphere_source = make_sphere_model(cross2) + add_vtk_source(renderer, sphere_source, colour = colour) + + colour = bang_list()[1] + sphere_source = make_sphere_model(glenoid1) + add_vtk_source(renderer, sphere_source, colour = colour) vault_line = make_vault_model(glenoid1, result) - add_vtk_source(renderer, vault_line) + add_vtk_source(renderer, vault_line, linewidth = line_width, + colour = colour) render_window_common(renderer, "Vault Lines") diff --git a/requirements.txt b/requirements.txt index 1e6c820..5b4f389 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ numpy vtk scikit-surgeryvtk +scikit-surgerycore diff --git a/setup.py b/setup.py index 5ba06bf..165b161 100644 --- a/setup.py +++ b/setup.py @@ -54,6 +54,7 @@ 'numpy', 'vtk', 'scikit-surgeryvtk' + 'scikit-surgerycore' ], entry_points={ diff --git a/vis_config.json b/vis_config.json new file mode 100644 index 0000000..3c470dd --- /dev/null +++ b/vis_config.json @@ -0,0 +1,8 @@ +{ + "model colour" : [0.89, 0.86, 0.79], + "plane resolution" : 1, + "plane size" : 200.0, + "vary plane colour" : true, + "point size" : 3.0, + "line width" : 4 +}