├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── dt.js ├── example ├── example.js └── example.png ├── lib ├── p1.js ├── p2.js ├── pinf.js └── pp.js ├── package.json └── test └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules/* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | - "0.10" 5 | before_install: 6 | - npm install -g npm@~1.4.6 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2013 Mikola Lysenko 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | distance-transform 2 | ================== 3 | [Distance transforms](http://en.wikipedia.org/wiki/Distance_transform) for [Lp metrics](https://en.wikipedia.org/wiki/Lp_space) on binary [ndarrays](https://github.com/mikolalysenko/ndarray). This code is based on Meijster's algorithm. For more information see: 4 | 5 | * https://github.com/parmanoir/Meijster-distance 6 | * http://dissertations.ub.rug.nl/FILES/faculties/science/2004/a.meijster/c2.pdf 7 | 8 | [![build status](https://secure.travis-ci.org/scijs/distance-transform.png)](http://travis-ci.org/scijs/distance-transform) 9 | 10 | ## Example 11 | 12 | ```javascript 13 | //Generate some shape as a binary voxel image 14 | var x = require("zeros")([256, 256]) 15 | x.set(128, 128, 1) 16 | 17 | //Distance transform x in the Euclidean metric 18 | require("distance-transform")(x) 19 | 20 | //Save result 21 | require("save-pixels")(x, "png").pipe(process.stdout) 22 | ``` 23 | 24 | #### Output 25 | 26 | 27 | 28 | ## Install 29 | Install using [npm](https://www.npmjs.com/): 30 | 31 | npm install distance-transform 32 | 33 | ## API 34 | #### `require("distance-transform")(array[, p])` 35 | Performs a distance transform of `array` in place using Meijster's algorithm. 36 | 37 | * `array` is the array to transform 38 | * `p` is the exponent for the metric. (Default 2) 39 | 40 | For different values of p you get different transforms 41 | 42 | * `p = 1` gives the Manhattan/taxicab distance metric 43 | * `p = 2` gives the Euclidean distance metric 44 | * `p = Infinity` gives the Chebyshev/chessboard distance metric 45 | * Other values of p give various interpolants 46 | 47 | `array` is updated in place and gets the distance values. 48 | 49 | ## License 50 | (c) 2013 Mikola Lysenko. MIT License. 51 | -------------------------------------------------------------------------------- /dt.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var ndarray = require("ndarray") 4 | var cwise = require("cwise") 5 | var ops = require("ndarray-ops") 6 | var pool = require("typedarray-pool") 7 | 8 | var phase2_1 = require("./lib/p1.js") 9 | var phase2_2 = require("./lib/p2.js") 10 | var phase2_inf = require("./lib/pinf.js") 11 | var phase2_p = require("./lib/pp.js") 12 | 13 | var binarize = cwise({ 14 | args: ["array", "array", "scalar"], 15 | body: function(out, a, inf) { 16 | out = a ? 0 : inf 17 | } 18 | }) 19 | 20 | function phase1(array, nrows, ncols) { 21 | var i, j, ptr=0, d, min = Math.min 22 | for(i=0; i=0; --i) { 46 | stride[i] = size 47 | size *= shape[i] 48 | inf += shape[i] 49 | stack_size = Math.max(stack_size, shape[i]) 50 | } 51 | 52 | //Allocate scratch buffers 53 | var b0_t = pool.mallocDouble(size) 54 | , b0 = ndarray(b0_t, shape.slice(0), stride.slice(0), 0) 55 | , b1_t = pool.mallocDouble(size) 56 | , b1 = ndarray(b1_t, shape.slice(0), stride.slice(0), 0) 57 | , s_q = pool.mallocUint32(stack_size) 58 | , t_q = pool.mallocUint32(stack_size) 59 | 60 | //Perform first phase of distance transform 61 | binarize(b0, array, inf) 62 | phase1(b0.data, (size/shape[d-1])|0, shape[d-1]|0) 63 | 64 | //Second passes 65 | for(i=d-1; i>0; --i) { 66 | s = b1.stride 67 | n = 1 68 | for(j=i-1; j=0; --j) { 73 | s[j] = n 74 | n *= shape[j] 75 | } 76 | ops.assign(b1, b0) 77 | 78 | //Execute phase 2 79 | m = shape[i-1] 80 | if(!isFinite(p)) { 81 | phase2_inf(b1.data, (size/m)|0, m|0, s_q, t_q) 82 | } else if(p === 1) { 83 | phase2_1(b1.data, (size/m)|0, m|0, s_q, t_q) 84 | } else if(p === 2) { 85 | phase2_2(b1.data, (size/m)|0, m|0, s_q, t_q) 86 | } else if(p < 1) { 87 | throw new Error("Values of p < 1 are not supported") 88 | } else { 89 | phase2_p(b1.data, (size/m)|0, m|0, s_q, t_q, p) 90 | } 91 | 92 | //Swap buffers 93 | tmp = b0 94 | b0 = b1 95 | b1 = tmp 96 | } 97 | 98 | //Copy b0 to result 99 | ops.assign(array, b0) 100 | 101 | pool.freeDouble(b0_t) 102 | pool.freeDouble(b1_t) 103 | pool.freeUint32(s_q) 104 | pool.freeUint32(t_q) 105 | return array 106 | } -------------------------------------------------------------------------------- /example/example.js: -------------------------------------------------------------------------------- 1 | //Generate some shape as a binary voxel image 2 | var x = require("zeros")([256, 256]) 3 | x.set(128, 128, 1) 4 | 5 | //Distance transform 6 | require("../dt.js")(x) 7 | 8 | //Save result 9 | require("save-pixels")(x, "png").pipe(process.stdout) -------------------------------------------------------------------------------- /example/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scijs/distance-transform/10d2c34f4b7c9184ee9ce8d0309ce7fffacdc752/example/example.png -------------------------------------------------------------------------------- /lib/p1.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var abs = Math.abs 4 | 5 | function dist_1(a, b) { 6 | return abs(a) + abs(b) 7 | } 8 | 9 | function dist_1_sep(i, u, g_i, g_u) { 10 | if (g_u >= (g_i + u - i)) { 11 | return 1<<30 12 | } else if (g_i > (g_u + u - i)) { 13 | return -(1<<30) 14 | } else { 15 | return (g_u - g_i + u + i)>>1 16 | } 17 | } 18 | 19 | module.exports = function phase2_1(array, nrows, ncols, s, t) { 20 | var d, u, v, w, q, y, ptr 21 | t[0] = 0 22 | for(y=0; y= 0 && dist_1(s[q] - t[q], array[ptr+s[q]]) > dist_1(u - t[q], v)) { 31 | --q 32 | } 33 | if(q < 0) { 34 | q = 0 35 | s[0] = u 36 | } else { 37 | w = 1 + dist_1_sep(s[q], u, array[ptr+s[q]], v) 38 | if(w < ncols) { 39 | ++q 40 | s[q] = u 41 | t[q] = w 42 | } 43 | } 44 | } 45 | 46 | //Second pass: fill in lower hull 47 | for(u=ncols-1; u>=0; --u) { 48 | d = dist_1(s[q] - u, array[ptr+s[q]]) 49 | array[u+ptr] = d 50 | if(u === t[q]) { 51 | --q 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /lib/p2.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | function dist_2(a, b) { 4 | return a * a + b * b 5 | } 6 | 7 | function dist_2_sep(i, u, g_i, g_u) { 8 | return ((u*u - i*i + g_u*g_u - g_i*g_i) / (2*(u-i)))|0 9 | } 10 | 11 | module.exports = function phase2_2(array, nrows, ncols, s, t) { 12 | var d, u, v, w, q, y, ptr 13 | t[0] = 0 14 | for(y=0; y= 0 && dist_2(s[q] - t[q], array[ptr+s[q]]) > dist_2(u - t[q], v)) { 23 | --q 24 | } 25 | if(q < 0) { 26 | q = 0 27 | s[0] = u 28 | } else { 29 | w = 1 + dist_2_sep(s[q], u, array[ptr+s[q]], v) 30 | if(w < ncols) { 31 | ++q 32 | s[q] = u 33 | t[q] = w 34 | } 35 | } 36 | } 37 | 38 | //Second pass: fill in lower hull 39 | for(u=ncols-1; u>=0; --u) { 40 | d = Math.sqrt(dist_2(s[q] - u, array[ptr+s[q]])) 41 | array[u+ptr] = d 42 | if(u === t[q]) { 43 | --q 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /lib/pinf.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var abs = Math.abs 4 | var max = Math.max 5 | var min = Math.min 6 | 7 | function dist_inf(a, b) { 8 | return max(abs(a), abs(b)) 9 | } 10 | 11 | function dist_inf_sep(i, u, g_i, g_u) { 12 | if (g_i <= g_u) { 13 | return max(i+g_u, (i+u)>>1) 14 | } else { 15 | return min(u-g_i, (i+u)>>1) 16 | } 17 | } 18 | 19 | module.exports = function phase2_inf(array, nrows, ncols, s, t) { 20 | var d, u, v, w, q, y, ptr 21 | t[0] = 0 22 | for(y=0; y= 0 && dist_inf(s[q] - t[q], array[ptr+s[q]]) > dist_inf(u - t[q], v)) { 31 | --q 32 | } 33 | if(q < 0) { 34 | q = 0 35 | s[0] = u 36 | } else { 37 | w = 1 + dist_inf_sep(s[q], u, array[ptr+s[q]], v) 38 | if(w < ncols) { 39 | ++q 40 | s[q] = u 41 | t[q] = w 42 | } 43 | } 44 | } 45 | 46 | //Second pass: fill in lower hull 47 | for(u=ncols-1; u>=0; --u) { 48 | d = dist_inf(s[q] - u, array[ptr+s[q]]) 49 | array[u+ptr] = d 50 | if(u === t[q]) { 51 | --q 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /lib/pp.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var bisect = require("bisect") 4 | var pow = Math.pow 5 | var abs = Math.abs 6 | var floor = Math.floor 7 | 8 | function dist_p(a, b, p) { 9 | return pow(abs(a), p) + pow(abs(b), p) 10 | } 11 | 12 | module.exports = function phase2_p(array, nrows, ncols, s, t, p) { 13 | var d, u, v, w, q, y, ptr, i, gi, gu 14 | 15 | function f(x) { 16 | return pow(abs(x-i), p) + pow(abs(gi), p) > pow(abs(x-u), p) + pow(abs(gu), p) 17 | } 18 | 19 | //Not super efficient, but good enough for now 20 | function dist_p_sep() { 21 | i = s[q] 22 | gi = array[ptr+i] 23 | gu = v 24 | var t = bisect(f, i, u+1, 0.25) 25 | return Math.floor(t) 26 | } 27 | 28 | t[0] = 0 29 | for(y=0; y= 0 && dist_p(s[q] - t[q], array[ptr+s[q]], p) > dist_p(u - t[q], v, p)) { 38 | --q 39 | } 40 | if(q < 0) { 41 | q = 0 42 | s[0] = u 43 | } else { 44 | w = 1 + dist_p_sep() 45 | if(w < ncols) { 46 | ++q 47 | s[q] = u 48 | t[q] = w 49 | } 50 | } 51 | } 52 | 53 | //Second pass: fill in lower hull 54 | for(u=ncols-1; u>=0; --u) { 55 | d = pow(dist_p(s[q] - u, array[ptr+s[q]], p), 1.0/p) 56 | array[u+ptr] = d 57 | if(u === t[q]) { 58 | --q 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "distance-transform", 3 | "version": "1.0.2", 4 | "description": "Distance transforms for ndarrays", 5 | "main": "dt.js", 6 | "directories": { 7 | "example": "example", 8 | "test": "test" 9 | }, 10 | "browserify": { 11 | "transform": ["cwise"] 12 | }, 13 | "dependencies": { 14 | "bisect": "^1.0.0", 15 | "cwise": "^1.0.6", 16 | "ndarray": "^1.0.18", 17 | "ndarray-ops": "^1.2.2", 18 | "typedarray-pool": "^1.1.0" 19 | }, 20 | "devDependencies": { 21 | "almost-equal": "^1.0.0", 22 | "tape": "^4.0.0", 23 | "save-pixels": "^2.2.0", 24 | "zeros": "^1.0.0" 25 | }, 26 | "scripts": { 27 | "test": "tape test/*.js" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git://github.com/scijs/distance-transform.git" 32 | }, 33 | "keywords": [ 34 | "scijs", 35 | "distance", 36 | "transform", 37 | "euclidean", 38 | "manhattan", 39 | "taxicab", 40 | "morphology", 41 | "chebyshev", 42 | "chessboard", 43 | "lp", 44 | "lebesgue", 45 | "metric" 46 | ], 47 | "author": "Mikola Lysenko", 48 | "license": "MIT", 49 | "readmeFilename": "README.md", 50 | "gitHead": "532a87a93df33505764c51e02656c57d0bb573ab", 51 | "bugs": { 52 | "url": "https://github.com/scijs/distance-transform/issues" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | var ndarray = require("ndarray") 3 | var ops = require("ndarray-ops") 4 | var dt = require("../dt.js") 5 | var zeros = require("zeros") 6 | var almostEqual = require("almost-equal") 7 | 8 | require("tape")("distance-transform", function(t) { 9 | 10 | //1D 11 | var x = zeros([2]) 12 | x.set(0, 1) 13 | dt(x) 14 | t.equals(x.get(0), 0) 15 | t.equals(x.get(1), 1) 16 | 17 | ops.assigns(x, 0) 18 | x.set(0, 1) 19 | dt(x, 1) 20 | t.equals(x.get(0), 0) 21 | t.equals(x.get(1), 1) 22 | 23 | ops.assigns(x, 0) 24 | x.set(0, 1) 25 | dt(x, Infinity) 26 | t.equals(x.get(0), 0) 27 | t.equals(x.get(1), 1) 28 | 29 | ops.assigns(x, 0) 30 | x.set(0, 1) 31 | dt(x, 3) 32 | t.equals(x.get(0), 0) 33 | t.equals(x.get(1), 1) 34 | 35 | 36 | //2D 37 | x = zeros([2, 2]) 38 | x.set(0, 0, 1) 39 | dt(x) 40 | t.equals(x.get(1, 1), Math.sqrt(2.0)) 41 | 42 | ops.assigns(x, 0) 43 | x.set(0, 0, 1) 44 | dt(x, 1) 45 | t.equals(x.get(1, 1), 2) 46 | 47 | ops.assigns(x, 0) 48 | x.set(0, 0, 1) 49 | dt(x, Infinity) 50 | t.equals(x.get(1, 1), 1) 51 | 52 | ops.assigns(x, 0) 53 | x.set(0, 0, 1) 54 | dt(x, 3) 55 | t.equals(x.get(1, 1), Math.pow(2, 1.0/3.0)) 56 | 57 | ops.assigns(x, 0) 58 | x.set(1, 1, 1) 59 | dt(x, 3) 60 | t.equals(x.get(0, 0), Math.pow(2, 1.0/3.0)) 61 | 62 | 63 | 64 | 65 | //3D 66 | x = zeros([2,2,2]) 67 | x.set(0, 0, 0, 1) 68 | dt(x) 69 | t.assert(almostEqual(x.get(1,1,1), Math.sqrt(3.0), almostEqual.FLT_EPSILON, almostEqual.FLT_EPSILON)) 70 | 71 | ops.assigns(x, 0) 72 | x.set(0, 0, 0, 1) 73 | dt(x, 1) 74 | t.equals(x.get(1, 1, 1), 3) 75 | 76 | ops.assigns(x, 0) 77 | x.set(0, 0, 0, 1) 78 | dt(x, Infinity) 79 | t.equals(x.get(1, 1, 1), 1) 80 | 81 | ops.assigns(x, 0) 82 | x.set(0, 0, 0, 1) 83 | dt(x, 3) 84 | t.equals(x.get(1, 1, 1), Math.pow(3, 1.0/3.0)) 85 | 86 | 87 | 88 | 89 | //4D 90 | x = zeros([2,2,2,2]) 91 | x.set(0, 0, 0, 0, 1) 92 | dt(x) 93 | t.equals(x.get(1,1,1,1), 2.0) 94 | 95 | ops.assigns(x, 0) 96 | x.set(0, 0, 0, 0, 1) 97 | dt(x, 1) 98 | t.equals(x.get(1, 1, 1, 1), 4) 99 | 100 | ops.assigns(x, 0) 101 | x.set(0, 0, 0, 0, 1) 102 | dt(x, Infinity) 103 | t.equals(x.get(1, 1, 1, 1), 1) 104 | 105 | ops.assigns(x, 0) 106 | x.set(0, 0, 0, 0, 1) 107 | dt(x, 3) 108 | t.equals(x.get(1, 1, 1, 1), Math.pow(4, 1.0/3.0)) 109 | 110 | 111 | t.end() 112 | }) --------------------------------------------------------------------------------