Skip to content

Commit 932fdd1

Browse files
committed
feat: first version of image-augment
1 parent 310fa63 commit 932fdd1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1241
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
package-lock.json
3+
.DS_Store
4+
.nyc

.travis.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
language: node_js
2+
node_js:
3+
- '8'
4+
install:
5+
- npm install -g codecov nyc npx
6+
- npm install
7+
8+
script:
9+
- npm run lint
10+
- npm test
11+
12+
after_success:
13+
- npm run report-coverage
14+
15+
deploy:
16+
provider: script
17+
skip_cleanup: true
18+
script:
19+
- npm run semantic-release

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,45 @@
11
# image-augment
2+
3+
This library has been freely inspired from [imgaug](https://github.com/aleju/imgaug)
4+
5+
It is made to work with [hasard](https://www.npmjs.com/package/hasard)
6+
7+
## Simple Example
8+
9+
```javascript
10+
const ia = require('image-augment');
11+
12+
// random example images
13+
const images = new Array(50).fill(1).map( () => {
14+
return new ia.RandomImage({
15+
width: 128
16+
height: 128
17+
});
18+
};
19+
20+
const sometimes = ((aug) => ia.sometimes({p : 0.5, augmenter: aug}))
21+
22+
const seq = new ia.Sequential({
23+
sequence : [
24+
new ia.Fliplr({p: 0.5}),
25+
new ia.Flipud({p: 0.5}),
26+
sometimes(new ia.CropAndPad(
27+
p:[-0.05, 0.1],
28+
padMode:ia.ALL,
29+
padCval: [0, 255]
30+
)),
31+
sometimes(new ia.Affine({
32+
scale:{"x": [0.8, 1.2], "y": [0.8, 1.2]},// scale images to 80-120% of their size, individually per axis
33+
translate_percent:{"x": [-0.2, 0.2], "y": [-0.2, 0.2]}, // translate by -20 to +20 percent (per axis)
34+
rotate:[-45, 45], // rotate by -45 to +45 degrees
35+
shear:[-16, 16], // shear by -16 to +16 degrees
36+
order:[0, 1], // use nearest neighbour or bilinear interpolation (fast)
37+
cval:[0, 255], // if mode is constant, use a cval between 0 and 255
38+
mode:ia.ALL // use any of scikit-image's warping modes (see 2nd image from the top for examples)
39+
))
40+
],
41+
randomOrder : true
42+
})
43+
44+
augmented = seq.augmentImagesAsync(images)
45+
```

doc/HEADER.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
[![Build Status](https://travis-ci.com/piercus/image-augment.svg?branch=master)](https://travis-ci.com/piercus/image-augment)
2+
3+
[![codecov](https://codecov.io/gh/piercus/image-augment/branch/master/graph/badge.svg)](https://codecov.io/gh/piercus/image-augment)
4+
5+
## Installation
6+
7+
```
8+
npm install image-augment
9+
```
10+
11+
## Description
12+
13+
Image augmentation library for machinie learning in javascript.
14+
15+
This library has been freely inspired from [imgaug](https://github.com/aleju/imgaug)
16+
17+
It is using [hasard](https://www.npmjs.com/package/hasard) for random variable manipulation.
18+
Currently the only backend available is [opencv4nodejs](https://github.com/justadudewhohacks/opencv4nodejs).
19+
20+
Future work :
21+
* add more augmenters
22+
* make it work in the browser with tensorflowjs (without opencv4nodejs)
23+
24+
Please open issues if you want a specific augmenter/backend compatibilty
25+
26+
## Simple Usage
27+
28+
```javascript
29+
const ia = require('image-augment');
30+
const cv = require('opencv4nodejs');
31+
32+
const image = cv.readImage('lena.png');
33+
34+
const blurred = ia.blur({ kernel: 3 }).runOnce({image: img})
35+
36+
cv.writeImage('lena-blurred-3x3.png');
37+
```
38+
| Input | Output |
39+
|---|---|
40+
| <img src="./test/data/lena.png"/> | <img src="./test/data/lena-blurred-3x3.png"/> |
41+
42+
## Simple Usage with a random variable
43+
44+
All parameters can be set as random variables using [hasard](https://www.npmjs.com/package/hasard) library
45+
46+
```javascript
47+
const ia = require('image-augment');
48+
const hasard = require('hasard');
49+
const cv = require('opencv4nodejs');
50+
51+
const image = cv.readImage('filename.png');
52+
53+
const blurred = ia.blur({ kernel: h.integer([0,10]) }).run({image: img, number : 5})
54+
55+
cv.writeImage('output.png');
56+
```
57+
58+
| Input | Output |
59+
|---|---|
60+
| <img src="./test/data/lena.png"/> | <img src="./test/data/lena-blurred-3x3.png"/> |
61+
62+
63+
This library has been freely inspired from [imgaug](https://github.com/aleju/imgaug)
64+
65+
It is made to work with [hasard](https://www.npmjs.com/package/hasard) and [opencv4nodejs](https://github.com/justadudewhohacks/opencv4nodejs)
66+
67+
# image-augment
68+
69+
70+
## Simple Example
71+
72+
```javascript
73+
const ia = require('image-augment');
74+
75+
// random example images
76+
const images = new Array(50).fill(1).map( () => {
77+
return new ia.RandomImage({
78+
width: 128
79+
height: 128
80+
});
81+
};
82+
83+
const sometimes = ((aug) => ia.sometimes({p : 0.5, augmenter: aug}))
84+
85+
const seq = new ia.Sequential({
86+
sequence : [
87+
new ia.Fliplr({p: 0.5}),
88+
new ia.Flipud({p: 0.5}),
89+
sometimes(new ia.CropAndPad(
90+
p:[-0.05, 0.1],
91+
padMode:ia.ALL,
92+
padCval: [0, 255]
93+
)),
94+
sometimes(new ia.Affine({
95+
scale:{"x": [0.8, 1.2], "y": [0.8, 1.2]},// scale images to 80-120% of their size, individually per axis
96+
translate_percent:{"x": [-0.2, 0.2], "y": [-0.2, 0.2]}, // translate by -20 to +20 percent (per axis)
97+
rotate:[-45, 45], // rotate by -45 to +45 degrees
98+
shear:[-16, 16], // shear by -16 to +16 degrees
99+
order:[0, 1], // use nearest neighbour or bilinear interpolation (fast)
100+
cval:[0, 255], // if mode is constant, use a cval between 0 and 255
101+
mode:ia.ALL // use any of scikit-image's warping modes (see 2nd image from the top for examples)
102+
))
103+
],
104+
randomOrder : true
105+
})
106+
107+
augmented = seq.augmentImagesAsync(images)
108+
```

lib/augmenters/abstract.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
const hasard = require('hasard');
2+
const backends = require('../backend');
3+
4+
class AbstractAugmenter {
5+
constructor(opts) {
6+
this.backend = opts.backend ? backends.get(opts.backend) : backends.getDefault();
7+
}
8+
9+
runOnce(o) {
10+
const metadata = this.backend.getMetadata(o.img);
11+
const o2 = Object.assign({}, {boxes: [], points: []}, metadata, o);
12+
const params = this.buildParams(o);
13+
const resolved = params.runOnce();
14+
return this.augment(o2, resolved);
15+
}
16+
17+
toSize2(opt) {
18+
const fn = function (size) {
19+
if (typeof (size) === 'number') {
20+
return [size, size];
21+
}
22+
23+
if (Array.isArray(size)) {
24+
if (opt.length === 2) {
25+
return size;
26+
}
27+
28+
throw new Error(`${size} whould be a length-2 array or a number`);
29+
}
30+
};
31+
32+
if (hasard.isHasard(opt)) {
33+
return hasard.fn(fn)(opt);
34+
}
35+
36+
return fn(opt);
37+
}
38+
39+
toSize4(opt) {
40+
const fn = function (size) {
41+
if (typeof (size) === 'number') {
42+
return [size, size, size, size];
43+
}
44+
45+
if (Array.isArray(size)) {
46+
if (opt.length === 2) {
47+
return [size[0], size[0], size[1], size[1]];
48+
}
49+
50+
if (opt.length === 4) {
51+
return size;
52+
}
53+
54+
throw new Error(`${size} whould be a number, a length-2 or a lenght-4 array`);
55+
}
56+
};
57+
58+
if (hasard.isHasard(opt)) {
59+
return hasard.fn(fn)(opt);
60+
}
61+
62+
return fn(opt);
63+
}
64+
65+
/**
66+
* @param {PipedImageAttribute} imgAttributes
67+
* @param {AugmenterRandomProperties} augmenterRandomProperties
68+
* @returns {PipedImageAttribute}
69+
*/
70+
augment(attr, opts) {
71+
return {
72+
img: this.augmentImage(attr, opts),
73+
boxes: this.augmentBoxes(attr, opts),
74+
points: this.augmentPoints(attr, opts)
75+
};
76+
}
77+
78+
augmentImage({img}) {
79+
return img;
80+
}
81+
82+
augmentPoints({points}) {
83+
return points;
84+
}
85+
86+
augmentBoxes(attr, opts) {
87+
const {boxes} = attr;
88+
const points = boxes.map(b => {
89+
return [
90+
this.backend.point(b[0], b[1]),
91+
this.backend.point(b[0] + b[2], b[1]),
92+
this.backend.point(b[0], b[1] + b[3]),
93+
this.backend.point(b[0] + b[2], b[1] + b[3])
94+
];
95+
}).reduce((a, b) => a.concat(b), []);
96+
97+
const pointsAfter = this.augmentPoints(Object.assign({}, attr, {points}), opts);
98+
99+
const boxesAfter = [];
100+
for (let i = 0; i < boxes.length; i++) {
101+
const left = Math.min(...pointsAfter.map(p => p.x));
102+
const right = Math.max(...pointsAfter.map(p => p.x));
103+
const top = Math.min(...pointsAfter.map(p => p.y));
104+
const bottom = Math.max(...pointsAfter.map(p => p.y));
105+
106+
boxesAfter.push([
107+
left,
108+
top,
109+
right - left,
110+
bottom - top
111+
]);
112+
}
113+
114+
return boxesAfter;
115+
}
116+
}
117+
118+
module.exports = AbstractAugmenter;
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
const h = require('hasard');
2+
const AbstractAugmenter = require('./abstract');
3+
4+
/**
5+
* Adds noise sampled from a poisson distribution with `lambda` being the exponent.
6+
* If perChannel is true, then the sampled values may be different per channel (and pixel).
7+
* if `scale` is defined (0 < scale < 1), then the noise can apply at a less granular scale
8+
* @param {Number} opts.lambda
9+
* @param {Number} [opts.scale=1]
10+
* @param {Number} [opts.perChannel=false]
11+
*/
12+
13+
class AdditivePoissonNoiseAugmenter extends AbstractAugmenter {
14+
constructor(opts) {
15+
super(opts);
16+
const {lambda, scale, perChannel} = opts;
17+
this.lambda = lambda;
18+
this.scale = scale;
19+
this.perChannel = perChannel;
20+
}
21+
22+
buildParams({width, height, channels}) {
23+
const scaleRef = h.reference(this.scale);
24+
25+
console.log([width, height, channels, this.lambda]);
26+
27+
const hasardPixelValue = h.multiply(
28+
h.if(h.boolean(), -1, 1),
29+
h.integer({
30+
type: 'poisson',
31+
lambda: this.lambda
32+
})
33+
);
34+
35+
const hasardPixelValueRef = h.reference({
36+
source: hasardPixelValue,
37+
inScope: 'colorPixel'
38+
});
39+
return h.object({
40+
noise: h.matrix({
41+
shape: [height, width, 4],
42+
value: h.if(this.perChannel,
43+
h.array([
44+
hasardPixelValue,
45+
hasardPixelValue,
46+
hasardPixelValue,
47+
0
48+
]),
49+
h.array({
50+
values: [
51+
hasardPixelValueRef,
52+
hasardPixelValueRef,
53+
hasardPixelValueRef,
54+
0
55+
],
56+
scope: 'colorPixel'
57+
})
58+
)
59+
}),
60+
scale: scaleRef
61+
});
62+
}
63+
64+
/**
65+
* @param {PipedImageAttribute} imgAttributes
66+
* @param {AugmenterRandomProperties} augmenterRandomProperties
67+
* @returns {PipedImageAttribute}
68+
*/
69+
augmentImage({img}, {noise, scale}) {
70+
return this.backend.addNoise(img, {noise, scale});
71+
}
72+
}
73+
74+
module.exports = AdditivePoissonNoiseAugmenter;

0 commit comments

Comments
 (0)