├── CITATION.cff ├── LICENSE ├── Load tiff.ipynb ├── README.MD ├── Run compactness.ipynb ├── codemeta.json ├── pydescriptors ├── __init__.py ├── compactness.py ├── cubeness.py ├── helpers.py └── moments.py ├── setup.py └── tests ├── __init__.py ├── test_cubeness.py └── test_moments.py /CITATION.cff: -------------------------------------------------------------------------------- 1 | # This CITATION.cff file was generated with FAIRshare. 2 | # Visit https://fairdataihub.org/fairshare to learn more! 3 | 4 | abstract: Python implementation of various 2D and 3D compactness measures. 5 | authors: 6 | - affiliation: eScience Center 7 | family-names: Martinez Ortiz 8 | given-names: Carlos 9 | orcid: 0000-0001-5565-7577 10 | cff-version: 1.2.0 11 | date-released: '2022-08-17T15:40:44.296Z' 12 | keywords: 13 | - Shape descriptor 14 | - Compactness 15 | license: Apache-2.0 16 | message: If you use this software, please cite it as below. 17 | repository-code: https://github.com/c-martinez/pydescriptors 18 | title: PyDescriptors 19 | type: software 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 Carlos Martinez-Ortiz 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Load tiff.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "name": "stdout", 12 | "output_type": "stream", 13 | "text": [ 14 | "Using matplotlib backend: TkAgg\n", 15 | "Populating the interactive namespace from numpy and matplotlib\n" 16 | ] 17 | } 18 | ], 19 | "source": [ 20 | "%pylab" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 2, 26 | "metadata": { 27 | "collapsed": true 28 | }, 29 | "outputs": [], 30 | "source": [ 31 | "%load_ext autoreload\n", 32 | "%autoreload 2" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": { 39 | "collapsed": true 40 | }, 41 | "outputs": [], 42 | "source": [ 43 | "from __future__ import division\n", 44 | "\n", 45 | "import glob\n", 46 | "import numpy as np\n", 47 | "from PIL import Image\n", 48 | "from mpl_toolkits.mplot3d import Axes3D" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 5, 54 | "metadata": { 55 | "collapsed": true 56 | }, 57 | "outputs": [], 58 | "source": [ 59 | "from compactness.helpers import getSphere\n", 60 | "from compactness.helpers import rotate3D\n", 61 | "\n", 62 | "from compactness.moments import immoment3D\n", 63 | "\n", 64 | "from compactness.cubeness import __int_max_abs__\n", 65 | "from compactness.cubeness import cubeness_mz" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 7, 71 | "metadata": { 72 | "collapsed": false 73 | }, 74 | "outputs": [], 75 | "source": [ 76 | "# Move to helpers ?\n", 77 | "def plotVolume(volume):\n", 78 | " fig = plt.figure()\n", 79 | " ax = fig.add_subplot(111, projection='3d')\n", 80 | " X, Y, Z = volume.nonzero()\n", 81 | " ax.plot(X, Y, Z, '.')\n", 82 | "#plotVolume(volume)" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": 48, 88 | "metadata": { 89 | "collapsed": true 90 | }, 91 | "outputs": [], 92 | "source": [ 93 | "# Test - Display Cube\n", 94 | "plotVolume(np.ones((140,140,140)))" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 8, 100 | "metadata": { 101 | "collapsed": false 102 | }, 103 | "outputs": [], 104 | "source": [ 105 | "# Test - Display sphere\n", 106 | "volume = getSphere(50)\n", 107 | "plotVolume(volume)" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": 23, 113 | "metadata": { 114 | "collapsed": false 115 | }, 116 | "outputs": [], 117 | "source": [ 118 | "# Test rotate3D\n", 119 | "nSamples = 10\n", 120 | "X = np.zeros(nSamples)\n", 121 | "Y = np.linspace(-1,1,nSamples)\n", 122 | "Z = np.zeros(nSamples)\n", 123 | "\n", 124 | "fig = plt.figure()\n", 125 | "ax = fig.add_subplot(111, projection='3d')\n", 126 | "\n", 127 | "for rx in np.linspace(0,pi, 5):\n", 128 | " for ry in np.linspace(0,pi, 5):\n", 129 | " X_,Y_,Z_ = rotate3D(X,Y,Z, rx, ry)\n", 130 | " ax.plot(X_, Y_, Z_,'+')\n" 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": 75, 136 | "metadata": { 137 | "collapsed": false 138 | }, 139 | "outputs": [ 140 | { 141 | "name": "stdout", 142 | "output_type": "stream", 143 | "text": [ 144 | "710403\n", 145 | "47806861\n", 146 | "49495500\n", 147 | "56286828\n", 148 | "67.2954097885\n", 149 | "69.6724253698\n", 150 | "79.2322498638\n", 151 | "==============\n", 152 | "710403.0\n", 153 | "-2.77516392089e-08\n", 154 | "3.87539955682e-09\n", 155 | "-1.4656222902e-09\n" 156 | ] 157 | } 158 | ], 159 | "source": [ 160 | "# Test immmoments\n", 161 | "volume = getSphere(50)\n", 162 | "X,Y,Z = volume.nonzero()\n", 163 | "\n", 164 | "# Moment 0,0,0 is the volume of the object\n", 165 | "assert volume.sum()==immoment3D(X,Y,Z,0,0,0)\n", 166 | "assert len(X)==immoment3D(X,Y,Z,0,0,0)\n", 167 | "\n", 168 | "m000 = immoment3D(X,Y,Z,0,0,0)\n", 169 | "m100 = immoment3D(X,Y,Z,1,0,0)\n", 170 | "m010 = immoment3D(X,Y,Z,0,1,0)\n", 171 | "m001 = immoment3D(X,Y,Z,0,0,1)\n", 172 | "print m000\n", 173 | "print m100\n", 174 | "print m010\n", 175 | "print m001\n", 176 | "\n", 177 | "print m100 / m000\n", 178 | "print m010 / m000\n", 179 | "print m001 / m000\n", 180 | "\n", 181 | " # Centering \n", 182 | " X = X-(m100 / m000)\n", 183 | " Y = Y-(m010 / m000)\n", 184 | " Z = Z-(m001 / m000)\n", 185 | "\n", 186 | " m000 = immoment3D(X,Y,Z,0,0,0)\n", 187 | " m100 = immoment3D(X,Y,Z,1,0,0)\n", 188 | " m010 = immoment3D(X,Y,Z,0,1,0)\n", 189 | " m001 = immoment3D(X,Y,Z,0,0,1)\n", 190 | "print '=============='\n", 191 | "print m000\n", 192 | "print m100\n", 193 | "print m010\n", 194 | "print m001" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": 45, 200 | "metadata": { 201 | "collapsed": true 202 | }, 203 | "outputs": [], 204 | "source": [ 205 | "# Test cubeness of sphere\n", 206 | "volume = getSphere(50)\n", 207 | "X,Y,Z = volume.nonzero()\n", 208 | "c,fxy = cubeness_mz(X,Y,Z)\n", 209 | "print c" 210 | ] 211 | }, 212 | { 213 | "cell_type": "code", 214 | "execution_count": 61, 215 | "metadata": { 216 | "collapsed": false 217 | }, 218 | "outputs": [ 219 | { 220 | "name": "stdout", 221 | "output_type": "stream", 222 | "text": [ 223 | "1.00053361793\n" 224 | ] 225 | } 226 | ], 227 | "source": [ 228 | "# Test cubeness of cube\n", 229 | "volume = np.ones((50,50,50))\n", 230 | "X,Y,Z = volume.nonzero()\n", 231 | "c,fxy = cubeness_mz(X,Y,Z)\n", 232 | "print c" 233 | ] 234 | }, 235 | { 236 | "cell_type": "code", 237 | "execution_count": 63, 238 | "metadata": { 239 | "collapsed": false 240 | }, 241 | "outputs": [ 242 | { 243 | "data": { 244 | "text/plain": [ 245 | "[]" 246 | ] 247 | }, 248 | "execution_count": 63, 249 | "metadata": {}, 250 | "output_type": "execute_result" 251 | } 252 | ], 253 | "source": [ 254 | "# Test -- plot fxy\n", 255 | "angles = np.linspace(0,np.pi,fxy.shape[0])\n", 256 | "fig = plt.figure()\n", 257 | "ax = fig.add_subplot(111, projection='3d')\n", 258 | "a,b = np.meshgrid(angles, angles)\n", 259 | "ax.plot(a.flatten(), b.flatten(), fxy.flatten(), '+')" 260 | ] 261 | }, 262 | { 263 | "cell_type": "code", 264 | "execution_count": null, 265 | "metadata": { 266 | "collapsed": true 267 | }, 268 | "outputs": [], 269 | "source": [] 270 | }, 271 | { 272 | "cell_type": "code", 273 | "execution_count": null, 274 | "metadata": { 275 | "collapsed": true 276 | }, 277 | "outputs": [], 278 | "source": [] 279 | }, 280 | { 281 | "cell_type": "code", 282 | "execution_count": null, 283 | "metadata": { 284 | "collapsed": true 285 | }, 286 | "outputs": [], 287 | "source": [] 288 | }, 289 | { 290 | "cell_type": "markdown", 291 | "metadata": {}, 292 | "source": [ 293 | "# And now for the problem of Manuel" 294 | ] 295 | }, 296 | { 297 | "cell_type": "code", 298 | "execution_count": 64, 299 | "metadata": { 300 | "collapsed": false 301 | }, 302 | "outputs": [], 303 | "source": [ 304 | "def loadVolume(dirPath):\n", 305 | " files = glob.glob(dirPath + '/*.tif')\n", 306 | " volume = np.zeros((140, 140, 140))\n", 307 | " for i,filename in enumerate(files):\n", 308 | " img = Image.open(filename)\n", 309 | " # print filename\n", 310 | " pixels = np.asarray(img)\n", 311 | " volume[i,:,:] = pixels\n", 312 | " volume = volume > 128\n", 313 | " return volume.astype(np.int)\n", 314 | "\n", 315 | "volume = loadVolume('01_xz')" 316 | ] 317 | }, 318 | { 319 | "cell_type": "code", 320 | "execution_count": 67, 321 | "metadata": { 322 | "collapsed": true 323 | }, 324 | "outputs": [], 325 | "source": [ 326 | "# plotVolume(volume)" 327 | ] 328 | }, 329 | { 330 | "cell_type": "code", 331 | "execution_count": 69, 332 | "metadata": { 333 | "collapsed": false 334 | }, 335 | "outputs": [ 336 | { 337 | "name": "stdout", 338 | "output_type": "stream", 339 | "text": [ 340 | "0.629101969423\n" 341 | ] 342 | } 343 | ], 344 | "source": [ 345 | "X,Y,Z = volume.nonzero()\n", 346 | "c,fxy = cubeness_mz(X,Y,Z)\n", 347 | "print c\n" 348 | ] 349 | }, 350 | { 351 | "cell_type": "code", 352 | "execution_count": null, 353 | "metadata": { 354 | "collapsed": true 355 | }, 356 | "outputs": [], 357 | "source": [] 358 | } 359 | ], 360 | "metadata": { 361 | "kernelspec": { 362 | "display_name": "IPython (Python 2)", 363 | "name": "python2" 364 | }, 365 | "language_info": { 366 | "codemirror_mode": { 367 | "name": "ipython", 368 | "version": 2 369 | }, 370 | "file_extension": ".py", 371 | "mimetype": "text/x-python", 372 | "name": "python", 373 | "nbconvert_exporter": "python", 374 | "pygments_lexer": "ipython2", 375 | "version": "2.7.6" 376 | }, 377 | "signature": "sha256:5823807ea1f5ec421e9111b2029656f3767fc33d4e85ac4916299ceab3e06c2a" 378 | }, 379 | "nbformat": 4, 380 | "nbformat_minor": 0 381 | } -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # PyDescriptors 2 | ## Compactness shape descriptors in Python 3 | 4 | This package contains Python implementation of various 2D and 3D compactness measures described in [2D and 3D Shape Descriptors](http://hdl.handle.net/10036/3026). 5 | 6 | ## Installation 7 | 8 | You can install this package by downloading the source and installing it with pip: 9 | ``` 10 | $ wget https://github.com/c-martinez/pydescriptors/archive/master.zip 11 | $ pip install master.zip 12 | ``` 13 | 14 | Or directly from git: 15 | ``` 16 | $ pip install git+git://github.com/c-martinez/pydescriptors.git 17 | ``` 18 | 19 | ## Usage 20 | ``` 21 | from pydescriptors.compactness import hz as compactness_hz 22 | from pydescriptors.compactness import bribiesca as compactness_bribiesca 23 | from pydescriptors.helpers import getSphere 24 | 25 | sphere = getSphere(20) 26 | X,Y,Z = sphere.nonzero() 27 | 28 | compactness_bribiesca(X,Y,Z) 29 | compactness_hz(X,Y,Z) 30 | ``` 31 | 32 | ## Testing 33 | Run: 34 | 35 | ``` 36 | $ nosetests 37 | ``` 38 | 39 | ## Citations 40 | If you use this code, please cite [2D and 3D Shape Descriptors](http://hdl.handle.net/10036/3026). 41 | 42 | TODO: 43 | - Add tests for compactness 44 | - Add Usage 45 | - Add installation guide (from source) 46 | - Add more descriptors 47 | - Documentation 48 | - Auto testing 49 | - pyflakes (auto) 50 | - pep8 (auto) 51 | - Add citation file 52 | - Add to Zenodo 53 | - Add TODO's as issues 54 | -------------------------------------------------------------------------------- /Run compactness.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 35, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "from __future__ import division\n", 12 | "\n", 13 | "import glob\n", 14 | "import numpy as np\n", 15 | "from PIL import Image\n", 16 | "\n", 17 | "from compactness.compactness import cubeness_mz" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 36, 23 | "metadata": { 24 | "collapsed": false 25 | }, 26 | "outputs": [], 27 | "source": [ 28 | "def loadVolume(dirPath):\n", 29 | " files = glob.glob(dirPath + '/*.tif')\n", 30 | " \n", 31 | " imgTmp = Image.open(files[0])\n", 32 | " imgTmp = np.asarray(imgTmp)\n", 33 | " volume = np.zeros((len(files), imgTmp.shape[0], imgTmp.shape[1]))\n", 34 | " \n", 35 | " for i,filename in enumerate(files):\n", 36 | " img = Image.open(filename)\n", 37 | " # print filename\n", 38 | " pixels = np.asarray(img)\n", 39 | " volume[i,:,:] = pixels\n", 40 | " volume = volume > 128\n", 41 | " return volume.astype(np.int)\n" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "files/022C4_yz2/035.tif is 140x140 (but all other photos on this folder are 138x138 -- ignored whole set" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 37, 54 | "metadata": { 55 | "collapsed": false 56 | }, 57 | "outputs": [ 58 | { 59 | "name": "stdout", 60 | "output_type": "stream", 61 | "text": [ 62 | "Loading files/013C4_xy 0.584894489741\n", 63 | "Loading files/01_xz 0.629101969423\n", 64 | "Loading files/02_yz 0.598024401056\n", 65 | "Loading files/02_yz2 0.573999005962\n", 66 | "Loading files/03_xy 0.566156310398\n", 67 | "Loading files/04_yz 0.625985195455\n", 68 | "Loading files/07_xy 0.6442018718\n", 69 | "Loading files/07_yz 0.511135021138\n", 70 | "Loading files/08_yz 0.683509933843\n", 71 | "Loading files/011C3_xy 0.688073723954\n", 72 | "Loading files/011C3_xy2 0.711362691406\n", 73 | "Loading files/024C5_yz1 0.700677313506\n", 74 | "Loading files/013C6 0.598221077286\n", 75 | "Loading files/015C5_xy 0.698833669426\n", 76 | "Loading files/021C4_xy 0.677478052813\n", 77 | "Loading files/021C4_xz 0.691620605205\n", 78 | "Loading files/022C5_xy1 0.633675997721\n", 79 | "Loading files/022C5_xy2 0.641549527519\n", 80 | "Loading files/022C5_xy3 0.621698899407\n", 81 | "Loading files/023C4_xy 0.61149338828\n", 82 | "Loading files/024C5_yz 0.768870485041\n", 83 | "Loading files/s02014 0.499540749088\n", 84 | "Loading files/025C4_yz 0.557830876382\n", 85 | "Loading files/027C4_xz 0.70013773188\n", 86 | "Loading files/028C4_xy 0.524008549197\n", 87 | "Loading files/028C4_xy2 0.520032391356\n", 88 | "Loading files/028C4_xz 0.531481162305\n", 89 | "Loading files/S0405_xy 0.696476898495\n", 90 | "Loading files/S0405_yz 0.663717074804\n", 91 | "Loading files/s0406_yz 0.642037405143\n", 92 | "Loading files/013C4_xz_001-140 0.593726977975\n", 93 | "Loading files/013C4_xz_053-193 0.59717290857\n" 94 | ] 95 | } 96 | ], 97 | "source": [ 98 | "bufsize = 0\n", 99 | "with open('cubeness.txt', 'w', bufsize) as log:\n", 100 | " files = glob.glob('files/*')\n", 101 | " for f in files:\n", 102 | " print 'Loading ',f,\n", 103 | " volume = loadVolume(f)\n", 104 | " X,Y,Z = volume.nonzero()\n", 105 | " c,fxy = cubeness_mz(X,Y,Z)\n", 106 | " print c\n", 107 | " log.write(f + ',' + str(c)+'\\n')" 108 | ] 109 | } 110 | ], 111 | "metadata": { 112 | "kernelspec": { 113 | "display_name": "IPython (Python 2)", 114 | "name": "python2" 115 | }, 116 | "language_info": { 117 | "codemirror_mode": { 118 | "name": "ipython", 119 | "version": 2 120 | }, 121 | "file_extension": ".py", 122 | "mimetype": "text/x-python", 123 | "name": "python", 124 | "nbconvert_exporter": "python", 125 | "pygments_lexer": "ipython2", 126 | "version": "2.7.6" 127 | }, 128 | "signature": "sha256:c9868fa2b90054e9957f5465a99163c839c23c3ea513c6039b0abd50b1c8d078" 129 | }, 130 | "nbformat": 4, 131 | "nbformat_minor": 0 132 | } -------------------------------------------------------------------------------- /codemeta.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "https://doi.org/10.5063/schema/codemeta-2.0", 3 | "@type": "SoftwareSourceCode", 4 | "license": "https://spdx.org/licenses/Apache-2.0", 5 | "codeRepository": "https://github.com/c-martinez/pydescriptors", 6 | "dateCreated": "2015-02-04T22:03:09Z", 7 | "dateModified": "2022-08-17T15:40:44.296Z", 8 | "issueTracker": "https://github.com/c-martinez/pydescriptors/issues", 9 | "name": "PyDescriptors", 10 | "description": "Python implementation of various 2D and 3D compactness measures.", 11 | "referencePublication": "http://hdl.handle.net/10036/3026", 12 | "keywords": [ 13 | "Shape descriptor", 14 | "Compactness" 15 | ], 16 | "programmingLanguage": [ 17 | "Python", 18 | "Jupyter Notebook" 19 | ], 20 | "author": [ 21 | { 22 | "@type": "Person", 23 | "@id": "https://orcid.org/0000-0001-5565-7577", 24 | "givenName": "Carlos", 25 | "familyName": "Martinez Ortiz", 26 | "affiliation": { 27 | "@type": "Organization", 28 | "name": "eScience Center" 29 | } 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /pydescriptors/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ['compactness', 'cubeness', 'helpers', 'moments'] 2 | -------------------------------------------------------------------------------- /pydescriptors/compactness.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | 3 | from .moments import immoment3D as _immoment3D 4 | from .helpers import recenter as _recenter 5 | 6 | import numpy as _np 7 | 8 | 9 | def bribiesca(X, Y, Z): 10 | assert X.shape[0] == Y.shape[0] 11 | assert Y.shape[0] == Z.shape[0] 12 | 13 | n = X.shape[0] 14 | A = _findArea(X, Y, Z) 15 | c = (n - (A / 6)) / (n - n ** (2 / 3)) 16 | return c 17 | 18 | 19 | def _findArea(X, Y, Z): 20 | voxels = set() 21 | for x, y, z in zip(X, Y, Z): 22 | voxels.add((x, y, z)) 23 | 24 | neighbours = [_np.array([-1, 0, 0]), _np.array([1, 0, 0]), 25 | _np.array([0, -1, 0]), _np.array([0, 1, 0]), 26 | _np.array([0, 0, -1]), _np.array([0, 0, 1])] 27 | A = 0 28 | # For each voxel 29 | for x, y, z in zip(X, Y, Z): 30 | # Check its 6 possible neighbours 31 | vox = _np.asarray(x, y, z) 32 | for neigh in neighbours: 33 | vNeigh = vox + neigh 34 | # If neighbour is there, contribute 0 area 35 | # Otherwise, contribute 1 36 | A += 0 if tuple(vNeigh) in voxels else 1 37 | return A 38 | 39 | 40 | def hz(X, Y, Z): 41 | X_, Y_, Z_ = _recenter(X, Y, Z) 42 | 43 | mu000 = _immoment3D(X_, Y_, Z_, 0, 0, 0) 44 | mu200 = _immoment3D(X_, Y_, Z_, 2, 0, 0) 45 | mu020 = _immoment3D(X_, Y_, Z_, 0, 2, 0) 46 | mu002 = _immoment3D(X_, Y_, Z_, 0, 0, 2) 47 | 48 | c = 3 ** (5 / 3) / (5 * ((4 * _np.pi) ** (2 / 3))) * \ 49 | (mu000 ** (5 / 3) / (mu200 + mu020 + mu002)) 50 | return c 51 | 52 | 53 | def hzweighted(X, Y, Z, beta): 54 | X_, Y_, Z_ = _recenter(X, Y, Z) 55 | 56 | mu000 = _immoment3D(X_, Y_, Z_, 0, 0, 0) 57 | 58 | # int int int ( x^2 + y^2 +z^2 ) ^ beta dx dy dz 59 | tri_integral = _triintegral(X_, Y_, Z_, beta) 60 | 61 | if beta >= 0: 62 | # c = (3 / ( 2 * beta + 3)) * ... 63 | # (( 3 / (4 * pi)) ^ ((2 * beta) / 3)) * ... 64 | # ((mu000 ^ ((2*beta + 3) / 3)) / tri_integral); 65 | const1 = 3 / (2 * beta + 3) 66 | const2 = (3 / (4 * _np.pi)) ** ((2 * beta) / 3) 67 | numera = mu000 ** ((2 * beta + 3) / 3) 68 | divis = tri_integral 69 | elif -1.5 < beta and beta < 0: 70 | # c = ( ( 2 * beta + 3) / 3 ) * ... 71 | # ( ( (4 * pi) / 3 ) ^ ( (2 * beta) / 3)) * ... 72 | # ( tri_integral / ( mu000 ^ ((2 * beta + 3) / 3) ) ); 73 | const1 = (2 * beta + 3) / 3 74 | const2 = ((4 * _np.pi) / 3) ** ((2 * beta) / 3) 75 | numera = tri_integral 76 | divis = mu000 ** ((2 * beta + 3) / 3) 77 | else: 78 | const1 = const2 = numera = 0 79 | divis = 1 80 | 81 | c = const1 * const2 * (numera / divis) 82 | return c 83 | 84 | 85 | def _triintegral(X_, Y_, Z_, beta): 86 | xyz_beta = (X_ ** 2 + Y_ ** 2 + Z_ ** 2) ** beta 87 | return xyz_beta.sum() 88 | -------------------------------------------------------------------------------- /pydescriptors/cubeness.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | 3 | import numpy as _np 4 | # from numpy.testing import assert_almost_equal 5 | from .moments import immoment3D as _immoment3D 6 | from .helpers import rotate3D as _rotate3D 7 | from .helpers import recenter as _recenter 8 | 9 | 10 | def _int_max_abs(X, Y, Z): 11 | """Calculate the triple integral maximum absolute value of a given 3D object. 12 | 13 | Keyword arguments: 14 | X -- The X coordinate of the voxels. 15 | Y -- The Y coordinate of the voxels. 16 | Z -- The Z coordinate of the voxels. 17 | 18 | Returns: 19 | The value of the integral. 20 | """ 21 | # \iiint max(|x|,|y|,|z|) dx dy dz 22 | # X, Y, Z must be centred 23 | XYZ = _np.vstack([X, Y, Z]) 24 | max_abs = _np.absolute(XYZ).max(axis=0) 25 | sss = max_abs.sum() 26 | return sss 27 | 28 | 29 | def _int_max_abs_beta(X, Y, Z, beta): 30 | """. 31 | """ 32 | # \iiint max(|x|,|y|,|z|) dx dy dz 33 | # X, Y, Z must be centred 34 | XYZ = _np.vstack([X, Y, Z]).astype(_np.double) 35 | max_abs = _np.absolute(XYZ).max(axis=0) 36 | max_abs_beta = max_abs[max_abs.nonzero()] ** beta 37 | 38 | sss = max_abs_beta.sum() 39 | return sss 40 | 41 | 42 | def mz(X, Y, Z, nSteps=100): 43 | """Computes the cubeness of a 3D object by method presented in 44 | paper: Measuring Cubeness of 3D Shapes by Martinez-Ortiz and Zunic. 45 | 46 | Keyword arguments: 47 | X -- The X coordinate of the voxels. 48 | Y -- The Y coordinate of the voxels. 49 | Z -- The Z coordinate of the voxels. 50 | 51 | Returns: 52 | c -- The cubeness_mz value of the given object. 53 | """ 54 | X_, Y_, Z_ = _recenter(X, Y, Z) 55 | 56 | # TODO: assert centorid is close to 0,0,0 ? 57 | # assert_almost_equal(_immoment3D(X_,Y_,Z_,1,0,0),0,4) # X 58 | # assert_almost_equal(_immoment3D(X_,Y_,Z_,0,1,0),0,4) # Y 59 | # assert_almost_equal(_immoment3D(X_,Y_,Z_,0,0,1),0,4) # Z 60 | 61 | angles = _np.linspace(0, _np.pi, nSteps) 62 | fxy = _np.zeros((nSteps, nSteps)) 63 | # TODO: Pythonize these for loops (at the cost of readability?) 64 | for i, rx in enumerate(angles): 65 | for j, ry in enumerate(angles): 66 | Xr, Yr, Zr = _rotate3D(X_, Y_, Z_, rx, ry) 67 | fxy[i, j] = _int_max_abs(Xr, Yr, Zr) 68 | 69 | vol = _immoment3D(X, Y, Z, 0, 0, 0) 70 | min_fxy = fxy.min() 71 | c = (3 / 8) * vol ** (4 / 3) / min_fxy 72 | return c 73 | 74 | 75 | def fit(X, Y, Z, nSteps=100): 76 | X_, Y_, Z_ = _recenter(X, Y, Z) 77 | X_ = _np.floor(X_) 78 | Y_ = _np.floor(Y_) 79 | Z_ = _np.floor(Z_) 80 | 81 | # Create fit cube of same volume as object 82 | vol_s = _immoment3D(X, Y, Z, 0, 0, 0) 83 | a_s_2 = int(_np.round(vol_s ** (1 / 3))) # Side of cube with equivalent volume 84 | b = _np.ones((a_s_2, a_s_2, a_s_2)) 85 | 86 | bX, bY, bZ = b.nonzero() 87 | bX, bY, bZ = _recenter(bX, bY, bZ) 88 | 89 | XYZ_set = set([(x, y, z) for x, y, z in zip(X_, Y_, Z_)]) 90 | 91 | angles = _np.linspace(0, _np.pi, nSteps) 92 | fxy = _np.zeros((nSteps, nSteps)) 93 | # TODO: Pythonize these for loops (at the cost of readability?) 94 | for i, rx in enumerate(angles): 95 | for j, ry in enumerate(angles): 96 | bXr, bYr, bZr = _rotate3D(bX, bY, bZ, rx, ry) 97 | bXr = _np.floor(bXr) 98 | bYr = _np.floor(bYr) 99 | bZr = _np.floor(bZr) 100 | 101 | br_set = set([(x, y, z) for x, y, z in zip(bXr, bYr, bZr)]) 102 | 103 | uXYZ = br_set.union(XYZ_set) 104 | iXYZ = br_set.intersection(XYZ_set) 105 | 106 | uXYZ = _np.array([_np.array([x, y, z]) for x, y, z in uXYZ]) 107 | iXYZ = _np.array([_np.array([x, y, z]) for x, y, z in iXYZ]) 108 | 109 | if len(iXYZ) != 0: 110 | vol_u = _immoment3D(uXYZ[:, 0], uXYZ[:, 1], uXYZ[:, 2], 0, 0, 0) 111 | vol_i = _immoment3D(iXYZ[:, 0], iXYZ[:, 1], iXYZ[:, 2], 0, 0, 0) 112 | 113 | fxy[i, j] = vol_i / vol_u 114 | else: 115 | fxy[i, j] = -1 116 | 117 | c = fxy.max() 118 | return c 119 | 120 | 121 | def mz_beta(X, Y, Z, beta, nSteps=100): 122 | assert(beta > -3) 123 | X_, Y_, Z_ = _recenter(X, Y, Z) 124 | 125 | angles = _np.linspace(0, _np.pi, nSteps) 126 | fxy = _np.zeros((nSteps, nSteps)) 127 | # TODO: Pythonize these for loops (at the cost of readability?) 128 | for i, rx in enumerate(angles): 129 | for j, ry in enumerate(angles): 130 | Xr, Yr, Zr = _rotate3D(X_, Y_, Z_, rx, ry) 131 | fxy[i, j] = _int_max_abs_beta(Xr, Yr, Zr, beta) 132 | 133 | vol = _immoment3D(X, Y, Z, 0, 0, 0) 134 | if beta >= 0: 135 | c = 3 / ((2 ** beta) * (beta + 3)) * \ 136 | vol ** ((beta + 3) / 3) / fxy.min() 137 | else: 138 | c = ((2 ** beta) * (beta + 3)) / \ 139 | 3 * fxy.min() / vol ** ((beta + 3) / 3) 140 | return c 141 | -------------------------------------------------------------------------------- /pydescriptors/helpers.py: -------------------------------------------------------------------------------- 1 | import numpy as _np 2 | 3 | from .moments import immoment3D as _immoment3D 4 | 5 | def getSphere(side): 6 | """Create a 3D volume of sideXsideXside, where voxels representing a 7 | sphere are ones and background is zeros. 8 | 9 | Keyword arguments: 10 | side -- the number of voxels the 3D volume should have on each side. 11 | 12 | Returns: 13 | A (side,side,side) shaped matrix of zeros and ones. 14 | """ 15 | volume = _np.zeros((side, side, side)) 16 | r = side / 2 17 | Xs, Ys = _np.meshgrid(_np.arange(-r, r), _np.arange(-r, r)) 18 | for k, z in enumerate(_np.arange(-r, r)): 19 | volume[:, :, k] = _np.sqrt(Xs ** 2 + Ys ** 2 + z ** 2) < r 20 | return volume 21 | 22 | 23 | def rotate3D(X, Y, Z, rx, ry): 24 | """Rotates a 3D object along one ordinate axis at a time. 25 | 26 | Keyword arguments: 27 | X -- The X coordinate of the voxels to be rotated. 28 | Y -- The Y coordinate of the voxels to be rotated. 29 | Z -- The Z coordinate of the voxels to be rotated. 30 | 31 | Returns: 32 | X,Y,Z coordinates of the rotated voxels. 33 | """ 34 | R = _np.eye(3) 35 | Rx = _np.array([[1, 0, 0], 36 | [0, _np.cos(rx), -_np.sin(rx)], 37 | [0, _np.sin(rx), _np.cos(rx)]]) 38 | Ry = _np.array([[_np.cos(ry), 0, _np.sin(ry)], 39 | [0, 1, 0], 40 | [-_np.sin(ry), 0, _np.cos(ry)]]) 41 | R = _np.dot(R, Rx) 42 | R = _np.dot(R, Ry) 43 | 44 | XYZ = _np.vstack([X, Y, Z]) 45 | XYZ_ = _np.dot(XYZ.T, R) 46 | 47 | return XYZ_[:, 0], XYZ_[:, 1], XYZ_[:, 2] 48 | 49 | 50 | def recenter(X, Y, Z): 51 | # TODO: Document, write unit test 52 | m000 = _immoment3D(X, Y, Z, 0, 0, 0) 53 | m100 = _immoment3D(X, Y, Z, 1, 0, 0) 54 | m010 = _immoment3D(X, Y, Z, 0, 1, 0) 55 | m001 = _immoment3D(X, Y, Z, 0, 0, 1) 56 | 57 | # Find centroid 58 | cx = m100 / m000 59 | cy = m010 / m000 60 | cz = m001 / m000 61 | 62 | # Recentering 63 | X_ = X - cx 64 | Y_ = Y - cy 65 | Z_ = Z - cz 66 | 67 | return X_, Y_, Z_ 68 | -------------------------------------------------------------------------------- /pydescriptors/moments.py: -------------------------------------------------------------------------------- 1 | def immoment3D(X, Y, Z, p, q, r): 2 | """Compute the P,Q,R moments of a 3D image. 3 | 4 | Keyword arguments: 5 | X -- The X coordinates of voxels 6 | Y -- The Y coordinates of voxels 7 | Z -- The Z coordinates of voxels 8 | P -- The power used for the X-axis 9 | Q -- The power used for the Y-axis 10 | R -- The power used for the Z-axis 11 | 12 | Returns: 13 | The numeric value of the PQR moment. 14 | """ 15 | assert len(X) == len(Y) 16 | assert len(Y) == len(Z) 17 | return (X ** p * Y ** q * Z ** r).sum() 18 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | _major = 0 4 | _minor = 1 5 | _revision = 2 6 | 7 | _version = "%d.%d.%d" % (_major, _minor, _revision) 8 | 9 | setup( 10 | name="pydescriptors", 11 | description="Implementation of various 2D and 3D compactness measures " + 12 | "described in 2D and 3D Shape Descriptors.", 13 | url="https://github.com/c-martinez/compactness", 14 | version=_version, 15 | packages=["pydescriptors"], 16 | package_dir={"pydescriptors": "pydescriptors"}, 17 | classifiers=[ 18 | "Intended Audience :: Science/Research", 19 | "Topic :: Scientific/Engineering", 20 | "Topic :: Image processing", 21 | ], 22 | install_requires=['numpy'] 23 | ) 24 | 25 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c-martinez/pydescriptors/02dc51d069dba9c53ef27379c70f3dd7853ab57d/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_cubeness.py: -------------------------------------------------------------------------------- 1 | from pydescriptors.cubeness import mz as cubeness_mz 2 | from pydescriptors.cubeness import fit as cubeness_fit 3 | from pydescriptors.helpers import getSphere 4 | 5 | import numpy as np 6 | 7 | from numpy.testing import assert_almost_equal 8 | 9 | _testDiameter = 10 10 | 11 | def test_cubenessMZ_cube(): 12 | """Test the cubeness_mz of a cube. 13 | """ 14 | volume = np.ones((_testDiameter, _testDiameter, _testDiameter)) 15 | X, Y, Z = volume.nonzero() 16 | 17 | c = cubeness_mz(X, Y, Z) 18 | 19 | # More precision requires more computation -- for unit test this is enough 20 | assert_almost_equal(c, 1.000, 2) 21 | 22 | 23 | def test_cubenessMZ_sphere(): 24 | """Test the cubeness_mz of a sphere. 25 | """ 26 | volume = getSphere(_testDiameter) 27 | X, Y, Z = volume.nonzero() 28 | 29 | c = cubeness_mz(X, Y, Z) 30 | 31 | # More precision requires more computation -- for unit test this is enough 32 | assert_almost_equal(c, 0.971, 2) 33 | 34 | 35 | def test_cubenessFit_cube(): 36 | """Test the cubeness_fit of a cube. 37 | """ 38 | volume = np.ones((_testDiameter, _testDiameter, _testDiameter)) 39 | X, Y, Z = volume.nonzero() 40 | 41 | c = cubeness_fit(X, Y, Z) 42 | 43 | # More precision requires more computation -- for unit test this is enough 44 | assert_almost_equal(c, 1.000, 2) 45 | 46 | 47 | def test_cubenessFit_sphere(): 48 | """Test the cubeness_fit of a sphere. 49 | """ 50 | volume = getSphere(_testDiameter) 51 | X, Y, Z = volume.nonzero() 52 | 53 | c = cubeness_fit(X, Y, Z) 54 | 55 | # More precision requires more computation -- for unit test this is enough 56 | assert_almost_equal(c, 0.723, 2) 57 | 58 | 59 | def xtest_cubenessMZ_beta_cube(): 60 | assert 1==0, "Not yet implemented" 61 | 62 | 63 | def xtest_cubenessMZ_beta_sphere(): 64 | assert 1==0, "Not yet implemented" 65 | -------------------------------------------------------------------------------- /tests/test_moments.py: -------------------------------------------------------------------------------- 1 | from pydescriptors.moments import immoment3D 2 | from pydescriptors.helpers import getSphere 3 | 4 | _testDiameter = 10 5 | 6 | def test_moment000(): 7 | """Test the 0-th order moment. For any given shape, this should be the same 8 | as the volume of the object. 9 | """ 10 | volume = getSphere(_testDiameter) 11 | X, Y, Z = volume.nonzero() 12 | 13 | # Moment 0,0,0 is the volume of the object 14 | assert volume.sum() == immoment3D(X, Y, Z, 0, 0, 0) 15 | assert len(X) == immoment3D(X, Y, Z, 0, 0, 0) 16 | 17 | 18 | def test_recentering(): 19 | """Test calculating the centroid of an object. 20 | """ 21 | volume = getSphere(_testDiameter) 22 | X, Y, Z = volume.nonzero() 23 | 24 | m000 = immoment3D(X, Y, Z, 0, 0, 0) 25 | m100 = immoment3D(X, Y, Z, 1, 0, 0) 26 | m010 = immoment3D(X, Y, Z, 0, 1, 0) 27 | m001 = immoment3D(X, Y, Z, 0, 0, 1) 28 | 29 | # recentering 30 | X = X - (m100 / m000) 31 | Y = Y - (m010 / m000) 32 | Z = Z - (m001 / m000) 33 | 34 | # After recentering, object should be located at 0,0,0 35 | assert immoment3D(X, Y, Z, 1, 0, 0) == 0 36 | assert immoment3D(X, Y, Z, 0, 1, 0) == 0 37 | assert immoment3D(X, Y, Z, 0, 0, 1) == 0 38 | --------------------------------------------------------------------------------