Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving image generator #45

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 170 additions & 37 deletions benchmark/generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,131 @@ var gm = require('gm').subClass({ imageMagick: true })
var fs = require('fs')
var path = require('path')
var chalk = require('chalk')
var rand = require('random-seed').create()
var randSeed = null
var log = console.log
var destDir = path.join(__dirname, 'testdata')

function generate (options) {
var number = options.number || 20
randSeed = options.seed || parseInt(Math.random() * 2147483647)
rand.seed(randSeed)
var json = { data: [] }
var imageOptions = randomizeOptions(number)
var destDir = path.join(__dirname, 'testdata')

rmDir(destDir)
fs.mkdir(destDir, function (error) {
if (error) throw error
randomizeOptions(number, function (err, imageOptionsArray) {
if (err) throw err

for (var i = 0; i < number; i++) {
var stream = renderHTMLPage();
(function (i) {
var fileName = `receipt-${i + 1}.jpg`
processStream(stream, path.join(destDir, fileName), imageOptions[i], function (error) {
if (error) throw error

json.data.push({
path: fileName,
results: {
amount: '698.00',
date: '2016-04-25'
}
})
for (var i = 0; i < number + 1; i++) {
var stream = renderHTMLPage();
(function (i) {
var original = i >= number
var fileName = original ? 'original.jpg' : `receipt-${i + 1}.jpg`
var imageOptions = original ? {} : imageOptionsArray[i]

if (json.data.length >= number) {
fs.writeFile(path.join(destDir, 'data.json'), JSON.stringify(json, null, 2), 'utf8')
log(chalk.green('Success! ') + number + ' sample receipt(s) has been created in ' + chalk.underline(destDir))
// Remove all disabled modifiers
for (var j = 0; j < (options.disabledModifiers || []).length; j++) {
imageOptions[options.disabledModifiers[j]] = null
}
})
})(i)
}

processStream(stream, path.join(destDir, fileName), imageOptions, function (error) {
if (error) throw error

if (!original) {
json.data.push({
path: fileName,
results: {
amount: '698.00',
date: '2016-04-25'
}
})

if (json.data.length >= number) {
fs.writeFileSync(path.join(destDir, 'data.json'), JSON.stringify(json, null, 2), 'utf8')
log(chalk.green('Success! ') + number + ' sample receipt(s) has been created in ' + chalk.underline(destDir))
log('Generated with seed number: ' + chalk.bold(randSeed))
}
}
})
})(i)
}
})
})
}

function randomizeOptions (number) {
var options = []
function randomizeOptions (number, cb) {
var promises = []

for (var i = 0; i < number; i++) {
options.push({
// All receipts has a bit of rotation and washout/gamma
rotate: biasedRotation(),
paperWashout: biasedWashout(),
photoGamma: biasedWashout(),
// 20% has some degree of paper bend
implode: shouldAdd(0.2) ? biasedImplode() : null
promises.push(new Promise(function (resolve, reject) {
randomizeOption(i, function (err, result) {
if (err) return reject(err)

resolve(result)
})
}))
}

Promise.all(promises).then(function (results) {
cb(null, results)
}, function (err) {
cb(err)
})
}

function randomizeOption (i, cb) {
var promises = []

promises.push(Promise.resolve({
// All receipts has a bit of rotation and washout/gamma
rotate: biasedRotation(),
paperWashout: biasedWashout(),
photoGamma: biasedWashout(),
// 20% has some degree of paper imposion bend
implode: shouldAdd(0.2) ? biasedImplode() : null,
// 30% has some degree if paper wave bend
wave: shouldAdd(0.3) ? biasedWave() : null
}))

// 20% has some lightning gradient
if (shouldAdd(0.2)) {
promises.push(new Promise(function (resolve, reject) {
randomGradientLightning(i + 1, function (err, filename) {
if (err) return reject(err)

resolve({
gradient: filename
})
})
}))
}

// 20% has some crumpled effect
if (shouldAdd(0.2)) {
promises.push(new Promise(function (resolve, reject) {
randomWrinkleDistortion(i + 1, function (err, filename) {
if (err) return reject(err)

resolve({
wrinkles: filename
})
})
})
)
}

return options
Promise.all(promises).then(function (array) {
// Combine all the options together
var options = array[0]
for (var i = 1; i < array.length; i++) {
Object.assign(options, array[i])
}

cb(null, options)
}, function (err) {
cb(err)
})
}

function biasedRotation () {
Expand All @@ -66,7 +140,7 @@ function biasedRotation () {

function shouldAdd (bias) {
bias = bias || 0.5
return Math.random() < bias
return rand.random() < bias
}

function biasedWashout () {
Expand All @@ -81,11 +155,43 @@ function biasedImplode () {
return biasedRandom(-0.2, 0.2, 0)
}

function biasedWave () {
var minWidth = 772 * 2
var maxWidth = 772 * 2
var avgWidth = 772 * 2.5
return [biasedRandom(10, 50, 10), biasedRandom(minWidth, maxWidth, avgWidth)]
}

function randomGradientLightning (number, cb) {
var randomHigh = rand.random() * (0.9 - 0.4) + 0.4
var randomLow = rand.random() * 0.4
var high = 255 * randomHigh
var low = 255 * randomLow
var filename = path.join(destDir, `gradient-lightning-map-${number}.jpg`)

gm(1500, 1500)
.in(`gradient:rgb(${high}, ${high}, ${high})-rgb(${low}, ${low}, ${low})`)
.write(filename, function (err) {
cb(err, filename)
})
}

function randomWrinkleDistortion (number, cb) {
var filename = path.join(destDir, `wrinkles-map-${number}.jpg`)
gm(1500, 1500)
.in('-seed', rand.intBetween(0, 2147483647))
.in('plasma:white-white')
.colorspace('gray')
.write(filename, function (err) {
cb(err, filename)
})
}

// From http://stackoverflow.com/a/29325222/939535
function biasedRandom (min, max, bias, influence) {
influence = influence || 1
var random = Math.random() * (max - min) + min
var mix = Math.random() * influence
var random = rand.random() * (max - min) + min
var mix = rand.random() * influence
return random * (1 - mix) + bias * mix
}

Expand All @@ -94,18 +200,43 @@ function renderHTMLPage () {
}

function processStream (stream, out, options, cb) {
var newStream
var imagemagick = gm(stream)
// Setting up green as the transparent color
imagemagick.transparent('green').background('green')

if (options.paperWashout) imagemagick.level(options.paperWashout.join(',')) // Washout the paper
if (options.photoGamma) imagemagick.level(options.photoGamma.join(',')) // Washout the photo

// Add crumpled/wrinkled paper effect
if (options.wrinkles) {
newStream = imagemagick.stream('jpg')
imagemagick = gm(newStream)
newStream = imagemagick
.compose('displace')
.displace(20, 20)
.composite(options.wrinkles).stream('jpg')
imagemagick = gm(newStream)
newStream = imagemagick
.compose('Linear_Burn')
.composite(options.wrinkles).stream('jpg')
imagemagick = gm(newStream)
}

if (options.implode) imagemagick.implode(options.implode)
if (options.wave) imagemagick.wave(options.wave[0], options.wave[1])
if (options.rotate) imagemagick.rotate('green', options.rotate)

// Rotated images will touch the border, so adding another 100px margin to clear
imagemagick.borderColor('green').border(100, 100)

// Add gradient lightning
if (options.gradient) {
newStream = imagemagick.stream('jpg')
imagemagick = gm(newStream)
imagemagick.compose('hardlight').composite(options.gradient)
}

imagemagick.write(out, function (error) {
cb(error)
})
Expand All @@ -131,6 +262,8 @@ function rmDir (dirPath) {
program
.usage('[options]')
.option('-a, --amount <n>', 'Number of images')
.option('-s, --seed <n>', 'Seed for psuedo random generation')
.option('-d, --disable [modifiers]', `Disable modifiers (e.g. ${['rotate', 'paperWashout', 'photoGamma', 'implode', 'wave', 'gradient', 'wrinkles'].join(', ')})`)
.parse(process.argv)

generate({ number: program.amount })
generate({ number: program.amount, seed: program.seed, disabledModifiers: (program.disable || '').split(',') })
10 changes: 9 additions & 1 deletion benchmark/image_preprocessors.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,16 @@ function extractTestData () {
function processData () {
return new Promise(function (resolve, reject) {
log('Processing data')
var imageProcessors = [

var imageProcessors = program.preprocessors || [
'graphicsmagick',
'sharp',
'opencv',
'imagemagick'
]
if (typeof imageProcessors === 'string') {
imageProcessors = imageProcessors.split(',')
}

fs.readFile(path.join(testdataDir, 'data.json'), 'utf8', function (error, data) {
if (error) {
Expand All @@ -143,6 +147,8 @@ function processData () {
var minRate = 0.85
var resultKeys = Object.keys(results)

if (program.verbose) console.log(JSON.stringify(results, null, 2))

// Remove preprocessors from reporting to GH
if (typeof program.onlyGhReport === 'string' && program.onlyGhReport.length > 0) {
var preprocessors = program.onlyGhReport.split(',')
Expand Down Expand Up @@ -189,7 +195,9 @@ function processData () {

program
.usage('[options]')
.option('--preprocessors [preprocessors]', 'Only run these preprocessors', ['graphicsmagick', 'sharp', 'opencv', 'imagemagick'])
.option('--only-gh-report [preprocessors]', 'Only report the desired preprocessors to Github', ['graphicsmagick', 'sharp', 'opencv', 'imagemagick'])
.option('--verbose')
.parse(process.argv)

extractTestData().then(processData)
10 changes: 5 additions & 5 deletions lib/image_processor/preprocessor/opencv.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ function getPaperThreshold (im, outfile, config) {
// Find the most common color
mask.convertHSVscale()
var channels = mask.split()
var res = channels[2].meanStdDev()
var minHue = res.mean[2] - res.stddev[2] * 3
var maxHue = res.mean[2] + res.stddev[2] * 3
mask = channels[2]
mask.inRange(minHue, maxHue)
var res = channels[0].meanStdDev()
var minHue = res.mean.get(0) - res.stddev.get(0) * 3
var maxHue = res.mean.get(0) + res.stddev.get(0) * 3
mask.inRange([minHue, 0, 0], [maxHue, 260, 180])

mask = mask.threshold(10, 255, 'Binary', 'Otsu')
mask.erode(5)
mask.dilate(20)
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"opencv": "git://github.com/peterbraden/node-opencv.git",
"pdf-text-extract": "^1.5.0",
"progress": "^2.0.0",
"random-seed": "0.3.0",
"tmp": "^0.0.33"
},
"optionalDependencies": {
Expand Down