Skip to content

Commit 998e039

Browse files
committed
new api
1 parent fb53cad commit 998e039

File tree

11 files changed

+1506
-29
lines changed

11 files changed

+1506
-29
lines changed

.eslintrc.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
extends: "react-app",
3+
env: { node: true, browser: true, amd: true, es6: true },
4+
}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ test.txt
1818
test-module
1919
node_modules
2020
callgrind*
21+
module

Makefile

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,18 @@ build/module.js: setup globals.bc
2323
-s ALLOW_MEMORY_GROWTH=1 \
2424
./globals.bc ./colorspace/ColorSpace.bc ./colorspace/Comparison.bc ./colorspace/Conversion.bc
2525

26-
rm -f test-module/module.js
27-
rm -f test-module/node.js
28-
cp build/module.js test-module/module.js
29-
cp build/node.js test-module/node.js
30-
cd test-module && npm version patch && npm publish
26+
rm -rf ./module
27+
mkdir -p ./module
28+
cp build/module.js module/module-wasm-bundle.js
29+
cp build/node.js module/node-wasm-bundle.js
30+
cp package.json ./module/package.json
31+
cp yarn.lock ./module/yarn.lock
32+
cp ./module-files/* ./module
33+
cp ./module-files/node.js ./module/web.js
34+
35+
sed -i 's/node-wasm-bundle/module-wasm-bundle/g' ./module/web.js
36+
37+
# cd module && npm version patch && npm publish
3138

3239

3340
.PHONY: debug_build/module.js
@@ -85,7 +92,7 @@ build/test_min_cut: setup globals.o
8592
clang++ -Ofast -g -o ./build/test_min_cut test_min_cut.cpp globals.o colorspace/Conversion.o colorspace/ColorSpace.o
8693

8794
build/test_polygon_fill: setup globals.o
88-
g++ -g -o ./build/test_polygon_fill test_polygon_fill.cpp globals.o colorspace/Conversion.o colorspace/ColorSpace.o
95+
clang++ -Ofast -g -o ./build/test_polygon_fill test_polygon_fill.cpp globals.o colorspace/Conversion.o colorspace/ColorSpace.o
8996

9097
.PHONY: test_polygon_fill
9198
test_polygon_fill: build/test_polygon_fill

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# autoseg | Automatic Fast WebAssembly Image Segmentation
2+
3+
autoseg segments images from a list of points and polygons containing
4+
classifications. Autoseg was originally created by [Severin Ibarluzea](https://twitter.com/seveibar) for usage with the [Universal Data Tool](https://github.com/UniversalDataTool/universal-data-tool).
5+
6+
## Features
7+
8+
- Simple API
9+
- WebWorker background computation
10+
- Deterministic, suitable for mask compression
11+
- Fast. WebAssembly compiled from state of the art implementations of SLIC superpixeling and graph cut
12+
13+
## API
14+
15+
```javascript
16+
const autoseg = require("autoseg") // OR require("autoseg/node")
17+
18+
await autoseg.loadImage({ data: imData, width: 320, height: 249 })
19+
20+
const maskImageData = await autoseg.getMask([
21+
{ regionType: "point", x: 50, y: 50, cls: 0 },
22+
{ regionType: "point", x: 150, y: 150, cls: 1 },
23+
])
24+
25+
// If you have a canvas, you can draw the image of the mask
26+
canvasContext.putImageData(maskImageData, 0, 0)
27+
28+
// NOTE: nodejs doesn't have builtin support for ImageData, but you
29+
// can use the returned data in a similar way, it's an object with
30+
// { data: Uint8ClampedArray, width: number, height: number }
31+
```

module-files/api.js

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// @flow
2+
3+
module.exports = (WASM_INIT) => {
4+
let wasm = WASM_INIT()
5+
6+
function isWasmLoaded() {
7+
return Boolean(wasm.setImageSize) // any property works
8+
}
9+
10+
const mod = { unresolvedCalls: [] }
11+
12+
async function resolveCalls() {
13+
while (mod.unresolvedCalls.length > 0) {
14+
const { f, args, resolve, reject } = mod.unresolvedCalls[0]
15+
mod.unresolvedCalls.shift()
16+
await f(...args)
17+
.then(resolve)
18+
.catch(reject)
19+
}
20+
}
21+
22+
function eventuallyResolve(f) {
23+
return (...args) => {
24+
if (isWasmLoaded()) {
25+
return resolveCalls().then(() => {
26+
return f(...args)
27+
})
28+
} else {
29+
return new Promise((resolve, reject) => {
30+
mod.unresolvedCalls.push({ f, args, resolve, reject })
31+
})
32+
}
33+
}
34+
}
35+
36+
let checkWasmLoadedInterval = setInterval(() => {
37+
if (isWasmLoaded()) {
38+
clearInterval(checkWasmLoadedInterval)
39+
resolveCalls()
40+
}
41+
}, 100)
42+
43+
mod.defaultConfig = {
44+
mode: "autoseg",
45+
maxClusters: 1000,
46+
classColors: [
47+
0x88000000,
48+
2285257716,
49+
2297665057,
50+
2286989132,
51+
2281729263,
52+
2286441849,
53+
2285412200,
54+
2288197353,
55+
2293245852,
56+
2293584191,
57+
2290652672,
58+
2285493453,
59+
2290842976,
60+
],
61+
classNames: [],
62+
}
63+
mod.config = { ...mod.defaultConfig }
64+
65+
mod.setConfig = eventuallyResolve(async (config) => {
66+
mod.config = { ...mod.defaultConfig, ...config }
67+
})
68+
mod.loadImage = eventuallyResolve(async (imageData) => {
69+
wasm.setSimpleMode(mod.config.mode === "simple")
70+
wasm.setMaxClusters(mod.config.maxClusters)
71+
wasm.setImageSize(imageData.width, imageData.height)
72+
mod.config.imageSize = { width: imageData.width, height: imageData.height }
73+
for (let i = 0; i < mod.config.classColors.length; i++) {
74+
wasm.setClassColor(i, mod.config.classColors[i])
75+
}
76+
const imageAddress = wasm.getImageAddr()
77+
wasm.HEAPU8.set(imageData.data, imageAddress)
78+
wasm.computeSuperPixels()
79+
mod.imageLoaded = true
80+
})
81+
mod.getMask = eventuallyResolve(async (objects) => {
82+
wasm.clearClassElements()
83+
const { width, height } = mod.config.imageSize
84+
// convert bounding boxes to polygons
85+
objects = objects.map((r) => {
86+
if (r.regionType !== "bounding-box") return r
87+
return {
88+
regionType: "polygon",
89+
cls: r.cls,
90+
points: [
91+
{ x: r.x, y: r.y },
92+
{ x: r.x + r.w, y: r.y },
93+
{ x: r.x + r.w, y: r.y + r.h },
94+
{ x: r.x, y: r.y + r.h },
95+
],
96+
}
97+
})
98+
for (let object of objects) {
99+
const clsIndex =
100+
typeof object.cls === "number"
101+
? object.cls
102+
: mod.config.classNames.indexOf(object.cls)
103+
if (clsIndex > mod.config.classColors.length || clsIndex === -1) {
104+
continue
105+
}
106+
107+
switch (object.regionType) {
108+
case "polygon": {
109+
const { points } = object
110+
const pi = wasm.addPolygon(clsIndex)
111+
const pointPairs = points.map((p, i) => [
112+
p,
113+
points[(i + 1) % points.length],
114+
])
115+
for (const [p1, p2] of pointPairs) {
116+
const ri1 = Math.round(p1.y * height)
117+
const ci1 = Math.round(p1.x * width)
118+
const ri2 = Math.round(p2.y * height)
119+
const ci2 = Math.round(p2.x * width)
120+
wasm.addLineToPolygon(pi, ri1, ci1, ri2, ci2)
121+
}
122+
break
123+
}
124+
case "point": {
125+
const { x, y } = object
126+
if (x < 0 || x >= 1) continue
127+
if (y < 0 || y >= 1) continue
128+
129+
wasm.addClassPoint(
130+
clsIndex,
131+
Math.floor(y * mod.config.height),
132+
Math.floor(x * mod.config.width)
133+
)
134+
break
135+
}
136+
default: {
137+
continue
138+
}
139+
}
140+
}
141+
142+
wasm.computeMasks()
143+
const maskAddress = wasm.getColoredMask()
144+
const cppImDataUint8 = new Uint8ClampedArray(
145+
wasm.HEAPU8.buffer,
146+
maskAddress,
147+
width * height * 4
148+
)
149+
150+
if (typeof ImageData !== "undefined") {
151+
// Browser
152+
return new ImageData(cppImDataUint8, width, height)
153+
} else {
154+
// NodeJS
155+
return { data: cppImDataUint8, width, height }
156+
}
157+
})
158+
159+
return mod
160+
}

module-files/node.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// This require doesn't exist until this module is packaged (see Makefile)
2+
const WASM_INIT = require("./node-wasm-bundle")
3+
const api = require("./api")
4+
5+
module.exports = api(WASM_INIT)

module-files/web.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// This require doesn't exist until this module is packaged (see Makefile)
2+
const WASM_INIT = require("./module-wasm-bundle")
3+
const api = require("./api")
4+
5+
module.exports = api(WASM_INIT)

module.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ bool simpleMode = false;
1818

1919
void setImageSize(int w, int h) {
2020
if (!loggedVersion) {
21-
printf("mmgc1_0.0.2\n");
21+
printf("autoseg_0.1\n");
2222
loggedVersion = true;
2323
}
2424
image = std::make_shared<std::vector<uint8_t>>(w * h * 4);
@@ -66,7 +66,9 @@ void computeMasks() {
6666
}
6767
prepareForMinCuts();
6868
for (int i = 0; i < totalClasses; i++) {
69-
printf("running min cut for cls: %d\n", i);
69+
if (verboseMode) {
70+
printf("running min cut for cls: %d\n", i);
71+
}
7072
minCutCls(i);
7173
}
7274
if (verboseMode) {

package.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
{
22
"name": "autoseg",
33
"version": "0.0.3",
4-
"main": "index.js",
4+
"main": "web.js",
55
"repository": "[email protected]:UniversalDataTool/autoseg.git",
66
"author": "seveibar <[email protected]>",
77
"license": "LGPL",
88
"devDependencies": {
9+
"@typescript-eslint/eslint-plugin": "2.x",
10+
"@typescript-eslint/parser": "2.x",
911
"ava": "^3.9.0",
10-
"lodash": "^4.17.15"
12+
"babel-eslint": "10.x",
13+
"eslint": "6.x",
14+
"eslint-config-react-app": "^5.2.1",
15+
"eslint-plugin-flowtype": "4.x",
16+
"eslint-plugin-import": "2.x",
17+
"eslint-plugin-jsx-a11y": "6.x",
18+
"eslint-plugin-react": "7.x",
19+
"eslint-plugin-react-hooks": "2.x",
20+
"lodash": "^4.17.15",
21+
"prettier": "^2.0.5"
1122
}
1223
}

tests/api/api.test.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const test = require("ava")
2+
const fs = require("fs")
3+
const path = require("path")
4+
const WASM_INIT = require("../../build/node.js")
5+
const autoseg = require("../../module-files/api.js")(WASM_INIT)
6+
7+
const f = (p) => path.join(__dirname, p)
8+
9+
const imData = Uint8Array.from(
10+
fs.readFileSync(f("../../assets/orange-320x249.bin"))
11+
)
12+
13+
test("use api to load ", async (t) => {
14+
// console.log(autoseg)
15+
// console.log(imData.length)
16+
await autoseg.loadImage({ data: imData, width: 320, height: 249 })
17+
18+
const result = await autoseg.getMask([
19+
{ regionType: "point", x: 50, y: 50, cls: 0 },
20+
{ regionType: "point", x: 150, y: 150, cls: 1 },
21+
])
22+
23+
t.assert(result)
24+
t.assert(result.data.length === 320 * 249 * 4)
25+
})

0 commit comments

Comments
 (0)