├── .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 | [](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 | })
--------------------------------------------------------------------------------