diff --git a/.gitignore b/.gitignore index 177663bbed808ea88b9953a47203ce4c7c321397..324bc1f07b54caf3323d9c825bc15658219a8c17 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ MATLAB dist dist/ +build venv/ diff --git a/AmpScan/__init__.py b/AmpScan/__init__.py index cbea4bf6728ba67b7784059193377e3cc79279f7..c218b69e4f683e7e76f3ea5e41bcce3fce6504ae 100644 --- a/AmpScan/__init__.py +++ b/AmpScan/__init__.py @@ -6,6 +6,6 @@ Created on Thu Dec 15 13:50:41 2016 """ from .core import AmpObject -from .registration import * +from .registration import registration from .align import align from .ampVis import vtkRenWin, qtVtkWindow \ No newline at end of file diff --git a/AmpScan/ampVis.py b/AmpScan/ampVis.py index 5705cc8f5642f174a6fb7cc840bc8d6b9b66a2b9..3a13a99eb312c5622c4d1372c5402fd72726b6a6 100644 --- a/AmpScan/ampVis.py +++ b/AmpScan/ampVis.py @@ -9,6 +9,7 @@ import numpy as np import vtk from vtk.util import numpy_support from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor +vtk.vtkObject.GlobalWarningDisplayOff() class vtkRenWin(vtk.vtkRenderWindow): @@ -238,7 +239,7 @@ class vtkRenWin(vtk.vtkRenderWindow): 1, vtkRGB) vtkRGB.Squeeze() im = np.flipud(np.resize(np.array(vtkRGB), - [width, height, 3])) / 255.0 + [height, width, 3])) / 255.0 return im def getScreenshot(self, fname, mag=10): @@ -287,7 +288,8 @@ class visMixin(object): def genIm(self, size=[512, 512], views=[[0, -1, 0]], background=[1.0, 1.0, 1.0], projection=True, - shading=True, mag=10, out='im', fh='test.tiff'): + shading=True, mag=10, out='im', fh='test.tiff', + zoom=1.0, az = 0, crop=False): r""" Creates a temporary off screen vtkRenWin which is then either returned as a numpy array or saved as a .png file @@ -331,15 +333,24 @@ class visMixin(object): # Set camera projection win.setProjection(projection) win.SetSize(size[0], size[1]) + win.Modified() win.OffScreenRenderingOn() for i, view in enumerate(views): - win.addAxes([self.actor,], color=[0.0, 0.0, 0.0], viewport=i) +# win.addAxes([self.actor,], color=[0.0, 0.0, 0.0], viewport=i) win.setView(view, i) # win.setProjection(projection, viewport=i) - win.renderActors([self.actor,], viewport=i, zoom=1.3) + win.renderActors([self.actor,], zoom=zoom) + win.rens[0].GetActiveCamera().Azimuth(az) win.Render() if out == 'im': im = win.getImage() + if crop is True: + mask = np.all(im == 1, axis=2) + mask = ~np.all(mask, axis=1) + im = im[mask, :, :] + mask = np.all(im == 1, axis=2) + mask = ~np.all(mask, axis=0) + im = im[:, mask, :] return im elif out == 'fh': win.getScreenshot(fh) diff --git a/AmpScan/core.py b/AmpScan/core.py index 96583e7e4df11cab431940eaebaabf09f14eb11e..0b204237181f03f71712457711a78043599d7cd7 100644 --- a/AmpScan/core.py +++ b/AmpScan/core.py @@ -100,6 +100,7 @@ class AmpObject(trimMixin, smoothMixin, analyseMixin, visMixin): if unify is True: self.unifyVert() # Call function to calculate the edges array +# self.fixNorm() if struc is True: self.calcStruct() self.values = np.zeros([len(self.vert)]) @@ -242,6 +243,18 @@ class AmpObject(trimMixin, smoothMixin, analyseMixin, visMixin): self.vert[self.faces[:,0]]) mag = np.linalg.norm(norms, axis=1) self.norm = np.divide(norms, mag[:,None]) + + def fixNorm(self): + r""" + Fix normals of faces so they all face outwards + """ + fC = self.vert[self.faces].mean(axis=1) + cent = self.vert.mean(axis=0) + polarity = np.sum(self.norm * (fC-cent), axis=1) < 0 + if polarity.mean() > 0.5: + self.faces[:, [1,2]] = self.faces[:, [2,1]] + self.calcNorm() + if hasattr(self, 'vNorm'): self.calcVNorm() def calcVNorm(self): """ @@ -259,7 +272,7 @@ class AmpObject(trimMixin, smoothMixin, analyseMixin, visMixin): row, col = np.unravel_index(o_idx, self.faces.shape) ndx = np.searchsorted(f[o_idx], range(self.vert.shape[0]), side='right') ndx = np.r_[0, ndx] - norms = self.norm[self.faces, :][row, col, :] + norms = self.norm[row, :] self.vNorm = np.zeros(self.vert.shape) for i in range(self.vert.shape[0]): self.vNorm[i, :] = norms[ndx[i]:ndx[i+1], :].mean(axis=0) @@ -328,8 +341,11 @@ class AmpObject(trimMixin, smoothMixin, analyseMixin, visMixin): >>> ang = [np.pi/2, -np.pi/4, np.pi/3] >>> amp.rotateAng(ang, ang='rad') """ - R = self.rotMatrix(rot, ang) - self.rotate(R, norms) + if type(rot)==type([]): + R = self.rotMatrix(rot, ang) + self.rotate(R, norms) + else: + raise TypeError("rotateAng requires a list") def rotate(self, R, norms=True): @@ -369,7 +385,6 @@ class AmpObject(trimMixin, smoothMixin, analyseMixin, visMixin): self.translate(T) - @staticmethod def rotMatrix(rot, ang='rad'): r""" @@ -403,3 +418,19 @@ class AmpObject(trimMixin, smoothMixin, analyseMixin, visMixin): [0, 0, 1]]) R = np.dot(np.dot(Rz, Ry), Rx) return R + + def flip(self, axis=1): + r""" + Flip the mesh in a plane + + Parameters + ---------- + axis: int, default 1 + The axis in which to flip the mesh + + """ + self.vert[:, axis] *= -1.0 + # Switch face order to normals face same direction + self.faces[:, [1, 2]] = self.faces[:, [2, 1]] + self.calcNorm() + self.calcVNorm() diff --git a/AmpScan/registration.py b/AmpScan/registration.py index 4a98b82b8badb61c986f8713c5076b0dceb960d5..4cf2039b7a850176f707dc661483d012f30a211d 100644 --- a/AmpScan/registration.py +++ b/AmpScan/registration.py @@ -7,6 +7,7 @@ import numpy as np import copy from scipy import spatial from .core import AmpObject +import matplotlib.pyplot as plt class registration(object): r""" @@ -48,7 +49,7 @@ class registration(object): def point2plane(self, steps = 1, neigh = 10, inside = True, subset = None, - scale=None, smooth=1, fixBrim=False, error=False): + scale=None, smooth=1, fixBrim=False, error='norm'): r""" Point to Plane method for registration between the two meshes @@ -121,7 +122,7 @@ class registration(object): GMag = np.sqrt(np.einsum('ijk, ijk->ij', G, G)) GInd = GMag.argmin(axis=1) else: - G, GInd = self.calcBarycentric(rVert, G, ind) + G, GInd = self.__calcBarycentric(rVert, G, ind) # Define vector from baseline point to intersect point D = G[np.arange(len(G)), GInd, :] rVert += D/step @@ -135,15 +136,17 @@ class registration(object): self.reg.calcStruct() self.reg.values[:] = self.calcError(error) - def calcError(self, direct=True): + def calcError(self, method='norm'): r""" Calculate the magnitude of distances between the baseline and registered array Parameters ---------- - direct: bool, default True - If true, the magnitude can be positive or negative depending on whether the registered - vertex is inside or outside the baseline surface + method: str, default 'norm' + The method used to calculate the distances. 'abs' returns the absolute + distance. 'cent'calculates polarity based upon distance from centroid. + 'norm' calculates dot product between baseline vertex normal and distance + normal Returns ------- @@ -151,31 +154,64 @@ class registration(object): Magnitude of distances """ - if direct is True: - self.b.calcVNorm() - values = np.linalg.norm(self.reg.vert - self.b.vert, axis=1) - # Calculate the unit vector normal between corresponding vertices - # baseline and target -# vector = (self.reg.vert - self.b.vert)/values[:, None] -# # Calculate angle between the two unit vectors using normal of cross -# # product between vNorm and vector and dot -# normcrossP = np.linalg.norm(np.cross(vector, self.b.vNorm), axis=1) -# dotP = np.einsum('ij,ij->i', vector, self.b.vNorm) -# angle = np.arctan2(normcrossP, dotP) -# polarity = np.ones(angle.shape) -# polarity[angle < np.pi/2] =-1.0 - cent = self.b.vert.mean(axis=0) - r = np.linalg.norm(self.reg.vert - cent, axis=1) - b = np.linalg.norm(self.b.vert - cent, axis=1) - polarity = np.ones([self.reg.vert.shape[0]]) - polarity[r<b] = -1 - values = values * polarity - return values - else: - values = np.linalg.norm(self.reg.vert - self.b.vert, axis=1) + method = '_registration__' + method + 'Dist' + try: + values = getattr(self, method)() return values + except: ValueError('"%s" is not a method, try "abs", "cent" or "prod"' % method) + + + + def __absDist(self): + r""" + Return the error based upon the absolute distance - def calcBarycentric(self, vert, G, ind): + Returns + ------- + values: array_like + Magnitude of distances + + """ + return np.linalg.norm(self.reg.vert - self.b.vert, axis=1) + + def __centDist(self): + r""" + Return the error based upon distance from centroid + + Returns + ------- + values: array_like + Magnitude of distances + + """ + values = np.linalg.norm(self.reg.vert - self.b.vert, axis=1) + cent = self.b.vert.mean(axis=0) + r = np.linalg.norm(self.reg.vert - cent, axis=1) + b = np.linalg.norm(self.b.vert - cent, axis=1) + polarity = np.ones([self.reg.vert.shape[0]]) + polarity[r<b] = -1 + return values * polarity + + def __normDist(self): + r""" + Returns error based upon scalar product of normal + + Returns + ------- + values: array_like + Magnitude of distances + + """ + self.b.calcVNorm() + D = self.reg.vert - self.b.vert + n = self.b.vNorm + values = np.linalg.norm(D, axis=1) + polarity = np.sum(n*D, axis=1) < 0 + values[polarity] *= -1.0 + return values + + + def __calcBarycentric(self, vert, G, ind): r""" Calculate the barycentric co-ordinates of each target face and the registered vertex, this ensures that the registered vertex is within the bounds of the target face. If not @@ -231,3 +267,26 @@ class registration(object): GMag = np.sqrt(np.einsum('ijk, ijk->ij', G, G)) GInd = GMag.argmin(axis=1) return G, GInd + + def plotResults(self, name=None, xrange=None, color=None, alpha=None): + r""" + Function to generate a mpl figure. Includes a rendering of the + AmpObject, a histogram of the registration values + + Returns + ------- + fig: mplfigure + A matplot figure of the standard analysis + + """ + fig, ax = plt.subplots(1) + n, bins, _ = ax.hist(self.reg.values, 50, density=True, range=xrange, + color=color, alpha=alpha) + mean = self.reg.values.mean() + stdev = self.reg.values.std() + ax.set_title(r'Distribution of shape variance, ' + '$\mu=%.2f$, $\sigma=%.2f$' % (mean, stdev)) + ax.set_xlim(None) + if name is not None: + plt.savefig(name, dpi = 300) + return ax, n, bins \ No newline at end of file diff --git a/AmpScan/pca.py b/AmpScan/ssm.py similarity index 97% rename from AmpScan/pca.py rename to AmpScan/ssm.py index 9b78f7c286a7fdd327b35b968e75dcc8a8241723..ba5133be3dd728f0fa5ba614029ce27510ea2ecc 100644 --- a/AmpScan/pca.py +++ b/AmpScan/ssm.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -Package for using AmpScan to run mean-centered principal component analysis +Package for using AmpScan to run statistical shape modeling using mean-centered principal component analysis on a group of scans Copyright: Joshua Steer 2018, Joshua.Steer@soton.ac.uk """ diff --git a/README.md b/README.md index b5d49024a6403f615c33a8c4418eb45a64979f80..ca913ad304be60d46aaff73ae7acf4665c2174c0 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,47 @@ AmpScan ======= -AmpScan is a Python package that provides matrix manipulation tools specifically for -the design of prosthetic sockets. It provides functions for handling common design workflows -such as importing, aligning and registering meshes. AmpScan relies heavily on [NumPy](http://www.numpy.org/) -and [SciPy](https://www.scipy.org/) to perform mathematical operations with vizualisation handled by -[PyQt](https://riverbankcomputing.com/software/pyqt/intro) and [VTK](https://www.vtk.org/). The package is -still under development by researchers at the University of Southampton. For full documentation, -visit the [AmpScan website](https://ampscan.readthedocs.io/en/latest/). +AmpScan is an open-source Python package for analysis and visualisation of digitised surface scan data, specifically for applications within Prosthetics and Orthotics. These industries are increasingly using surface scanners as part of clinical practice to capture the patient's individual geometry to design personalised devices. AmpScan gives researchers within this field access to powerful tools to analyse the collected scans to help inform clinical practice towards improved patient-outcomes. This package has been designed to be accessible for researchers with only a limited knowledge of Python. Therefore, analysis procedures can all be accessed using the lightweight Graphical User Interface. -Installation ------------- +AmpScan relies heavily on [NumPy](http://www.numpy.org/) and [SciPy](https://www.scipy.org/) to perform mathematical operations with visualisation handled by [PyQt](https://riverbankcomputing.com/software/pyqt/intro) and [VTK](https://www.vtk.org/). The package is still under development by researchers at the University of Southampton. For full documentation, visit the [AmpScan website](https://ampscan.readthedocs.io/en/latest/). -AmpScan has a number of dependencies, we recommend using conda to deal with these. To create a new -environment to run AmpScan in: +Installing with Conda (Recommended) +----------------------------------- -``conda create -n env_name python=3 numpy scipy pyqt matplotlib`` +AmpScan has a number of dependencies, namely; NumPy, SciPy, Matplotlib, PyQt and vtk. We recommend using +conda to deal with these. Before installation, ensure your environment is using Python 3. Verify that +you are running the latest version of pip: -``conda install -c conda-forge vtk=8.1.0`` +``python -m pip install --upgrade pip`` -For the most up to date version of AmpScan, clone directly from the gitlab repository into a virtual environment using: +Install dependencies using conda: + +``conda install numpy scipy pyqt matplotlib vtk==8.1.0`` + +Install AmpScan using pip: + +``pip install AmpScan`` + +Installing with Pip +------------------- + +AmpScan has a number of dependencies, namely; NumPy, SciPy, Matplotlib, PyQt and vtk. Before +installing, ensure you have the latest version of pip: + +``python -m pip install --upgrade pip`` + +Then install the dependencies using: + +``pip install numpy matplotlib scipy pyqt5 vtk==8.1.0`` + +You can then install AmpScan from test PyPI using: + +``pip install AmpScan`` + +Developer Install +----------------- + +For the most up to date version of AmpScan, clone directly from the gitlab repository using: ``git clone https://git.soton.ac.uk/js22g12/AmpScan.git`` @@ -27,11 +49,15 @@ Navigate to the `AmpScan/` directory and run a pip install using: ``pip install -e .`` -A pip installation is also available through test PyPI (not latest version) using: - -``$ pip install --index-url https://test.pypi.org/simple/ AmpScan`` +Maintainer Notes +---------------- +Documentation for the AmpScan library is automatically generated using +[sphinx](http://www.sphinx-doc.org/en/master/). Any additional code should be documented in +accordance with 'numpy style' docstrings. A template can be found +[here](https://www.numpy.org/devdocs/docs/howto_document.html#example). -## How to acknowledge +How to acknowledge +------------------ Find license [here](../LICENSE) diff --git a/docs/code.rst b/docs/code.rst index 57b1b5e9c26012b58f5a43d8b96f9749808fe12d..2bb84ea6c4ca9e07245cec7877a3d8f6876246d1 100644 --- a/docs/code.rst +++ b/docs/code.rst @@ -9,10 +9,10 @@ Module Documentation source/analyse source/core source/fe - source/pca source/pressSens source/registration source/smooth + source/ssm source/trim source/tsbSocketDesign diff --git a/docs/index.rst b/docs/index.rst index 5d30659f227ac7996272ecd7e7604b9652fbeab0..55291ceb294901dd32b32ac518b9119999172cef 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,4 +1,3 @@ - .. image:: AmpScanlogo.svg :width: 30% :align: center @@ -7,41 +6,70 @@ AmpScan ======= -AmpScan is a Python package that provides matrix manipulation tools specifically for -the design of prosthetic sockets. It provides functions for handling common design workflows -such as importing, aligning and registering meshes. AmpScan relies heavily on `NumPy`_ and -`SciPy`_ to perform mathematical operations with vizualisation handled by `PyQt`_ and `VTK`_. -The package is still under active development by researchers at the University of Southampton -- this documentation should be considered the 'go-to' for anyone interested in using or -developing AmpScan. +AmpScan is an open-source Python package for analysis and visualisation of digitised surface scan data, +specifically for applications within Prosthetics and Orthotics. These industries are increasingly using +surface scanners as part of clinical practice to capture the patient's individual geometry to design +personalised devices. AmpScan gives researchers within this field access to powerful tools to analyse +the collected scans to help inform clinical practice towards improved patient-outcomes. This package +has been designed to be accessible for researchers with only a limited knowledge of Python. Therefore, +analysis procedures can all be accessed using the lightweight Graphical User Interface. + +AmpScan relies heavily on numpy_ and scipy_ to perform +mathematical operations with visualisation handled by PyQt_ +and VTK_. The package is still under development by researchers at the University of Southampton. +For full documentation, visit the AmpScan_ website. .. _numpy: http://www.numpy.org/ .. _SciPy: https://www.scipy.org/ .. _PyQt: https://riverbankcomputing.com/software/pyqt/intro .. _VTK: https://www.vtk.org/ +.. _AmpScan: https://ampscan.readthedocs.io/en/latest/ +Installing with Conda (Recommended) +----------------------------------- -Installation ------------- +AmpScan has a number of dependencies, namely; NumPy, SciPy, Matplotlib, PyQt and vtk. We recommend using +conda to deal with these. Before installation, ensure your environment is using Python 3. Verify that +you are running the latest version of pip: -AmpScan has a number of dependencies, we recommend using conda to deal with these. To create a new -environment to run AmpScan in: +``python -m pip install --upgrade pip`` -``conda create -n env_name python=3 numpy scipy pyqt matplotlib`` +Install dependencies using conda: -``conda install -c conda-forge vtk=8.1.0`` +``conda install numpy scipy pyqt matplotlib vtk==8.1.0`` -For the most up to date version of AmpScan, clone directly from the gitlab repository into a virtual environment using: +Install AmpScan using pip: -``git clone https://git.soton.ac.uk/js22g12/AmpScan.git`` +``pip install AmpScan`` -Navigate to the `AmpScan/` directory and run a pip editable install using: -``pip install -e .`` +Installing with Pip +------------------- + +AmpScan has a number of dependencies, namely; NumPy, SciPy, Matplotlib, PyQt and vtk. Before +installing, ensure you have the latest version of pip: + +``python -m pip install --upgrade pip`` + +Then install the dependencies using: + +``pip install numpy matplotlib scipy pyqt5 vtk==8.1.0`` -A pip installation is also available through test PyPI (not latest version) using: +You can then install AmpScan from test PyPI using: -``$ pip install --index-url https://test.pypi.org/simple/ AmpScan`` +``pip install AmpScan`` + + +Developer Install +----------------- + +For the most up to date version of AmpScan, clone directly from the gitlab repository using: + +``git clone https://git.soton.ac.uk/js22g12/AmpScan.git`` + +Navigate to the `AmpScan/` directory and run a pip install using: + +``pip install -e .`` Getting Started diff --git a/docs/source/AmpScan.rst b/docs/source/AmpScan.rst index 7e1dd174c0c7f412a2a9aa5561c44874595b07cd..a3727d2a298d90d67829c4ded55415aed9ec6901 100644 --- a/docs/source/AmpScan.rst +++ b/docs/source/AmpScan.rst @@ -36,34 +36,18 @@ AmpScan.core module :undoc-members: :show-inheritance: -AmpScan.fe module ------------------ +AmpScan.registration module +--------------------------- -.. automodule:: AmpScan.fe +.. automodule:: AmpScan.registration :members: :undoc-members: :show-inheritance: -AmpScan.pca module +AmpScan.ssm module ------------------ -.. automodule:: AmpScan.pca - :members: - :undoc-members: - :show-inheritance: - -AmpScan.pressSens module ------------------------- - -.. automodule:: AmpScan.pressSens - :members: - :undoc-members: - :show-inheritance: - -AmpScan.registration module ---------------------------- - -.. automodule:: AmpScan.registration +.. automodule:: AmpScan.ssm :members: :undoc-members: :show-inheritance: @@ -84,14 +68,6 @@ AmpScan.trim module :undoc-members: :show-inheritance: -AmpScan.tsbSocketDesign module ------------------------------- - -.. automodule:: AmpScan.tsbSocketDesign - :members: - :undoc-members: - :show-inheritance: - Module contents --------------- diff --git a/docs/source/pca.rst b/docs/source/ssm.rst similarity index 67% rename from docs/source/pca.rst rename to docs/source/ssm.rst index 56b013e7859e09fdd8fafae1e3721ad274e3df41..a114e9a477e80d58896809c5d09d7b335f13952b 100644 --- a/docs/source/pca.rst +++ b/docs/source/ssm.rst @@ -1,7 +1,7 @@ -pca module +ssm module ========== -.. automodule:: AmpScan.pca +.. automodule:: AmpScan.ssm :members: :undoc-members: :show-inheritance: diff --git a/joss/AmpScan_Overview.png b/joss/AmpScan_Overview.png new file mode 100644 index 0000000000000000000000000000000000000000..8125946c1edd223a3bb2fa574f61ab119fba94a5 Binary files /dev/null and b/joss/AmpScan_Overview.png differ diff --git a/joss/paper.md b/joss/paper.md index 0a71f4b4d2a28ef94ace5f9e7873b9b440840807..00960c158921d354f8671e9697950700f7b91a64 100644 --- a/joss/paper.md +++ b/joss/paper.md @@ -1,30 +1,50 @@ --- -title: 'AmpScan: A lightweight Python package for shape analysis' +title: 'AmpScan: A lightweight Python package for clinical analysis of prosthetics and orthotics' +authors: + - name: Joshua W Steer + orcid: 0000-0002-6288-1347 + affiliation: 1 + - name: Oliver Stocks + affiliation: 1 + - name: Peter R Worsley + orcid: 0000-0003-0145-5042 + affiliation: 2 + - name: Alexander S Dickinson + orcid: 0000-0002-9647-1944 + affiliation: 1 +affiliations: + - name: Bioengineering Research Group, University of Southampton + index: 1 + - name: Fundamental Care and Safety Research Group, University of Southampton + index: 2 date: 03 September 2018 bibliography: paper.bib --- # Summary -Digitised surface scanners are incresingly used. In particular, -Based upon MATLAB code from a previously published paper [@Dickinson:2016] +The increasing accessibility of digitised surface scanners are giving users the ability to accurately digitise the 3D surface geometry of real world objects which may then be 3D printed. In addition to hobbyist applications, these devices are being increasingly used within prosthetics and orthotics clinics where they are used to capture the patients individual geometry. These scans are imported into computer-aided design packages to generate patient-specific medical devices, such as prosthetic sockets or ankle-foot orthosis. This increasing digitsiation of patient data provides great potential for analysis of these scans in order to inform and improve clinical practice. While this has been an area of academic interest for several decades [@Sanders: 2005], clinical use is minimal. One of the reasons for this is the lack of tools available for clinicians to analyse the geometry of their patient datasets. -Alignment of files used an iterative closest point algorithm -The authors recognise that these individual steps have been extensively documented in -literature and +In a previously published paper [@Dickinson:2016], a method was detailed for comparing between pairs of surface scans using alignment, registration and visualisation. In this paper, this method was used for evaluation of the accuracy and inter- and intra-reliability of the scanners. Further studies have also demonstrated using this package for comparing the consistency of casting techniques [Dickinson: APOSM], statistical shape modeling across the population [@Worsley: ISPO World] and quantifying rectifications between the residual limb and the prosthetic socket [@Steer: AOPA]. -In the name of simplicity for researchers, this package has been designed to be lightweight and curated for a specific application. -Other packages perform the specific tasks of AmpScan more comprehensively such as [mayavi](https://docs.enthought.com/mayavi/mayavi/index.html) for scientfic data visualisation, [open3d](http://www.open3d.org/docs/getting_started.html) for alignment and registration techniques + -This is a lightweight package built mostly on Numpy. As such this requires minimal additional -packages and simple install designed to be used by researchers without extensive software background. +This method was written in MATLAB, however, this was considered restrictive for other researchers to access as they may not possess a MATLAB license. In order to maximise access to the developed techniques and improve performance, especially for 3D visualisation, the methods were rewritten from scratch within Python, including updated algorithms for each stage of the aforementioned process. -Those looking for a more comprehensive set of registration and algorithms are recommended to look for alternative -software packages such as open3D. +The AmpScan package has been designed with clinical researchers in mind, with an appreciation that they may not have an extensive background in coding. To this aim, this software has been developed in Python and leverages the commonly used libraries of NumPy, SciPy, matplotlib, vtk and pyqt. As such, full functionality of the software can be accessed without requiring additional installs. The core analysis of the package can be carried out with, additionally, a simple GUI has been supplied to allow the user to access the core functionality of AmpScan without scripting. -Simple visualisation is provided by adding a thin layer around the VTK package. Again those look - -We encourage researchers who have developed their own alignment and registration algorithms in python, further the package does not contain any mesh fixing tools that may be required from messy scans. Until such methods are implemented, the authors recommend [meshlab](http://www.meshlab.net/) which also contains methods for alignment and visualisation. +The core functionality of AmpScan is summarised below with a more detailed description available in the online [documentation](https://ampscan.readthedocs.io/en/latest/): +- **[AmpObject](https://ampscan.readthedocs.io/en/latest/source/AmpScan.html#AmpScan.core.AmpObject)**: this is the key object of the package and holds the key data and methods. The key data held within the AmpObject is the mesh data including arrays of the vertices, faces, normals and field values. Additionally, the vtk actor for visualisation is also stored. The [core](https://ampscan.readthedocs.io/en/latest/source/core.html) methods of the AmpObject include imports for .stl files, saving .stl files, rotation and translation. Additional methods are added via mixins for [analysis](https://ampscan.readthedocs.io/en/latest/source/analyse.html), [smoothing](https://ampscan.readthedocs.io/en/latest/source/smooth.html), [trimming](https://ampscan.readthedocs.io/en/latest/source/trim.html) and [visualisation](https://ampscan.readthedocs.io/en/latest/source/ampVis.html). +- **[Alignment](https://ampscan.readthedocs.io/en/latest/source/align.html)**: This takes two AmpObjects, one fixed and one moving, and applies a rigid transformation to the moving AmpObject in order to minimise the spatial error between the two AmpObjects. This is performed through an Iterative Closest Point (ICP) algorithm. +- **[Registration](https://ampscan.readthedocs.io/en/latest/source/registration.html)**: This takes two AmpObjects, one baseline and one target, and applied a non-rigid transformation to morph the baseline vertices onto the surface of the target. This is performed by a point-to-plane method. The registered shape ends up with the same number of vertices and connectivity as the baseline, thereby enabling shape comparison between the shapes. +- **[Statistical Shape Modelling](https://ampscan.readthedocs.io/en/latest/source/ssm.html)**: This provides methods for analysing the shapes of scans across a population using the Principal Component Analysis method. +- **Graphical User Interface**: This enables visualisation to multiple AmpObjects within a single window, giving access to the automated and manual alignment tools as well as registration. This facilitates the core analysis of the scan data for users who are not experienced Python users. +Other packages perform the specific tasks of AmpScan more comprehensively such as [mayavi](https://docs.enthought.com/mayavi/mayavi/index.html) for scientific data visualisation, [open3d](http://www.open3d.org/docs/getting_started.html) for alignment and registration techniques. We encourage researchers who have developed their own alignment and registration algorithms in python, further the package does not contain any mesh fixing tools that may be required from messy scans. Until such methods are implemented, the authors recommend [meshlab](http://www.meshlab.net/) which also contains methods for alignment and visualisation. +# Acknowledgments +While the AmpScan package was not funded directly, the authors would like to thank the following for their financial support: +- JWS: the University of Southampton’s EPSRC Doctoral Training Program (ref EP/M508147/1) and EUROSTARS project (ref 9396) +- PRW: the EPSRC-NIHR “Medical Device and Vulnerable Skin Network” (ref EP/M000303/1), +- ASD: the Royal Academy of Engineering, UK, (ref RF/130). # References \ No newline at end of file diff --git a/setup.py b/setup.py index a41750fd415bf2fcacf43da102570e031a7e5679..0ca4a23771c253824a4adf5878092a19359f9c11 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ def readme(): setup(name='AmpScan', - version='0.4', + version='0.7', description=('Package for analysis of ' 'surface scan data of residual limbs'), long_description=readme(), diff --git a/templates/numpyDocsTemp.py b/templates/numpyDocsTemp.py deleted file mode 100644 index c0c1c0ad39fb5c1ba73076b51838bb67d9bf5e92..0000000000000000000000000000000000000000 --- a/templates/numpyDocsTemp.py +++ /dev/null @@ -1,123 +0,0 @@ -"""This is the docstring for the numpyDocsTemp.py module. Modules names should -have short, all-lowercase names. The module name may have underscores if -this improves readability. - -Every module should have a docstring at the very top of the file. The -module's docstring may extend over multiple lines. If your docstring does -extend over multiple lines, the closing three quotation marks must be on -a line by itself, preferably preceded by a blank line. - -""" -from __future__ import division, absolute_import, print_function - -import os # standard library imports first - -# Do NOT import using *, e.g. from numpy import * -# -# Import the module using -# -# import numpy -# -# instead or import individual functions as needed, e.g -# -# from numpy import array, zeros -# -# If you prefer the use of abbreviated module names, we suggest the -# convention used by NumPy itself:: - -import numpy as np -import matplotlib as mpl -import matplotlib.pyplot as plt - -# These abbreviated names are not to be used in docstrings; users must -# be able to paste and execute docstrings after importing only the -# numpy module itself, unabbreviated. - - -def foo(var1, var2, long_var_name='hi'): - r"""A one-line summary that does not use variable names or the - function name. - - Several sentences providing an extended description. Refer to - variables using back-ticks, e.g. `var`. - - Parameters - ---------- - var1 : array_like - Array_like means all those objects -- lists, nested lists, etc. -- - that can be converted to an array. We can also refer to - variables like `var1`. - var2 : int - The type above can either refer to an actual Python type - (e.g. ``int``), or describe the type of the variable in more - detail, e.g. ``(N,) ndarray`` or ``array_like``. - long_var_name : {'hi', 'ho'}, optional - Choices in brackets, default first when optional. - - Returns - ------- - type - Explanation of anonymous return value of type ``type``. - describe : type - Explanation of return value named `describe`. - out : type - Explanation of `out`. - type_without_description - - Other Parameters - ---------------- - only_seldom_used_keywords : type - Explanation - common_parameters_listed_above : type - Explanation - - Raises - ------ - BadException - Because you shouldn't have done that. - - See Also - -------- - otherfunc : relationship (optional) - newfunc : Relationship (optional), which could be fairly long, in which - case the line wraps here. - thirdfunc, fourthfunc, fifthfunc - - Notes - ----- - Notes about the implementation algorithm (if needed). - - This can have multiple paragraphs. - - You may include some math: - - .. math:: X(e^{j\omega } ) = x(n)e^{ - j\omega n} - - And even use a Greek symbol like :math:`\omega` inline. - - References - ---------- - Cite the relevant literature, e.g. [1]_. You may also cite these - references in the notes section above. - - .. [1] O. McNoleg, "The integration of GIS, remote sensing, - expert systems and adaptive co-kriging for environmental habitat - modelling of the Highland Haggis using object-oriented, fuzzy-logic - and neural-network techniques," Computers & Geosciences, vol. 22, - pp. 585-588, 1996. - - Examples - -------- - These are written in doctest format, and should illustrate how to - use the function. - - >>> a = [1, 2, 3] - >>> print [x + 3 for x in a] - [4, 5, 6] - >>> print "a\n\nb" - a - b - - """ - - pass diff --git a/tests/sample_test.py b/tests/sample_test.py index a9955bfcaf116c5c83648e7419fb32e723baee76..a5d4fb70477d7e9231fecd484e8daf9c0d7443ae 100644 --- a/tests/sample_test.py +++ b/tests/sample_test.py @@ -21,20 +21,24 @@ class TestBasicFunction(unittest.TestCase): s = str(type(vtk)) self.assertEqual(s, "<class 'module'>") s = str(type(AmpScan.core)) - self.assertEqual(s, "<class 'module'>") + self.assertEqual(s, "<class 'module'>", "Failed import: AmpScan.core") @unittest.expectedFailure def test_failure(self): s = str(type("string")) self.assertEqual(s, "<class 'module'>") -# def test_import_stl(self): -# modPath = os.path.abspath(os.getcwd()) -# sys.path.insert(0, modPath) -# stlPath = os.path.abspath(os.getcwd()) + "\\tests\\sample_stl_sphere_ASCII.stl" -# from AmpScan.core import AmpObject -# Amp = AmpObject(stlPath) -# self.assertRaises(MemoryError) + def test_import_stl(self): + modPath = os.path.abspath(os.getcwd()) + sys.path.insert(0, modPath) + stlPath = os.path.abspath(os.getcwd()) + "\\tests\\sample_stl_sphere_BIN.stl" + from AmpScan.core import AmpObject + Amp = AmpObject(stlPath) + s = str(type(Amp)) + self.assertEqual(s, "<class 'AmpScan.core.AmpObject'>", "Not expected Object") + with self.assertRaises(TypeError): + Amp.rotateAng(7) + Amp.rotateAng({}) if __name__ == '__main__': unittest.main()