Source: sequential.js

const h = require('hasard');
const debug = require('debug')('image-augmenter:augmenters:sequential');
const AbstractAugmenter = require('./abstract');

/**
* Build a sequence of actions
* @param {Object} opts options
* @param {Array.<ImageAugment> | Hasard.<Array.<ImageAugment>>} opts.steps steps to be run in sequence
* @example
* // Simple usage
* const img = cv.imread('lenna.jpg');
* const augmenter = ia.sequential([
* 	ia.blur(2),
* 	ia.perspectiveTransform(0.2)
* ]);
* const {image} = augmenter.read({images: [img]});
*/

class Sequential extends AbstractAugmenter {
	constructor(opts, ia) {
		super(opts, ia);
		if (Array.isArray(opts) || h.isHasard(opts)) {
			this.steps = opts;
		} else {
			this.steps = opts.steps;
		}

		this.hasHasardSteps = h.isHasard(this.steps);
	}

	buildParams() {
		return this.steps;
	}

	augment(o, steps, runOpts) {
		if (this.hasHasardSteps) {
			return this._augmentNonParallel(o, steps, runOpts);
		}

		return this._augmentParallel(o, steps, runOpts);
	}

	_augmentParallel(o, steps, runOpts) {
		// When using augmentParallel steps
		// are not hasard object
		// then they are the same for each image
		// so we can run one parrallel process for every image

		return this._runSteps({steps: steps[0], runOpts, images: o.images});
	}

	_augmentNonParallel(o, steps, runOpts) {
		// When using _augmentNonParallel
		// steps are hasard object
		// then they are not the same for each image
		// and we cannot parallelize the images processing

		const promises = this.backend.splitImages(o.images).map((image, imageIndex) => {
			return this._runSteps({steps: steps[imageIndex], runOpts, images: [image]});
		});

		return Promise.all(promises).then(results => {
			return {
				images: results.map(({images}) => images).reduce((a, b) => a.concat(b)),
				points: results.map(({points}) => points).reduce((a, b) => a.concat(b)),
				boxes: results.map(({boxes}) => boxes).reduce((a, b) => a.concat(b))
			};
		});
	}

	_runSteps({steps, runOpts, images}) {
		let promise = Promise.resolve({images, disposable: false});
		steps.forEach(s => {
			// Console.log({s})
			promise = promise.then(current => {
				return this._runOneStep({step: s, runOpts, current}).then(res => {
					return res;
				});
			});
		});
		return promise;
	}

	_runResolvedStep({step, runOpts, current}) {
		step.setBackend(this.backend.backendLib);
		if (!this.backend.isImages(current.images)) {
			// Console.log(this.backend.isImages.toString(), current)
			throw (new Error('Can only augment on object with image property in it'));
		}

		if (this.backend.isEmptyImages(current.images)) {
			throw (new Error('Empty image not allowed'));
		}

		const runOpts2 = Object.assign({}, runOpts, current);
		debug(`Step ${step.constructor.name}/${step.backend.key} started`);
		return step.run(runOpts2).then(res => {
			if (current.disposable) {
				this.backend.dispose(current.images);
			}

			debug(`Step ${step.constructor.name}/${step.backend.key} done`);
			return Object.assign({}, res, {disposable: true});
		});
	}

	_runOneStep({step, runOpts, current}) {
		if (h.isHasard(step)) {
			const promises = this.backend.splitImages(current.images).map(image => {
				const stepResolved = step.runOnce(runOpts);
				return this._runResolvedStep({step: stepResolved, runOpts, current: Object.assign({}, current, {images: [image]})});
			});
			return Promise.all(promises).then(results => {
				return {
					images: this.backend.mergeImages(results.map(({images}) => images)),
					points: results.map(({points}) => points).reduce((a, b) => a.concat(b)),
					boxes: results.map(({boxes}) => boxes).reduce((a, b) => a.concat(b))
				};
			});
		}

		return this._runResolvedStep({step, runOpts, current});
	}
}
module.exports = Sequential;