Repository: KitwareMedical/HASI (language: python)

Issue:
ENH: Return transform from registration methods
Updates registration methods to return the tuple `(transform, transformed_mesh)` so that transforms may be used directly in shape analysis.


Discussion / hints:

Fix patch:
diff --git a/examples/MeshToMeshRegistration.ipynb b/examples/MeshToMeshRegistration.ipynb
index c98649d..a1bf1ab 100644
--- a/examples/MeshToMeshRegistration.ipynb
+++ b/examples/MeshToMeshRegistration.ipynb
@@ -8,7 +8,7 @@
     "\n",
     "ITK natively supports image-to-image registration, which is a common operation for medical images with symmetry. Another common method of storing 3D volumetric data is to represent volume surfaces with meshes. In this example we seek to register two meshes using various metrics and optimization techniques built on top of ITK.\n",
     "\n",
-    "Registration classes are defined in the HASI `registration` submodule and built on top of the ITK Python wrapping. The `MeanSquaresRegistrar` and `DiffeoRegistrar` classes apply registration techniques to images derived from mesh inputs. Mesh registration is carried out with each class in this notebook on sample bone femur mesh data in the `examples/Data` folder. "
+    "Registration classes are defined in the Python `hasi` submodule and built on top of the ITK Python wrapping. The `MeanSquaresRegistrar`, `DiffeoRegistrar`, and `ElastixRegistrar` classes apply registration techniques to images derived from mesh inputs, while the `PointSetEntropyRegistrar` aims to register meshes via point set metrics. Mesh registration is carried out with each class in this notebook on sample bone femur mesh data in the `examples/Data` folder."
    ]
   },
   {
@@ -18,12 +18,13 @@
     "This notebook requires the following modules, which can be either acquired via `pip` or built alongside the ITK `master` branch:\n",
     "- [ITK](https://github.com/InsightSoftwareConsortium/ITK/)\n",
     "- [ITKBoneEnhancement](https://github.com/InsightSoftwareConsortium/ITKBoneEnhancement)\n",
-    "- [ITKMeshToPolyData](https://github.com/InsightSoftwareConsortium/ITKMeshToPolyData)"
+    "- [ITKMeshToPolyData](https://github.com/InsightSoftwareConsortium/ITKMeshToPolyData)\n",
+    "- [ITKElastix](https://github.com/InsightSoftwareConsortium/ITKElastix)"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 1,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -37,7 +38,7 @@
     "import itk\n",
     "from itkwidgets import view, checkerboard, compare\n",
     "from ipywidgets import FloatProgress, Label, HBox, VBox, FloatText, ColorPicker, Button\n",
-    "PATTERN_COUNT = 10\n",
+    "PATTERN_COUNT = 5\n",
     "\n",
     "module_path = os.path.abspath(os.path.join('..'))\n",
     "\n",
@@ -47,49 +48,58 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": 2,
    "metadata": {},
    "outputs": [],
    "source": [
-    "os.makedirs('Data', exist_ok=True)"
+    "os.makedirs('Input', exist_ok=True)\n",
+    "os.makedirs('Output', exist_ok=True)"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": 3,
    "metadata": {},
    "outputs": [],
    "source": [
-    "FIXED_MESH_FILE = 'Data/901-L-mesh.vtk'\n",
-    "MOVING_MESH_FILE = 'Data/901-R-mesh.vtk'\n",
+    "meshes = ['901','902','906','907','908','915','916','917','918']\n",
+    "MESH_TO_USE = meshes[0]\n",
+    "\n",
+    "TARGET_MESH_FILE = f'Input/{MESH_TO_USE}-R-mesh.vtk'\n",
+    "# Future update will replace with smaller template\n",
+    "TEMPLATE_MESH_FILE = f'Input/901-L-mesh.vtk'\n",
     "\n",
-    "MEANSQUARES_OUTPUT_FILE = 'Data/901-meansquares-registered.vtk'\n",
-    "DIFFEO_OUTPUT_FILE = 'Data/901-diffeo-registered.vtk'"
+    "MEANSQUARES_OUTPUT_FILE = f'Output/{MESH_TO_USE}-meansquares-registered.obj'\n",
+    "DIFFEO_OUTPUT_FILE = f'Output/{MESH_TO_USE}-diffeo-registered.obj'\n",
+    "ELASTIX_OUTPUT_FILE = f'Output/{MESH_TO_USE}-elastix-registered.obj'\n",
+    "POINTSET_OUTPUT_FILE = f'Output/{MESH_TO_USE}-pointset-registered.obj'"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
+   "execution_count": 4,
    "metadata": {},
    "outputs": [],
    "source": [
     "# Download meshes\n",
-    "if not os.path.exists(FIXED_MESH_FILE):\n",
-    "    url = 'https://data.kitware.com/api/v1/file/5f9daaae50a41e3d1924dae1/download'\n",
-    "    urlretrieve(url, FIXED_MESH_FILE)\n",
-    "if not os.path.exists(MOVING_MESH_FILE):\n",
+    "if not os.path.exists(TARGET_MESH_FILE):\n",
     "    url = 'https://data.kitware.com/api/v1/file/5f9daaba50a41e3d1924dae9/download'\n",
-    "    urlretrieve(url, MOVING_MESH_FILE)"
+    "    urlretrieve(url, TARGET_MESH_FILE)\n",
+    "if not os.path.exists(TEMPLATE_MESH_FILE):\n",
+    "    url = 'https://data.kitware.com/api/v1/file/5f9daaae50a41e3d1924dae1/download'\n",
+    "    urlretrieve(url, TEMPLATE_MESH_FILE)"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
-   "metadata": {},
+   "execution_count": 5,
+   "metadata": {
+    "scrolled": true
+   },
    "outputs": [],
    "source": [
-    "fixed_mesh = itk.meshread(FIXED_MESH_FILE, itk.F)\n",
-    "moving_mesh = itk.meshread(MOVING_MESH_FILE, itk.F)"
+    "template_mesh = itk.meshread(TEMPLATE_MESH_FILE, itk.F)\n",
+    "target_mesh = itk.meshread(TARGET_MESH_FILE, itk.F)"
    ]
   },
   {
@@ -103,43 +113,26 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
-   "metadata": {
-    "scrolled": true
-   },
-   "outputs": [
-    {
-     "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "28896761f6a94485b95131a6c102765a",
-       "version_major": 2,
-       "version_minor": 0
-      },
-      "text/plain": [
-       "Viewer(geometries=[{'vtkClass': 'vtkPolyData', 'points': {'vtkClass': 'vtkPoints', 'numberOfComponents': 3, 'd…"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
-    "view(geometries=[fixed_mesh,moving_mesh])"
+    "view(geometries=[template_mesh,target_mesh])"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "# Mesh-To-Image Conversion in the Base Class\n",
-    "Mesh registration classes inheriting from `MeshToMeshRegistrar` implement unique registration algorithms. The base class includes common definitions for 3D mesh-to-mesh registration, abstract methods, and a mesh-to-image conversion method.\n",
+    "## Mesh-To-Image Conversion in the Base Class\n",
+    "Python registration classes inheriting from `MeshToMeshRegistrar` implement unique registration algorithms. The base class includes common definitions for 3D mesh-to-mesh registration, abstract methods, and a mesh-to-image conversion method.\n",
     "\n",
     "The `mesh_to_image` function takes in a 3D mesh and converts it into an ITK 3D image object. The spacing, origin, and size of the 3D image may be calculated from the minimum bounding box of the mesh or set from a reference image."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
+   "execution_count": 6,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -149,30 +142,40 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 15,
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "template_image = registrar.mesh_to_image(template_mesh)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
    "metadata": {},
    "outputs": [],
    "source": [
-    "fixed_img = registrar.mesh_to_image(fixed_mesh)\n",
-    "moving_img = registrar.mesh_to_image(moving_mesh, fixed_img)"
+    "target_image = registrar.mesh_to_image(target_mesh, template_image)"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Comparing the meshes generated from the given meshes, we see that the two bone images are generally similar but do not exactly align. Registration will translate one mesh so that the two bones better coincide."
+    "Comparing the meshes generated from the given meshes, we see that the two bone images are generally similar but do not exactly line up together. Registration will translate one mesh so that the two bones better coincide."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 16,
-   "metadata": {},
+   "execution_count": 9,
+   "metadata": {
+    "scrolled": true
+   },
    "outputs": [
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "615313a2a584448fb0d8ddec1713256e",
+       "model_id": "921d76aefc6c432da62a9e16d76ed311",
        "version_major": 2,
        "version_minor": 0
       },
@@ -185,7 +188,38 @@
     }
    ],
    "source": [
-    "checkerboard(fixed_img, moving_img, pattern=PATTERN_COUNT)"
+    "# TODO second image does not match first image\n",
+    "checkerboard(template_image, target_image, pattern=PATTERN_COUNT)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Point set downsizing in the Base Class\n",
+    "\n",
+    "The base `MeshToMeshRegistrar` class also provides functionality for uniform random point set sampling from a given mesh, primarily used to improve performance for point set based registration of dense meshes. Note that uniform random sampling may not be suitable for all shapes, in which case application-specific resampling should be carried out externally prior to registration."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "target_points_reduced = \\\n",
+    "    registrar.randomly_sample_mesh_points(mesh=target_mesh, sampling_rate=0.01)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "view(geometries=[target_mesh],point_sets=[target_points_reduced])"
    ]
   },
   {
@@ -194,14 +228,14 @@
    "source": [
     "## Run Mean Squares Image Registration\n",
     "\n",
-    "The `MeanSquaresRegistrar` class converts meshes to images and runs [Broyden–Fletcher–Goldfarb–Shanno Optimization](https://itk.org/Doxygen/html/classitk_1_1LBFGSBOptimizerv4.html) on a `BSplineTransform` to iteratively reduce the mean square error. The resulting transform is then applied to resample the target mesh into the template mesh domain.\n",
+    "The `MeanSquaresRegistrar` class converts meshes to images and runs [Broyden-Fletcher-Goldfarb-Shanno Optimization](https://itk.org/Doxygen/html/classitk_1_1LBFGSBOptimizerv4.html) on a [BSplineTransform](https://itk.org/Doxygen/html/classitk_1_1BSplineTransform.html) to iteratively reduce the mean square error. The resulting transform is then applied to resample the target mesh into the template mesh domain.\n",
     "\n",
     "Progress is shown with an itkwidgets display via hooks into the ITK event-observer system. The resultant mesh is returned as an object in the Python environment and may be optionally written out to a file. Iteration updates may also be printed to the output window with the optional `verbose` flag."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 17,
+   "execution_count": 11,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -210,7 +244,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 18,
+   "execution_count": 12,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -220,7 +254,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 19,
+   "execution_count": 13,
    "metadata": {
     "scrolled": true
    },
@@ -228,7 +262,7 @@
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "e10ad99a2bec41bf8b19052f51398255",
+       "model_id": "fe4013ff61494dc1b21f6692b3807f74",
        "version_major": 2,
        "version_minor": 0
       },
@@ -255,7 +289,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 20,
+   "execution_count": 14,
    "metadata": {},
    "outputs": [
     {
@@ -264,7 +298,7 @@
        "0"
       ]
      },
-     "execution_count": 20,
+     "execution_count": 14,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -277,7 +311,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 21,
+   "execution_count": 15,
    "metadata": {
     "scrolled": true
    },
@@ -613,13 +647,7 @@
       "137 0.0018541785149490267 4.266601313887413e-06\n",
       "137 0.0018541785149490267 4.266601313887413e-06\n",
       "138 0.0018488759253997083 5.7777829698971234e-06\n",
-      "138 0.0018488759253997083 5.7777829698971234e-06\n"
-     ]
-    },
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
+      "138 0.0018488759253997083 5.7777829698971234e-06\n",
       "139 0.0018488214728622956 8.851509846565252e-06\n",
       "139 0.0018488214728622956 8.851509846565252e-06\n",
       "140 0.0018455469378301228 2.599417286662253e-05\n",
@@ -644,16 +672,16 @@
       "149 0.0018145124514857313 1.4350173651247737e-05\n",
       "150 0.0018066250300585167 1.5691026184229582e-05\n",
       "Solution = [0.13402887609394482, 0.38638569619547997, 0.23606438303975347, 0.08090428115206634, 0.3371552494822925, 0.6297552297838296, 1.1229306570791724, 0.5915276797073591, -0.6501585707310422, -0.7446125409124095, 1.5822996076684495, 0.33812689655954936, -0.09533707327220255, 0.14323242573263428, 0.39314045140991594, 0.01781402159117454, 0.44014202472024216, -0.34604681545726734, 0.8547492367739802, 0.48990145314663786, 2.6749226196952196, -1.115997974713456, -2.2156304215727864, 1.3388512328189912, -1.0166326637160048, 0.40567013436780897, 0.4902943676933346, -0.4657187939300901, -0.2555566706555399, 1.2858430169256285, 2.1924469624186034, 0.06952772124813388, -0.2117963367624445, -3.483371050637788, 0.2831559210498422, 0.45890124174333025, 3.414791757987822, -1.4964415045565087, 5.487554613707934, 2.0563929913927126, 3.6636118391013515, -0.3807696412152009, -0.6332402583374483, 0.44570394154035636, 0.1349228672715871, -1.9586756989974752, -0.43366531768299427, 0.14029650505679847, -0.054114649467300116, -0.5083922268479795, -0.07099260709634977, 0.03728747070526949, 0.5164211248876853, 1.2525939847253014, 2.6352389013389494, 0.4726049750573159, 0.8098614338927825, 2.2290435919556892, 2.9043193108797665, 0.4358126005278896, 0.04372904454553582, -0.17712576252753612, 0.09010849377520042, 0.04248498492279253, 0.01223590604363185, 0.23279929123081108, 0.09047686953540336, -0.08139176694422395, 0.19202795282424606, -0.634807469094887, -0.10656627630760404, -0.07819133706175223, 0.5265148877559752, -0.13480648932268952, 0.6613949318863346, 0.5723320665406273, 0.09111542897140043, 0.42576208634333257, 0.4342839852027086, 0.09950677971056757, -0.156382630346921, 1.7866157394416238, 0.2660375267913238, -0.5755626655447524, 1.01693940282988, 1.1509732776949892, 0.36381390390395646, 0.4471046323278427, 2.136348163836796, 2.493602467332689, -1.4615517705722725, 0.8990209460725334, -0.09044169348278833, 0.36104234336770813, -1.9926033922770345, -0.6721296056163661, -0.20584241565063283, 3.6633139805476738, 2.09481291943043, -0.3947558284710436, -0.7042816279383664, -1.0402970542204806, -1.8928521049020093, -0.6745743503897967, -2.58959112927434, -1.6728998075845298, 1.2573912942726535, -2.6913162173492307, -1.01002291778196, -0.5452327942699867, -2.426963175865346, -1.5426157950259753, -0.032788534297728975, 0.5666485439579507, 0.7026943381399053, 0.016612868290184636, -0.4964737922688639, -1.5340055878182774, 1.17155365623282, 0.053087199196095426, -0.36073715476533846, 3.8244385295298486, 5.614689616021612, -0.11038651919391634, -0.023662733868969568, 1.4681747949368995, 1.1172969873781085, -0.11472777348956885, 0.1449026071653071, 0.7140396698319708, 0.3431309289891663, -0.11253152597933845, 0.24299313896282662, 2.8354883696615905, -0.20263660945631462, -1.7232716709754285, -0.8830112348451592, -2.2219982131402, -4.191692565214607, -1.8666856707573343, -0.11646817800061632, -0.4571616974849223, -0.634137429528726, -0.1532889027157762, 1.027878699892728, -0.09788073444087389, -2.8338435420330383, -0.8109594305764039, 1.5604842372928454, -0.7238312237774122, 0.2281581976182558, -5.828843030703713, -1.8819478265095853, 0.43418325530092144, 3.02644640015456, -3.95112808408695, 0.39508288323345686, 2.438019488369614, -0.46554639096082967, -0.2725516749875228, 0.4230871002497909, -6.340114544972837, -7.54828683791304, -0.5541084804171731, 4.014375480276859, -3.1151755056752277, 3.538658134726041, 2.1842772830928747, 6.640082980035057, 1.5739462067596686, -4.037125817215529, 4.1939012019657165, 1.3181398735725214, -1.64624490603558, -6.339834341795177, 0.11531274484948192, 0.03130534903303278, -0.5852740144690427, -0.6759141397598057, -0.02469081670769886, 1.4263118053122015, 5.717379812994844, 3.403525966338044, 0.7546373580413233, 1.724077820650585, -2.471524317890313, -6.879051222251853, 0.5233901673952562, 0.12660694695255617, -2.164041703558112, -2.7992202738263874, -0.05941913541633608]\n",
-      "Wrote resulting mesh to Data/901-meansquares-registered.vtk\n"
+      "Wrote resulting mesh to Output/901-meansquares-registered.obj\n"
      ]
     }
    ],
    "source": [
-    "meansquares_mesh_result = registrar.register(template_mesh=fixed_mesh,\n",
-    "                                             target_mesh=moving_mesh,\n",
+    "(transform, mesh_result) = registrar.register(template_mesh,\n",
+    "                                             target_mesh,\n",
     "                                             filepath=MEANSQUARES_OUTPUT_FILE,\n",
-    "                                             num_iterations=200,\n",
-    "                                             verbose=True)"
+    "                                             verbose=True,\n",
+    "                                             num_iterations=200)"
    ]
   },
   {
@@ -665,26 +693,11 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 22,
+   "execution_count": null,
    "metadata": {
-    "scrolled": false
+    "scrolled": true
    },
-   "outputs": [
-    {
-     "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "c656c68980d14b0597f40a6b0caba96b",
-       "version_major": 2,
-       "version_minor": 0
-      },
-      "text/plain": [
-       "Viewer(geometries=[{'vtkClass': 'vtkPolyData', 'points': {'vtkClass': 'vtkPoints', 'numberOfComponents': 3, 'd…"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    }
-   ],
+   "outputs": [],
    "source": [
     "view(geometries=[meansquares_mesh_result,moving_mesh])"
    ]
@@ -698,7 +711,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 23,
+   "execution_count": 16,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -707,26 +720,11 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 24,
+   "execution_count": null,
    "metadata": {
     "scrolled": true
    },
-   "outputs": [
-    {
-     "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "556bd9eb307c4fa68194ee52f9fce4d8",
-       "version_major": 2,
-       "version_minor": 0
-      },
-      "text/plain": [
-       "Viewer(geometries=[{'vtkClass': 'vtkPolyData', 'points': {'vtkClass': 'vtkPoints', 'numberOfComponents': 3, 'd…"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    }
-   ],
+   "outputs": [],
    "source": [
     "view(geometries=[file_mesh_result,meansquares_mesh_result])"
    ]
@@ -744,7 +742,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 25,
+   "execution_count": 17,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -753,7 +751,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 26,
+   "execution_count": 18,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -762,13 +760,13 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 27,
+   "execution_count": 19,
    "metadata": {},
    "outputs": [
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "4689c76cf18e4e32a5e8c45d3cb53aa2",
+       "model_id": "3548d02c774e4a9d8672070b3a45794f",
        "version_major": 2,
        "version_minor": 0
       },
@@ -795,7 +793,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 28,
+   "execution_count": 20,
    "metadata": {
     "scrolled": true
    },
@@ -805,39 +803,39 @@
      "output_type": "stream",
      "text": [
       "1.7976931348623157e+308\n",
-      "0.039025624562107085\n",
+      "0.03902562456210709\n",
       "0.03620058111051529\n",
       "0.03385740047695736\n",
       "0.0316529992799868\n",
-      "0.02967127488345423\n",
-      "0.02786333676924597\n",
-      "0.026263721371434446\n",
+      "0.029671274883454234\n",
+      "0.027863336769245968\n",
+      "0.02626372137143445\n",
       "0.024784571211718686\n",
-      "0.023439112890373825\n",
+      "0.02343911289037383\n",
       "0.022179969985074023\n",
       "0.021160217575979016\n",
       "0.02022989923756486\n",
       "0.019367058349974004\n",
       "0.018567845127124925\n",
       "0.017848969406583762\n",
-      "0.017187661496342346\n",
+      "0.01718766149634235\n",
       "0.01657368277679982\n",
       "0.01599817637581068\n",
-      "0.015457831585642885\n",
+      "0.01545783158564288\n",
       "0.014948025199071284\n",
-      "0.014461622847641747\n",
-      "0.014000352105777525\n",
+      "0.014461622847641745\n",
+      "0.014000352105777526\n",
       "0.013558082766414237\n",
       "0.01311751745327867\n",
       "0.012612455200814074\n",
       "0.012162631078637215\n",
       "0.011740243666151801\n",
-      "0.011309865240838799\n",
-      "0.010899203476967509\n",
+      "0.011309865240838797\n",
+      "0.01089920347696751\n",
       "0.010521650813570736\n",
-      "0.010196561107985157\n",
-      "0.009891130130343635\n",
-      "0.009600816910980125\n",
+      "0.010196561107985154\n",
+      "0.009891130130343637\n",
+      "0.009600816910980126\n",
       "0.009315760503390665\n",
       "0.009039516487389243\n",
       "0.008767196010894804\n",
@@ -845,60 +843,60 @@
       "0.008248633969299386\n",
       "0.00799543225258579\n",
       "0.0077453645420522965\n",
-      "0.00749730174447974\n",
+      "0.007497301744479739\n",
       "0.007257772289115741\n",
       "0.007027261536318484\n",
       "0.0068053907333040725\n",
-      "0.006592009351901685\n",
-      "0.006385407763355719\n",
+      "0.006592009351901684\n",
+      "0.006385407763355721\n",
       "0.006188817973227064\n",
-      "0.005999093025599275\n",
+      "0.005999093025599274\n",
       "0.005811112139699502\n",
-      "0.005627719817663616\n",
+      "0.0056277198176636145\n",
       "0.005450013698900426\n",
-      "0.005276483161505481\n",
-      "0.005107718474447619\n",
-      "0.004944021699863354\n",
-      "0.004784881046850776\n",
+      "0.005276483161505479\n",
+      "0.00510771847444762\n",
+      "0.004944021699863355\n",
+      "0.004784881046850777\n",
       "0.004627403945911204\n",
       "0.004472631444322943\n",
       "0.00432708986678605\n",
       "0.004181755611710442\n",
-      "0.004046007042062048\n",
-      "0.003910543564363515\n",
+      "0.004046007042062047\n",
+      "0.003910543564363516\n",
       "0.003776928835572415\n",
-      "0.003651748774315907\n",
+      "0.0036517487743159076\n",
       "0.0035254355563773946\n",
-      "0.0034056166274056584\n",
-      "0.0032874208444440187\n",
+      "0.003405616627405658\n",
+      "0.00328742084444402\n",
       "0.0031766063482686433\n",
-      "0.0030732377341834986\n",
+      "0.0030732377341834995\n",
       "0.0029663118260594124\n",
       "0.0028654978328103895\n",
-      "0.0027705034302599975\n",
+      "0.002770503430259998\n",
       "0.0026746611593414884\n",
-      "0.002581828293476095\n",
-      "0.0024926027552577873\n",
+      "0.0025818282934760954\n",
+      "0.0024926027552577877\n",
       "0.0024041100509028675\n",
       "0.0023211537031277334\n",
-      "0.0022394923684441784\n",
-      "0.0021573903066322564\n",
+      "0.002239492368444179\n",
+      "0.002157390306632257\n",
       "0.0020814704811892046\n",
       "0.0020060111617612683\n",
       "0.0019369915179307477\n",
-      "0.0018649415726044625\n",
-      "0.0018017916860055036\n",
+      "0.001864941572604462\n",
+      "0.0018017916860055033\n",
       "0.0017384859107382948\n",
-      "0.0016788831232328189\n",
+      "0.001678883123232819\n",
       "0.0016249236832228252\n",
       "0.0015726446858505016\n",
-      "0.0015220808485447667\n",
+      "0.0015220808485447664\n",
       "0.0014738722656119662\n",
-      "0.0014252690409045166\n",
+      "0.0014252690409045168\n",
       "0.0013813925110105407\n",
       "0.0013371158259658372\n",
       "0.0012945639055305799\n",
-      "0.001252099575874205\n",
+      "0.0012520995758742053\n",
       "0.0012112875077578335\n",
       "0.0011739707910407246\n",
       "0.0011380005543078454\n",
@@ -908,17 +906,17 @@
       "0.0009972384165631203\n",
       "0.000966312922775045\n",
       "0.0009342770999523545\n",
-      "0.0009045181337248312\n",
+      "0.0009045181337248313\n",
       "0.0008768690771979427\n",
       "0.0008500828244220042\n",
-      "0.0008254308975864035\n",
-      "0.0008010413062407819\n",
+      "0.0008254308975864031\n",
+      "0.0008010413062407818\n",
       "0.0007760553272720413\n",
       "0.000753211383801746\n",
-      "0.000729756847116741\n",
-      "0.0007076614714839721\n",
-      "0.0006874866020348999\n",
-      "0.0006662312584093007\n",
+      "0.0007297568471167409\n",
+      "0.0007076614714839723\n",
+      "0.0006874866020348998\n",
+      "0.0006662312584093008\n",
       "0.0006451482244339356\n",
       "0.000627055849899595\n",
       "0.0006092029421030994\n",
@@ -926,30 +924,30 @@
       "0.0005717931942866128\n",
       "0.0005549292888368047\n",
       "0.0005380478371784952\n",
-      "0.0005201146650359145\n",
+      "0.0005201146650359143\n",
       "0.0005054419685110467\n",
       "0.0004924934986366237\n",
-      "0.00047739876792524756\n",
+      "0.00047739876792524745\n",
       "0.00046469263404502135\n",
-      "0.0004536443655474988\n",
+      "0.00045364436554749875\n",
       "0.0004412859633354016\n",
       "0.00042848630259308915\n",
-      "0.00041766763881578004\n",
+      "0.00041766763881578\n",
       "0.0004072797829573886\n",
-      "0.0003959767465342979\n",
-      "0.00038491058157508494\n",
-      "0.00037565536484099885\n",
-      "0.00036738715164529696\n",
+      "0.00039597674653429795\n",
+      "0.0003849105815750849\n",
+      "0.0003756553648409989\n",
+      "0.000367387151645297\n",
       "0.0003576693564867682\n",
-      "0.0003478250341790231\n",
+      "0.0003478250341790232\n",
       "0.0003392932258692299\n",
       "0.00033114287304467506\n",
-      "0.0003221956622535673\n",
+      "0.00032219566225356736\n",
       "0.00031399916957712304\n",
       "0.0003071797291965539\n",
       "0.0003006855530398379\n",
       "0.00029443366939331994\n",
-      "0.0002868720363774873\n",
+      "0.00028687203637748737\n",
       "0.0002807450793450514\n",
       "0.0002750292160159778\n",
       "0.0002696259382267158\n",
@@ -957,55 +955,55 @@
       "0.00025747523460078786\n",
       "0.00025246120875020776\n",
       "0.00024737284238620174\n",
-      "0.00024211694965375674\n",
-      "0.00023645391595279223\n",
+      "0.00024211694965375677\n",
+      "0.0002364539159527922\n",
       "0.00023244279139616122\n",
       "0.00022849429549262563\n",
-      "0.000224154946090284\n",
-      "0.00021992880522408423\n",
-      "0.00021512615793204347\n",
+      "0.00022415494609028398\n",
+      "0.00021992880522408425\n",
+      "0.0002151261579320435\n",
       "0.00021112664175118356\n",
       "0.000208052177442707\n",
       "0.00020474338345446234\n",
       "0.00020072304144019672\n",
       "0.00019775908628190988\n",
-      "0.00019465371448508102\n",
+      "0.000194653714485081\n",
       "0.00019172507890867358\n",
       "0.00018905619608860144\n",
       "0.00018583485063236214\n",
-      "0.00018278286577062247\n",
+      "0.00018278286577062242\n",
       "0.0001801076905540537\n",
-      "0.00017738310919106166\n",
-      "0.00017473395371285803\n",
+      "0.0001773831091910617\n",
+      "0.00017473395371285798\n",
       "0.0001727125726534076\n",
       "0.00017047710685367778\n",
-      "0.00016821089468757003\n",
-      "0.0001659042372877211\n",
+      "0.00016821089468757006\n",
+      "0.00016590423728772116\n",
       "0.00016407927565906488\n",
       "0.00016247074847690454\n",
-      "0.0001601752052319858\n",
-      "0.00015844276355980668\n",
-      "0.0001567643486716562\n",
+      "0.00016017520523198582\n",
+      "0.00015844276355980665\n",
+      "0.00015676434867165618\n",
       "0.0001548519720160484\n",
       "0.0001539444764862004\n",
-      "0.00015242706419291767\n",
+      "0.0001524270641929177\n",
       "0.00015028598879511014\n",
       "0.00014889764349392524\n",
       "0.00014737156388394115\n",
-      "0.00014625559851989025\n",
+      "0.00014625559851989023\n",
       "0.0001448521264190814\n",
       "0.00014381301362204171\n",
-      "0.0001419614668236029\n",
+      "0.00014196146682360288\n",
       "0.0001408659763373339\n",
       "0.0001396365178579487\n",
-      "0.0001383314515723009\n",
-      "0.00013735419852379168\n",
+      "0.00013833145157230093\n",
+      "0.00013735419852379166\n",
       "0.00013624883128071137\n",
       "0.0001348271413493323\n",
       "0.0001340181343812743\n",
       "0.00013327402210206197\n",
       "0.00013232118285356898\n",
-      "Wrote resulting mesh to Data/901-diffeo-registered.vtk\n"
+      "Wrote resulting mesh to Output/901-diffeo-registered.obj\n"
      ]
     }
    ],
@@ -1015,38 +1013,28 @@
     "\n",
     "registrar.filter.AddObserver(itk.IterationEvent(),update_diff_progress)\n",
     "\n",
-    "diffeo_mesh_result = registrar.register(fixed_mesh,moving_mesh,filepath=DIFFEO_OUTPUT_FILE,verbose=True)"
+    "(transform, mesh_result) = registrar.register(template_mesh,\n",
+    "                                        target_mesh,\n",
+    "                                        filepath=DIFFEO_OUTPUT_FILE,\n",
+    "                                        verbose=True)"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Compare translated image to target image."
+    "Compare the translated image to the target image."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 29,
+   "execution_count": null,
    "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "048f1a7e14914b508a81f2ba91ebd808",
-       "version_major": 2,
-       "version_minor": 0
-      },
-      "text/plain": [
-       "Viewer(geometries=[{'vtkClass': 'vtkPolyData', 'points': {'vtkClass': 'vtkPoints', 'numberOfComponents': 3, 'd…"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    }
-   ],
+   "outputs": [],
    "source": [
-    "view(geometries=[diffeo_mesh_result,moving_mesh])"
+    "#diffeo_image_result = registrar.mesh_to_image(diffeo_mesh_result)\n",
+    "#checkerboard(diffeo_image_result,moving_img,pattern=PATTERN_COUNT)\n",
+    "view(geometries=[mesh_result, target_mesh])"
    ]
   },
   {
@@ -1058,20 +1046,51 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 30,
+   "execution_count": null,
    "metadata": {
     "scrolled": false
    },
+   "outputs": [],
+   "source": [
+    "file_mesh_result = itk.meshread(DIFFEO_OUTPUT_FILE, itk.F)\n",
+    "#file_image_result = registrar.mesh_to_image(file_mesh_result)\n",
+    "#checkerboard(diffeo_image_result,file_image_result,pattern=PATTERN_COUNT)\n",
+    "view(geometries=[file_mesh_result,mesh_result])"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Entropy-based Registration\n",
+    "\n",
+    "The `PointSetEntropyRegistrar` class registers two 3D meshes by converting them to point clouds and computing the transform which minimizes entropy measures between the two clouds. In this example we substitute a [`EuclideanDistancePointSetToPointSetMetric`](https://itk.org/Doxygen/html/classitk_1_1EuclideanDistancePointSetToPointSetMetricv4.html) to compare the two clouds."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 21,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from src.hasi.hasi.pointsetentropyregistrar import PointSetEntropyRegistrar\n",
+    "registrar = PointSetEntropyRegistrar()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 22,
+   "metadata": {},
    "outputs": [
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "a6b99e27bc9e4b4d90333cb3f9fe5f99",
+       "model_id": "d79eeb19df1b4e1ba79f20932c4d5fb1",
        "version_major": 2,
        "version_minor": 0
       },
       "text/plain": [
-       "Viewer(geometries=[{'vtkClass': 'vtkPolyData', 'points': {'vtkClass': 'vtkPoints', 'numberOfComponents': 3, 'd…"
+       "HBox(children=(Label(value='Register images'), FloatProgress(value=0.0, max=2000.0)))"
       ]
      },
      "metadata": {},
@@ -1079,33 +1098,541 @@
     }
    ],
    "source": [
-    "file_mesh_result = itk.meshread(DIFFEO_OUTPUT_FILE, itk.F)\n",
-    "view(geometries=[file_mesh_result,diffeo_mesh_result])"
+    "progress = FloatProgress(\n",
+    "        min=0.0,\n",
+    "        max=2000.0,\n",
+    "        step=1\n",
+    "    )\n",
+    "progressBox = HBox([\n",
+    "    Label('Register images'),\n",
+    "    progress\n",
+    "])\n",
+    "progressBox"
    ]
   },
   {
-   "cell_type": "markdown",
+   "cell_type": "code",
+   "execution_count": 23,
    "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "0"
+      ]
+     },
+     "execution_count": 23,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
    "source": [
-    "### Clean up example files"
+    "def update_progress():\n",
+    "    progressBox.value = registrar.optimizer.GetCurrentIteration()\n",
+    "\n",
+    "registrar.optimizer.AddObserver(itk.IterationEvent(),update_progress)"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 31,
+   "execution_count": 25,
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "0 0.11653694013677242\n",
+      "0 0.11653694013677242\n",
+      "0 0.11653694013677242\n",
+      "0 0.11653694013677242\n",
+      "0 0.11653694013677242\n",
+      "0 0.14299087349649367\n",
+      "0 0.14299087349649367\n",
+      "1 0.1348841081511713\n",
+      "1 0.1348841081511713\n",
+      "2 0.12898271315311685\n",
+      "2 0.12898271315311685\n",
+      "3 0.1246315007837364\n",
+      "3 0.1246315007837364\n",
+      "4 0.1212819745835345\n",
+      "4 0.1212819745835345\n",
+      "5 0.1188246510044722\n",
+      "5 0.1188246510044722\n",
+      "6 0.1167187295839064\n",
+      "6 0.1167187295839064\n",
+      "7 0.11487317279098527\n",
+      "7 0.11487317279098527\n",
+      "8 0.11329302557318019\n",
+      "8 0.11329302557318019\n",
+      "9 0.11189197105622001\n",
+      "9 0.11189197105622001\n",
+      "10 0.11063591302518083\n",
+      "10 0.11063591302518083\n",
+      "11 0.10948059642565089\n",
+      "11 0.10948059642565089\n",
+      "12 0.10847938077972978\n",
+      "12 0.10847938077972978\n",
+      "13 0.10758298309564214\n",
+      "13 0.10758298309564214\n",
+      "14 0.10676379456645364\n",
+      "14 0.10676379456645364\n",
+      "15 0.10598345913678081\n",
+      "15 0.10598345913678081\n",
+      "16 0.10526402003158299\n",
+      "16 0.10526402003158299\n",
+      "17 0.10457306585168157\n",
+      "17 0.10457306585168157\n",
+      "18 0.10387656202239602\n",
+      "18 0.10387656202239602\n",
+      "19 0.10321961004986056\n",
+      "19 0.10321961004986056\n",
+      "20 0.10260461948624788\n",
+      "20 0.10260461948624788\n",
+      "21 0.10200627180917485\n",
+      "21 0.10200627180917485\n",
+      "22 0.10140152320105686\n",
+      "22 0.10140152320105686\n",
+      "23 0.10085563915665782\n",
+      "23 0.10085563915665782\n",
+      "24 0.10034827708295513\n",
+      "24 0.10034827708295513\n",
+      "25 0.09987114566974371\n",
+      "25 0.09987114566974371\n",
+      "26 0.09939470307629758\n",
+      "26 0.09939470307629758\n",
+      "27 0.0989324930617468\n",
+      "27 0.0989324930617468\n",
+      "28 0.09843966219514229\n",
+      "28 0.09843966219514229\n",
+      "29 0.09793908007742391\n",
+      "29 0.09793908007742391\n",
+      "30 0.09746950137220102\n",
+      "30 0.09746950137220102\n",
+      "31 0.09699893197711784\n",
+      "31 0.09699893197711784\n",
+      "32 0.0965052677411332\n",
+      "32 0.0965052677411332\n",
+      "33 0.0959963956203316\n",
+      "33 0.0959963956203316\n",
+      "34 0.09552171558222458\n",
+      "34 0.09552171558222458\n",
+      "35 0.0950693391134096\n",
+      "35 0.0950693391134096\n",
+      "36 0.09462671665609802\n",
+      "36 0.09462671665609802\n",
+      "37 0.09419694917774411\n",
+      "37 0.09419694917774411\n",
+      "38 0.09377644294501333\n",
+      "38 0.09377644294501333\n",
+      "39 0.0933548624454608\n",
+      "39 0.0933548624454608\n",
+      "40 0.09295615861185354\n",
+      "40 0.09295615861185354\n",
+      "41 0.09254462214410952\n",
+      "41 0.09254462214410952\n",
+      "42 0.09215495182325018\n",
+      "42 0.09215495182325018\n",
+      "43 0.09177679344625343\n",
+      "43 0.09177679344625343\n",
+      "44 0.09139691968346694\n",
+      "44 0.09139691968346694\n",
+      "45 0.09101364574007395\n",
+      "45 0.09101364574007395\n",
+      "46 0.09065170965464969\n",
+      "46 0.09065170965464969\n",
+      "47 0.09029439105857602\n",
+      "47 0.09029439105857602\n",
+      "48 0.08993620528677936\n",
+      "48 0.08993620528677936\n",
+      "49 0.089576654474697\n",
+      "49 0.089576654474697\n",
+      "50 0.0892357186062858\n",
+      "50 0.0892357186062858\n",
+      "51 0.08890318634427212\n",
+      "51 0.08890318634427212\n",
+      "52 0.08857484591934735\n",
+      "52 0.08857484591934735\n",
+      "53 0.08824765070697012\n",
+      "53 0.08824765070697012\n",
+      "54 0.0879365720705108\n",
+      "54 0.0879365720705108\n",
+      "55 0.0876374062156336\n",
+      "55 0.0876374062156336\n",
+      "56 0.08735467919687344\n",
+      "56 0.08735467919687344\n",
+      "57 0.08709243613886834\n",
+      "57 0.08709243613886834\n",
+      "58 0.08682144369574353\n",
+      "58 0.08682144369574353\n",
+      "59 0.08654414202379235\n",
+      "59 0.08654414202379235\n",
+      "60 0.08626254207447752\n",
+      "60 0.08626254207447752\n",
+      "61 0.08599400764805978\n",
+      "61 0.08599400764805978\n",
+      "62 0.08571369478160706\n",
+      "62 0.08571369478160706\n",
+      "63 0.08544927946273669\n",
+      "63 0.08544927946273669\n",
+      "64 0.0852015329571939\n",
+      "64 0.0852015329571939\n",
+      "65 0.08497298525422586\n",
+      "65 0.08497298525422586\n",
+      "66 0.08474202240764461\n",
+      "66 0.08474202240764461\n",
+      "67 0.08450828156005492\n",
+      "67 0.08450828156005492\n",
+      "68 0.0842914506966503\n",
+      "68 0.0842914506966503\n",
+      "69 0.08406680886737634\n",
+      "69 0.08406680886737634\n",
+      "70 0.08385487057325931\n",
+      "70 0.08385487057325931\n",
+      "71 0.08365411612858605\n",
+      "71 0.08365411612858605\n",
+      "72 0.08346651436065196\n",
+      "72 0.08346651436065196\n",
+      "73 0.0832785764861526\n",
+      "73 0.0832785764861526\n",
+      "74 0.0830956389628318\n",
+      "74 0.0830956389628318\n",
+      "75 0.08291439083955028\n",
+      "75 0.08291439083955028\n",
+      "76 0.08274437674883718\n",
+      "76 0.08274437674883718\n",
+      "77 0.0825703839081505\n",
+      "77 0.0825703839081505\n",
+      "78 0.0824032020547137\n",
+      "78 0.0824032020547137\n",
+      "79 0.08224174585332442\n",
+      "79 0.08224174585332442\n",
+      "80 0.0821049199029264\n",
+      "80 0.0821049199029264\n",
+      "81 0.08197881458078403\n",
+      "81 0.08197881458078403\n",
+      "82 0.08185837977285738\n",
+      "82 0.08185837977285738\n",
+      "83 0.08173939492955193\n",
+      "83 0.08173939492955193\n",
+      "84 0.08162607917415969\n",
+      "84 0.08162607917415969\n",
+      "85 0.08149151010532397\n",
+      "85 0.08149151010532397\n",
+      "86 0.08137243122263513\n",
+      "86 0.08137243122263513\n",
+      "87 0.0812571145125053\n",
+      "87 0.0812571145125053\n",
+      "88 0.08114100389719058\n",
+      "88 0.08114100389719058\n",
+      "89 0.0810355806556446\n",
+      "89 0.0810355806556446\n",
+      "90 0.08093689958353018\n",
+      "90 0.08093689958353018\n",
+      "91 0.08084432340724178\n",
+      "91 0.08084432340724178\n",
+      "92 0.08075432246017161\n",
+      "92 0.08075432246017161\n",
+      "93 0.08066661226855704\n",
+      "93 0.08066661226855704\n",
+      "94 0.08057127187243747\n",
+      "94 0.08057127187243747\n",
+      "95 0.0804907122823307\n",
+      "95 0.0804907122823307\n",
+      "96 0.08040891193177041\n",
+      "96 0.08040891193177041\n",
+      "97 0.0803347754174154\n",
+      "97 0.0803347754174154\n",
+      "98 0.08026206771962685\n",
+      "98 0.08026206771962685\n",
+      "99 0.08019365011624813\n",
+      "99 0.08019365011624813\n",
+      "100 0.08012285675776327\n",
+      "100 0.08012285675776327\n",
+      "101 0.08005565092136738\n",
+      "101 0.08005565092136738\n",
+      "102 0.07998782447569118\n",
+      "102 0.07998782447569118\n",
+      "103 0.07992587283346397\n",
+      "103 0.07992587283346397\n",
+      "104 0.07986854946334795\n",
+      "104 0.07986854946334795\n",
+      "105 0.0798146522457944\n",
+      "105 0.0798146522457944\n",
+      "106 0.07975990131395232\n",
+      "106 0.07975990131395232\n",
+      "107 0.07970880718246323\n",
+      "107 0.07970880718246323\n",
+      "108 0.07965760682664694\n",
+      "108 0.07965760682664694\n",
+      "109 0.07960994446773094\n",
+      "109 0.07960994446773094\n",
+      "110 0.07956609873567748\n",
+      "110 0.07956609873567748\n",
+      "111 0.07952829162689509\n",
+      "111 0.07952829162689509\n",
+      "112 0.07948904691038888\n",
+      "112 0.07948904691038888\n",
+      "113 0.07944754980615033\n",
+      "113 0.07944754980615033\n",
+      "114 0.07940782518972385\n",
+      "114 0.07940782518972385\n",
+      "115 0.07936701011111992\n",
+      "115 0.07936701011111992\n",
+      "116 0.07932161389681383\n",
+      "116 0.07932161389681383\n",
+      "117 0.0792766135193253\n",
+      "117 0.0792766135193253\n",
+      "118 0.0792310499763896\n",
+      "118 0.0792310499763896\n",
+      "119 0.07918177483914192\n",
+      "119 0.07918177483914192\n",
+      "120 0.079136206453552\n",
+      "120 0.079136206453552\n",
+      "121 0.07909377774757867\n",
+      "121 0.07909377774757867\n",
+      "122 0.07905321741476633\n",
+      "122 0.07905321741476633\n",
+      "123 0.07901297327911636\n",
+      "123 0.07901297327911636\n",
+      "124 0.07897263287479192\n",
+      "124 0.07897263287479192\n",
+      "125 0.07893020894939834\n",
+      "125 0.07893020894939834\n",
+      "126 0.07888937653560078\n",
+      "126 0.07888937653560078\n",
+      "127 0.07885078010664662\n",
+      "127 0.07885078010664662\n",
+      "128 0.07881429351011222\n",
+      "128 0.07881429351011222\n",
+      "129 0.07878122910715848\n",
+      "129 0.07878122910715848\n",
+      "130 0.07874749852498979\n",
+      "130 0.07874749852498979\n",
+      "131 0.07871370393036527\n",
+      "131 0.07871370393036527\n",
+      "132 0.07867831529276731\n",
+      "132 0.07867831529276731\n",
+      "133 0.07864333851910414\n",
+      "133 0.07864333851910414\n",
+      "134 0.07860784351176148\n",
+      "134 0.07860784351176148\n",
+      "135 0.07857242616774444\n",
+      "135 0.07857242616774444\n",
+      "136 0.07853529670524428\n",
+      "136 0.07853529670524428\n",
+      "137 0.0784991816064703\n",
+      "137 0.0784991816064703\n",
+      "138 0.07846271769870426\n",
+      "138 0.07846271769870426\n",
+      "139 0.07842739363385651\n",
+      "139 0.07842739363385651\n",
+      "140 0.07839649056983462\n",
+      "140 0.07839649056983462\n",
+      "141 0.07836341037669622\n",
+      "141 0.07836341037669622\n",
+      "142 0.07833203858657982\n",
+      "142 0.07833203858657982\n",
+      "143 0.07830316778136227\n",
+      "143 0.07830316778136227\n",
+      "144 0.0782691390416011\n",
+      "144 0.0782691390416011\n",
+      "145 0.07823290835424561\n",
+      "145 0.07823290835424561\n",
+      "146 0.07819688597804653\n",
+      "146 0.07819688597804653\n",
+      "147 0.07816065321347515\n",
+      "147 0.07816065321347515\n",
+      "148 0.07812730628399299\n",
+      "148 0.07812730628399299\n",
+      "149 0.07809864237317031\n",
+      "149 0.07809864237317031\n",
+      "150 0.07806935155422634\n",
+      "150 0.07806935155422634\n",
+      "151 0.07804108279695075\n",
+      "151 0.07804108279695075\n",
+      "152 0.07801440057306076\n",
+      "152 0.07801440057306076\n",
+      "153 0.07798590731404664\n",
+      "153 0.07798590731404664\n",
+      "154 0.07795941748948845\n",
+      "154 0.07795941748948845\n",
+      "155 0.07793304498122117\n",
+      "155 0.07793304498122117\n",
+      "156 0.07790729739927842\n",
+      "156 0.07790729739927842\n",
+      "157 0.07788171634650963\n",
+      "157 0.07788171634650963\n",
+      "158 0.07786002108230078\n",
+      "158 0.07786002108230078\n",
+      "159 0.0778418289576281\n",
+      "159 0.0778418289576281\n",
+      "160 0.07782041792321259\n",
+      "160 0.07782041792321259\n",
+      "161 0.07780186568706805\n",
+      "161 0.07780186568706805\n",
+      "162 0.07777993581285195\n",
+      "162 0.07777993581285195\n",
+      "163 0.07775818825350836\n",
+      "163 0.07775818825350836\n",
+      "164 0.07773874114907033\n",
+      "164 0.07773874114907033\n",
+      "165 0.07772034802581972\n",
+      "165 0.07772034802581972\n",
+      "166 0.07769733659356273\n",
+      "166 0.07769733659356273\n",
+      "167 0.07767202799110828\n",
+      "167 0.07767202799110828\n",
+      "168 0.0776486543720145\n",
+      "168 0.0776486543720145\n",
+      "169 0.07762443322758518\n",
+      "169 0.07762443322758518\n",
+      "170 0.07760132034966206\n",
+      "170 0.07760132034966206\n",
+      "171 0.0775786644820982\n",
+      "171 0.0775786644820982\n",
+      "172 0.07755359858359552\n",
+      "172 0.07755359858359552\n",
+      "173 0.07752408320911462\n",
+      "173 0.07752408320911462\n",
+      "174 0.07749639760064031\n",
+      "174 0.07749639760064031\n",
+      "175 0.0774711253569307\n",
+      "175 0.0774711253569307\n",
+      "176 0.07744313746151987\n",
+      "176 0.07744313746151987\n",
+      "177 0.07742068272469127\n",
+      "177 0.07742068272469127\n",
+      "178 0.07739916751579805\n",
+      "178 0.07739916751579805\n",
+      "179 0.07738054812004662\n",
+      "179 0.07738054812004662\n",
+      "180 0.07736386192511777\n",
+      "180 0.07736386192511777\n",
+      "181 0.07734787821703756\n",
+      "181 0.07734787821703756\n",
+      "182 0.07733333593020697\n",
+      "182 0.07733333593020697\n",
+      "183 0.0773199095020824\n",
+      "183 0.0773199095020824\n",
+      "184 0.07730767136007201\n",
+      "184 0.07730767136007201\n",
+      "185 0.07729162664437002\n",
+      "185 0.07729162664437002\n",
+      "186 0.07727752588289806\n",
+      "186 0.07727752588289806\n",
+      "187 0.07726456154265426\n",
+      "187 0.07726456154265426\n",
+      "188 0.07725047090491934\n",
+      "188 0.07725047090491934\n",
+      "189 0.07723627452026564\n",
+      "189 0.07723627452026564\n",
+      "190 0.07722303995500378\n",
+      "190 0.07722303995500378\n",
+      "191 0.0772089717092574\n",
+      "191 0.0772089717092574\n",
+      "192 0.07718700761430665\n",
+      "192 0.07718700761430665\n",
+      "193 0.07717203940004785\n",
+      "193 0.07717203940004785\n",
+      "194 0.0771581849284021\n",
+      "194 0.0771581849284021\n",
+      "195 0.07714476923964943\n",
+      "195 0.07714476923964943\n",
+      "196 0.07713411831461757\n",
+      "196 0.07713411831461757\n",
+      "197 0.07712559659868713\n",
+      "197 0.07712559659868713\n",
+      "198 0.07711663140860699\n",
+      "198 0.07711663140860699\n",
+      "199 0.07710719114870243\n",
+      "199 0.07710719114870243\n",
+      "200 0.07710719114870243\n",
+      "200 0.07710719114870243\n",
+      "Number of iterations run: 200\n",
+      "Final metric value: 0.07710719114870243\n",
+      "Final transform position: [1.0510722916013444, 0.056150613167790735, 0.0954078508297607, -0.06017527215791907, 0.9639973982287809, -0.2441693793300643, 0.013387196579158319, 0.24634951322976123, 0.9032286665510005, 0.05277936189701296, 0.05044218686835098, -0.15347480191550858]\n",
+      "Optimizer scales: [25.05989319751523, 4.9605442354869025, 2.257081946547168, 25.059893197514786, 4.960544235487002, 2.257081946547168, 25.059893197514842, 4.9605442354869025, 2.257081946547168, 1.0000000000000018, 1.0000000000000018, 1.0000000000000018]\n",
+      "Optimizer learning rate: 1.0\n"
+     ]
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Wrote resulting mesh to Output/901-pointset-registered.obj\n"
+     ]
+    }
+   ],
+   "source": [
+    "metric = itk.EuclideanDistancePointSetToPointSetMetricv4[itk.PointSet[itk.F,3]].New()\n",
+    "\n",
+    "(transform, mesh_result) = registrar.register(template_mesh,\n",
+    "                       target_mesh,\n",
+    "                       metric=metric,\n",
+    "                       filepath=POINTSET_OUTPUT_FILE,\n",
+    "                       resample_rate=0.005,\n",
+    "                       verbose=True,\n",
+    "                       learning_rate=1.0,\n",
+    "                       max_iterations=200,\n",
+    "                       resample_from_target=False)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 26,
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "template_resampled = \\\n",
+    "    registrar.resample_template_from_target(mesh_result, target_mesh)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 27,
    "metadata": {},
    "outputs": [],
    "source": [
+    "itk.meshwrite(template_resampled,'Output/pointset-resampled.obj')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "view(geometries=[target_mesh,mesh_result,template_resampled])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "# Clean up file output\n",
     "os.remove(MEANSQUARES_OUTPUT_FILE)\n",
-    "os.remove(DIFFEO_OUTPUT_FILE)"
+    "os.remove(DIFFEO_OUTPUT_FILE)\n",
+    "os.remove(ELASTIX_OUTPUT_FILE)\n",
+    "os.remove(POINTSET_OUTPUT_FILE)"
    ]
   }
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 3",
+   "display_name": "venv-itk4",
    "language": "python",
-   "name": "python3"
+   "name": "venv-itk4"
   },
   "language_info": {
    "codemirror_mode": {
diff --git a/src/hasi/hasi/meansquaresregistrar.py b/src/hasi/hasi/meansquaresregistrar.py
index bb4c23d..9c6dae0 100644
--- a/src/hasi/hasi/meansquaresregistrar.py
+++ b/src/hasi/hasi/meansquaresregistrar.py
@@ -42,9 +42,6 @@ class MeanSquaresRegistrar(MeshToMeshRegistrar):
     SMOOTHING_SIGMAS_PER_LEVEL = [0] * NUMBER_OF_LEVELS
     SHRINK_FACTORS_PER_LEVEL = [1] * Dimension
 
-    # TODO investigate if this is useful
-    LinearInterpolatorType = \
-        itk.LinearInterpolateImageFunction[ImageType, itk.D]
     TransformType = itk.BSplineTransform[itk.D, Dimension, V_SPLINE_ORDER]
     
 
@@ -100,6 +97,7 @@ def print_iteration():
             infnorm = self.optimizer.GetInfinityNormOfProjectedGradient()
             print(f'{iteration} {metric} {infnorm}')
 
+        # FIXME adds a duplicate observer if multiple calls without re-initialization
         if(verbose):
             self.optimizer.AddObserver(itk.IterationEvent(),
                                             print_iteration)
@@ -107,6 +105,8 @@ def print_iteration():
         self.optimizer.SetNumberOfIterations(num_iterations)
 
         # Define object to handle image registration
+
+        # TODO use functional interface image_registration_method()
         RegistrationType = \
             itk.ImageRegistrationMethodv4[ImageType,ImageType]
         registration = RegistrationType.New(InitialTransform=transform,
@@ -126,16 +126,11 @@ def print_iteration():
         #     no observed impact on performance from this warning
         registration.Update()
 
-        # TODO functional interface
-        # image_registration_method()
-
         # Report results
         if(verbose):
             print('Solution = ' + str(list(transform.GetParameters())))
-
-        # Update fixed image
-        # TODO try inverting moving transform and compare results
-
+        
+        # Update template
         transformed_mesh = itk.transform_mesh_filter(template_mesh, transform=transform)
 
         # Write out
diff --git a/src/hasi/hasi/meshtomeshregistrar.py b/src/hasi/hasi/meshtomeshregistrar.py
index ba8ac39..c56355b 100644
--- a/src/hasi/hasi/meshtomeshregistrar.py
+++ b/src/hasi/hasi/meshtomeshregistrar.py
@@ -2,14 +2,14 @@
 
 # Filename: MeshToMeshRegistration
 # Contributors: Tom Birdsong
-# Purpose: Python implementation of Slicer SALT mesh-to-mesh registration Cxx
-# CLI.
-#    Ported from https://github.com/slicersalt/RegistrationBasedCorrespondence/
+# Purpose: Base Python class for implementations of mesh-to-mesh registration algorithms.
+#   Initially ported from https://github.com/slicersalt/RegistrationBasedCorrespondence/
 import logging
 import os
+import random
 
 import itk
-
+import numpy as np
 
 class MeshToMeshRegistrar:
     # Common definitions
@@ -26,6 +26,7 @@ class MeshToMeshRegistrar:
     FixedPointSetType = itk.PointSet[itk.F, Dimension]
 
     MeshType = itk.Mesh[itk.F, Dimension]
+    PointSetType = itk.PointSet[itk.F, Dimension]
 
     def __init__(self):
         self.initialize()
@@ -91,3 +92,56 @@ def mesh_to_image(self, meshes:list, reference_image:ImageType=None) -> ImageTyp
             images.append(distance.GetOutput())
 
         return images[0] if len(images) == 1 else images
+
+    # Randomly sample mesh to get a reduced point set
+    # This may not be suitable for low-density meshes
+    def randomly_sample_mesh_points(self, mesh:MeshType, sampling_rate:float=1.0) -> PointSetType:
+        if(sampling_rate > 1.0):
+            raise ValueError('Cannot resample with a rate greater than 1.0!')
+        
+        selected_points = set()
+        num_source_points = mesh.GetNumberOfPoints()
+        resampled_set = self.PointSetType.New()
+
+        while resampled_set.GetNumberOfPoints() < sampling_rate * num_source_points:
+            point_index = random.randrange(0, num_source_points)
+            while(point_index in selected_points):
+                point_index = random.randrange(0, num_source_points)
+            selected_points.add(point_index)
+
+            resampled_set.SetPoint(
+                resampled_set.GetNumberOfPoints(),
+                mesh.GetPoint(point_index))
+
+        return resampled_set
+    
+    @staticmethod
+    def resample_template_from_target(template_mesh:MeshType, target_mesh:MeshType) -> MeshType:
+        # Set samples from target mesh points
+        measurement = itk.Vector[itk.F,3]()
+        sample = itk.ListSample[type(measurement)].New()
+
+        # Use available wrapping for itk.VectorContainer
+        if (itk.ULL, itk.ULL) in itk.VectorContainer.keys():
+            neighbors = itk.VectorContainer[itk.ULL, itk.ULL].New()
+        else:
+            neighbors = itk.VectorContainer[itk.UL, itk.UL].New()
+
+        sample.Resize(target_mesh.GetNumberOfPoints())
+        for i in range(0, target_mesh.GetNumberOfPoints()):
+            measurement.SetVnlVector(target_mesh.GetPoint(i).GetVnlVector())
+            sample.SetMeasurementVector(i, measurement)
+
+        # Build kd-tree
+        tree_generator = itk.KdTreeGenerator[type(sample)].New(Sample=sample, BucketSize=16)
+        tree_generator.Update()
+
+        # Iteratively update template points to nearest neighbor in target point set
+        tree = tree_generator.GetOutput()
+
+        for i in range(0, template_mesh.GetNumberOfPoints()):
+            measurement.SetVnlVector(template_mesh.GetPoint(i).GetVnlVector())
+            tree.Search(measurement, 1, neighbors)
+            template_mesh.SetPoint(i, target_mesh.GetPoint(neighbors.GetElement(0)))
+
+        return template_mesh
diff --git a/src/hasi/hasi/pointsetentropyregistrar.py b/src/hasi/hasi/pointsetentropyregistrar.py
new file mode 100644
index 0000000..e59049f
--- /dev/null
+++ b/src/hasi/hasi/pointsetentropyregistrar.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python3
+
+# Filename:     pointsetentropyregistrar.py
+# Contributors: Tom Birdsong
+# Purpose:  Python implementation of mesh-to-mesh registration
+#     using point set entropy-based registration
+# Referenced https://github.com/InsightSoftwareConsortium/ITKTrimmedPointSetRegistration/blob/master/examples/TrimmedPointSetRegistrationExample.cxx
+#     for development purposes
+
+import logging
+import os
+import pytest
+
+import itk
+from .meshtomeshregistrar import MeshToMeshRegistrar
+
+class PointSetEntropyRegistrar(MeshToMeshRegistrar):
+    # Common definitions
+    MAX_ITERATIONS = 200
+
+    Dimension = 3
+    PixelType = itk.F
+    ImageType = itk.Image[PixelType, Dimension]
+    TemplateImageType = ImageType
+    TargetImageType = ImageType
+
+    MeshType = itk.Mesh[itk.F, Dimension]
+
+    # Class definitions
+    PointSetType = itk.PointSet[itk.F,Dimension]
+    TransformType = itk.AffineTransform[itk.D, Dimension]
+
+    def __init__(self):
+        super(self.__class__,self).__init__()
+
+    def initialize(self):
+        # Optimizer is exposed so that calling scripts can reference with custom observers
+        self.optimizer = itk.GradientDescentOptimizerv4.New(
+            MaximumStepSizeInPhysicalUnits=3,
+            MinimumConvergenceValue=-1,
+            ConvergenceWindowSize=1,
+            DoEstimateLearningRateOnce=False,
+            DoEstimateLearningRateAtEachIteration=False,
+            ReturnBestParametersAndValue=True)
+
+    def register(self,
+                 template_mesh:MeshType,
+                 target_mesh:MeshType,
+                 filepath:str=None,
+                 verbose=False,
+                 metric=None,
+                 resample_rate=1.0,
+                 max_iterations=MAX_ITERATIONS,
+                 learning_rate=1.0,
+                 resample_from_target=False) -> (TransformType, MeshType):
+
+        # Convert meshes to point sets
+        template_point_set = self.PointSetType.New(Points=template_mesh.GetPoints())
+        target_point_set = self.PointSetType.New(Points=target_mesh.GetPoints())
+
+        # Optionally resample to improve performance
+        if(resample_rate != 1.0):
+            template_point_set = self.randomly_sample_mesh_points(mesh=template_mesh, sampling_rate=resample_rate)
+            target_point_set = self.randomly_sample_mesh_points(mesh=target_mesh, sampling_rate=resample_rate)
+
+        # Define registration components
+        transform = self.TransformType.New()
+        transform.SetIdentity()
+        
+        template_image = self.mesh_to_image(template_mesh)
+        # Set physical dimensions for transform
+        fixed_physical_dimensions = list()
+        fixed_origin = list(template_image.GetOrigin())
+
+        for i in range(0, self.Dimension):
+            fixed_physical_dimensions.append(template_image.GetSpacing()[i] * \
+                (template_image.GetLargestPossibleRegion().GetSize()[i] - 1))
+
+        if not metric:
+            metric = itk.JensenHavrdaCharvatTsallisPointSetToPointSetMetricv4[self.PointSetType].New(
+                FixedPointSet=template_point_set,
+                MovingPointSet=target_point_set,
+                MovingTransform=transform,
+                PointSetSigma=20.0,
+                KernelSigma=3.0,
+                UseAnisotropicCovariances=False,
+                CovarianceKNeighborhood=5,
+                EvaluationKNeighborhood=10,
+                Alpha=1.1)
+            #metric.SetVirtualDomainFromImage( template_image );
+
+        metric.SetFixedPointSet(template_point_set)
+        metric.SetMovingPointSet(target_point_set)
+        metric.SetMovingTransform(transform)
+
+        metric.Initialize()
+
+        # Define scales to guide gradient descent steps
+        ShiftScalesType = \
+            itk.RegistrationParameterScalesFromPhysicalShift[type(metric)]
+        shift_scale_estimator = ShiftScalesType.New(
+            Metric=metric,
+            VirtualDomainPointSet=metric.GetVirtualTransformedPointSet())
+
+        self.optimizer.SetMetric(metric)
+        self.optimizer.SetNumberOfIterations(max_iterations)
+        self.optimizer.SetScalesEstimator(shift_scale_estimator)
+        self.optimizer.SetLearningRate(learning_rate)
+
+        def print_iteration():
+            print(f'{self.optimizer.GetCurrentIteration()} {self.optimizer.GetCurrentMetricValue()}')
+
+        if verbose:
+            self.optimizer.AddObserver(itk.AnyEvent(), print_iteration)
+
+        self.optimizer.StartOptimization()
+
+        print(f'Number of iterations run: {self.optimizer.GetCurrentIteration()}')
+        print(f'Final metric value: {self.optimizer.GetCurrentMetricValue()}')
+        print(f'Final transform position: {list(self.optimizer.GetCurrentPosition())}')
+        print(f'Optimizer scales: {list(self.optimizer.GetScales())}')
+        print(f'Optimizer learning rate: {self.optimizer.GetLearningRate()}')
+
+        moving_transform = metric.GetMovingTransform()
+
+        transformed_template_mesh = itk.transform_mesh_filter(template_mesh, transform=moving_transform)
+
+        if(resample_from_target):
+            transformed_template_mesh = self.resample_template_from_target(transformed_template_mesh, target_mesh)
+
+        # Write out
+        if filepath is not None:
+            itk.meshwrite(transformed_template_mesh,filepath)
+            if(verbose):
+                print(f'Wrote resulting mesh to {filepath}')
+
+        return (transform, transformed_template_mesh)
diff --git a/src/hasi/pyproject.toml b/src/hasi/pyproject.toml
index 996a1db..373a217 100644
--- a/src/hasi/pyproject.toml
+++ b/src/hasi/pyproject.toml
@@ -8,7 +8,7 @@ author = "Kitware Medical"
 author-email = "matt.mccormick@kitware.com"
 home-page = "https://github.com/KitwareMedical/HASI"
 requires = [
-    "itk-hasi",
+    "itk-hasi"
 ]
 requires-python=">=3.7"
 classifiers = [


Test patch:
diff --git a/src/hasi/test_meshtomeshregistration.py b/src/hasi/test_meshtomeshregistration.py
index ebdd422..f234971 100644
--- a/src/hasi/test_meshtomeshregistration.py
+++ b/src/hasi/test_meshtomeshregistration.py
@@ -21,27 +21,28 @@
 
 MEANSQUARES_METRIC_MAXIMUM_THRESHOLD = 0.002
 DIFFEO_METRIC_MAXIMUM_THRESHOLD = 0.0002
+POINT_SET_METRIC_MAXIMUM_THRESHOLD = 0.0001
 MAX_ITERATIONS = 200
 
-FIXED_MESH_FILE = 'test/Input/901-L-mesh.vtk'
-MOVING_MESH_FILE = 'test/Input/901-R-mesh.vtk'
+TEMPLATE_MESH_FILE = 'test/Input/901-L-mesh.vtk'
+TARGET_MESH_FILE = 'test/Input/901-R-mesh.vtk'
 
 os.makedirs('test', exist_ok=True)
 os.makedirs('test/Input', exist_ok=True)
 os.makedirs('test/Output', exist_ok=True)
 
-if not os.path.exists(FIXED_MESH_FILE):
+if not os.path.exists(TEMPLATE_MESH_FILE):
     url = 'https://data.kitware.com/api/v1/file/5f9daaae50a41e3d1924dae1/download'
-    urlretrieve(url, FIXED_MESH_FILE)
+    urlretrieve(url, TEMPLATE_MESH_FILE)
     
-if not os.path.exists(MOVING_MESH_FILE):
-    url = 'https://data.kitware.com/api/v1/file/5f9daaae50a41e3d1924dae1/download'
-    urlretrieve(url, MOVING_MESH_FILE)
+if not os.path.exists(TARGET_MESH_FILE):
+    url = 'https://data.kitware.com/api/v1/file/5f9daaba50a41e3d1924dae9/download'
+    urlretrieve(url, TARGET_MESH_FILE)
 
 os.makedirs('test/Output', exist_ok=True)
 
-mesh1 = itk.meshread(FIXED_MESH_FILE)
-mesh2 = itk.meshread(MOVING_MESH_FILE)
+template_mesh = itk.meshread(TEMPLATE_MESH_FILE)
+target_mesh = itk.meshread(TARGET_MESH_FILE)
 
 # Test base class import and functions
 def test_mesh_to_image():
@@ -55,15 +56,13 @@ def test_mesh_to_image():
     registrar.initialize()
 
     # Mesh-to-image executes without error
-    img1 = registrar.mesh_to_image(mesh1)
+    img1 = registrar.mesh_to_image(template_mesh)
 
     # Image was generated
     assert(type(img1) == itk.Image[itk.F, 3])
 
     # Mesh-to-image can run with reference image
-    img2 = registrar.mesh_to_image(mesh2, img1)
-
-    # Image was generated
+    img2 = registrar.mesh_to_image(target_mesh, img1)
     assert(type(img2) == itk.Image[itk.F, 3])
 
     # Images occupy the same physical space
@@ -72,7 +71,7 @@ def test_mesh_to_image():
 
 # Test registration with mean squares image metric
 def test_meansquares_registration():
-    MESH_OUTPUT = 'test/Output/testMeanSquaresRegisterOutput.vtk'
+    MESH_OUTPUT_FILE = 'test/Output/testMeanSquaresRegisterOutput.vtk'
 
     # Class is imported
     from hasi.meansquaresregistrar import MeanSquaresRegistrar
@@ -81,10 +80,16 @@ def test_meansquares_registration():
     registrar = MeanSquaresRegistrar()
 
     # Registration executes
-    (transform, mesh3) = registrar.register(mesh1,mesh2,filepath=MESH_OUTPUT)
+    (transform, template_output_mesh) = registrar.register(template_mesh=template_mesh,
+                                            target_mesh=target_mesh,
+                                            filepath=MESH_OUTPUT_FILE)
 
     # Mesh was generated
-    assert(type(mesh3) == type(mesh1))
+    assert(type(template_output_mesh) == type(template_mesh))
+
+    # Mesh is a resampling of the original template
+    assert(template_output_mesh.GetNumberOfPoints() == \
+        template_mesh.GetNumberOfPoints())
 
     # Optimization converged
     assert(registrar.optimizer.GetCurrentMetricValue() <
@@ -97,20 +102,24 @@ def test_meansquares_registration():
     assert(type(transform) == registrar.TransformType)
 
     # Mesh was output
-    assert(os.path.isfile(MESH_OUTPUT))
+    assert(os.path.isfile(MESH_OUTPUT_FILE))
 
     # File output matches mesh
-    fileMesh3 = itk.meshread(MESH_OUTPUT)
-    assert(fileMesh3.GetNumberOfPoints() == mesh3.GetNumberOfPoints())
-    assert(all(mesh3.GetPoint(i) == fileMesh3.GetPoint(i)
-                for i in range(0,mesh3.GetNumberOfPoints())))
+    output_mesh = itk.meshread(MESH_OUTPUT_FILE)
+    assert(output_mesh.GetNumberOfPoints() == \
+        template_output_mesh.GetNumberOfPoints())
+    assert(all(output_mesh.GetPoint(i) == template_output_mesh.GetPoint(i)
+                for i in range(0,output_mesh.GetNumberOfPoints())))
+
+    # Resample template
+    template_resampled = registrar.resample_template_from_target(template_output_mesh, target_mesh)
 
     # Clean up
-    os.remove(MESH_OUTPUT)
+    os.remove(MESH_OUTPUT_FILE)
 
 # Test registration with diffeomorphic demons image registration
 def test_diffeo_registration():
-    MESH_OUTPUT = 'test/Output/testDiffeoRegisterOutput.vtk'
+    MESH_OUTPUT_FILE = 'test/Output/testDiffeoRegisterOutput.vtk'
 
     # Class is imported
     from hasi.diffeoregistrar import DiffeoRegistrar
@@ -119,16 +128,22 @@ def test_diffeo_registration():
     registrar = DiffeoRegistrar()
 
     # Registration executes without error
-    (transform, mesh4) = registrar.register(mesh1,mesh2,filepath=MESH_OUTPUT)
+    (transform, template_output_mesh) = registrar.register(template_mesh=template_mesh,
+                                                           target_mesh=target_mesh,
+                                                           filepath=MESH_OUTPUT_FILE)
     
     # Transform was output
     assert(type(transform) == registrar.TransformType)
 
     # Mesh was generated
-    assert(type(mesh4) == type(mesh1))
+    assert(type(template_output_mesh) == type(template_mesh))
+    
+    # Mesh is a resampling of the original template
+    assert(template_output_mesh.GetNumberOfPoints() == \
+        template_mesh.GetNumberOfPoints())
 
     # Mesh was output
-    assert(os.path.isfile(MESH_OUTPUT))
+    assert(os.path.isfile(MESH_OUTPUT_FILE))
 
     # Optimization converged
     assert(registrar.filter.GetMetric() <
@@ -138,4 +153,48 @@ def test_diffeo_registration():
     assert(registrar.filter.GetElapsedIterations() <= MAX_ITERATIONS)
 
     # Clean up
-    os.remove(MESH_OUTPUT)
+    os.remove(MESH_OUTPUT_FILE)
+
+def test_pointset_registration():
+    MESH_OUTPUT_FILE = 'test/Output/testPointSetRegistrarOutput.vtk'
+    RESAMPLED_OUTPUT_FILE = 'test/Output/testPointSetResampledOutput.vtk'
+
+    # Class is imported
+    from hasi.pointsetentropyregistrar import PointSetEntropyRegistrar
+
+    # Class is instantiable
+    registrar = PointSetEntropyRegistrar()
+
+    # Registration executes without error
+    (transform, template_output_mesh) = registrar.register(template_mesh=template_mesh,
+                               target_mesh=target_mesh,
+                               filepath=MESH_OUTPUT_FILE,
+                               resample_rate=0.001,
+                               resample_from_target=True)
+    
+    # Transform was output
+    assert(type(transform) == registrar.TransformType)
+
+    # Mesh was generated
+    assert(type(template_output_mesh) == type(template_mesh))
+
+    # Mesh is a resampling of the original template
+    assert(template_output_mesh.GetNumberOfPoints() == \
+        template_mesh.GetNumberOfPoints())
+
+    # Mesh was output
+    assert(os.path.isfile(MESH_OUTPUT_FILE))
+
+    # Optimization converged
+    assert(registrar.optimizer.GetCurrentMetricValue() <
+           POINT_SET_METRIC_MAXIMUM_THRESHOLD)
+
+    # Optimization did not exceed allowable iterations
+    assert(registrar.optimizer.GetCurrentIteration() <= registrar.MAX_ITERATIONS)
+
+    # Resample template
+    template_resampled = \
+        registrar.resample_template_from_target(template_output_mesh, target_mesh)
+
+    # Clean up
+    os.remove(MESH_OUTPUT_FILE)