diff --git a/adapters/sequence/README.md b/adapters/sequence/README.md new file mode 100644 index 0000000..63b5fc5 --- /dev/null +++ b/adapters/sequence/README.md @@ -0,0 +1,3 @@ +- make a new guy if the # is too large for a sequence +- if the distnace is too close, skip adding that image. +- or if when checking distance and time, one of them is greater than the max \ No newline at end of file diff --git a/adapters/sequence/build.js b/adapters/sequence/build.js index 8dff4a3..958af13 100644 --- a/adapters/sequence/build.js +++ b/adapters/sequence/build.js @@ -7,10 +7,7 @@ Promise = require('bluebird'); module.exports = (images, params) => { return Promise.map(images, async (image) => await meta(image)) - .then(metas => { - const sortedMetas = metas.sort((a, b) => a.date - b.date); - return split(sortedMetas) - }) + .then(async metas => await split(metas, params)) .catch(e => { throw e; }) } \ No newline at end of file diff --git a/adapters/sequence/helpers.js b/adapters/sequence/helpers.js index c3bf881..e6c0022 100644 --- a/adapters/sequence/helpers.js +++ b/adapters/sequence/helpers.js @@ -13,12 +13,10 @@ const uuidv4 = require('uuid/v4'); * @return {number} euclidean distance between two points */ exports.calcDistance = (metaLoc, nextMetaLoc) => { - return distance( - point([metaLoc.lon, metaLoc.lat]), - point([metaLoc.lon, metaLoc.lat]) - // { units: 'miles'} + return Math.sqrt( + Math.pow(nextMetaLoc.lon - metaLoc.lon, 2) + + Math.pow(nextMetaLoc.lat - metaLoc.lat, 2) ) - } /** @@ -29,7 +27,7 @@ exports.calcDistance = (metaLoc, nextMetaLoc) => { * @return {number} milisecond diff between two dates.ee * */ -exports.calcDelta = (metaDate, nextMetaDate) => metaDate.diff(nextMetaDate); +exports.calcDelta = (metaDate, nextMetaDate) => nextMetaDate.diff(metaDate) / 1000; /** * given a sequence map, adds a new sequence array and returns the map. @@ -38,19 +36,4 @@ exports.calcDelta = (metaDate, nextMetaDate) => metaDate.diff(nextMetaDate); * @param {array} sequence sequence to be added to sequenceMap * @return {object} updated sequenceMap */ -exports.addSequence = (sequenceMap, sequence) => { sequenceMap[uuidv4()] = sequence; return sequenceMap }; - -/** - * given thwo partner meta objects, determines if the two are within user defined time/distnace thresholds - * - * @param {object} meta metadata object at current index of images - * @param {object} partnerMeta metadata object for (either next or previous) partner meta object - */ -exports.withinThresholds = (meta, partnerMeta) => { - const distance = calcDistance(meta.loc, nextMeta.loc); - delta = calcDelta(meta.timestamp, nextMeta.timestamp), - toMuchTime = delta > maxDelta - toFar = distance > maxDistance; - - return !toFar && !toMuchTime; -} \ No newline at end of file +exports.addSequence = (sequenceMap, sequence) => { sequenceMap[uuidv4()] = sequence; return sequenceMap }; \ No newline at end of file diff --git a/adapters/sequence/index.js b/adapters/sequence/index.js index 0a67085..f0f7c0b 100644 --- a/adapters/sequence/index.js +++ b/adapters/sequence/index.js @@ -25,12 +25,13 @@ module.exports = (paths, cutDist, cutTime, cutSize) => { .then(async (images) => { try { const params = { - dist: cutDist || 0, - detla: cutTime || 0, + maxDist: cutDist || 0.2, + minDist: 0.1, + maxDetla: cutTime || 120, size: cutSize || 0 }, sequences = await buildSequences(flatten(images), params); - + resolve(sequences); } catch (e) { reject(e); diff --git a/adapters/sequence/meta.js b/adapters/sequence/meta.js index 74387d9..1902ebb 100644 --- a/adapters/sequence/meta.js +++ b/adapters/sequence/meta.js @@ -15,8 +15,7 @@ Promise = require('bluebird'); */ module.exports = (image) => { return new Promise((resolve, reject) => - exif - .read(image) + exif.read(image) .then(tags => { resolve({ image: image, diff --git a/adapters/sequence/split.js b/adapters/sequence/split.js index 49b8bda..e713dc1 100644 --- a/adapters/sequence/split.js +++ b/adapters/sequence/split.js @@ -3,47 +3,57 @@ const calcDistance = require('./helpers').calcDistance; const calcDelta = require('./helpers').calcDelta; -const addSequence = require('helpers').addSequence -const withinThresholds = require('helpers').withinThresholds; +const addSequence = require('./helpers').addSequence Promise = require('bluebird'); /** - * provided list of image metadata objects and thresholds for + * provided list of image metadata objects and thresholds used to define sequences, + * build and reply an object of image sequences + * + * @param metas {list} list of image metadata objects + * @param params {object} map of user (or default) tresholds used to build sequences + * @return {object} map of generated image sequences + * */ module.exports = (metas, params) => { const sortedMetas = metas.sort((a, b) => a.timestamp - b.timestamp), pelIndex = sortedMetas.length - 2, lastIdx = sortedMetas.length - 1, sequences = {}, - sequenceless = []; - maxDist = params.dist, - maxDelta = params.delta, - maxSize = params.maxSize; + sequenceless = [], + maxDist = params.maxDist, + maxDelta = params.maxDelta, + maxSize = params.maxSize, + minDist = params.minDist; let currentSequence = []; - - Promise.all(sortedMetas, (meta, i) => { - if (currentSequence.length === maxSize) { - sequences = addSequence({}, currentSequence); - currentSequence = []; - - } - - const partnerMeta = sortedMetas[i + (i <= pelIndex ? 1 : -1)], - meetsThreshold = withinThresholds(meta, partnerMeta); - - if (meetsThreshold) { + // for the 1st to 2nd to last meta... + sortedMetas.slice(0, pelIndex).forEach((meta, i) => { + const partnerMeta = sortedMetas[i + 1], + distance = calcDistance(meta.loc, partnerMeta.loc), + tooClose = false; + // tooClose = distance < minDist; + + // ... if image is not too close to its partner, add it to a sequence. + if (!tooClose) { + // ... if the current sequence length matches the maximum size, + // or images are too far apart (in space or time), + // add the current sequence to the sequence map, then make a new sequence. + const delta = calcDelta(meta.timestamp, partnerMeta.timestamp), + needNewSequence = currentSequence.length === maxSize || distance > maxDist || delta > maxDelta; + + if (needNewSequence) { + addSequence(sequences, currentSequence); + currentSequence = []; + + } + currentSequence.push(meta); - } else { - sequenceless.push(meta); - } - - }) - .then(() => { - return { sequences: sequences, sequenceless: sequenceless} + } }) - + if (currentSequence.length > 0) addSequence(sequences, currentSequence); + return sequences; }; \ No newline at end of file diff --git a/schema/index.js b/schema/index.js index 8b4e6d5..e43129a 100644 --- a/schema/index.js +++ b/schema/index.js @@ -1,5 +1,6 @@ 'use strict'; module.exports = { - metadata: require('./metadata') + metadata: require('./metadata'), + sequences: require('./sequences') } \ No newline at end of file diff --git a/schema/metadata.js b/schema/metadata.js index ca6e199..65d9011 100644 --- a/schema/metadata.js +++ b/schema/metadata.js @@ -33,9 +33,6 @@ const locSchema = Joi * invalid: '2008-9-23T14:27:07.240Z' */ const timeStampSchema = Joi.object(); -// Joi -// .string() -// .regex(/([1-2]{1}[0-9]{3})-([0-1]{1}[0-9]{1})-([0-3]{1}[0-9]{1})T([0-2]{1}[0-9]{1}):([0-6]{1}[0-9]{1}):([0-6]{1}[0-9]{1}).[0-9]{3}Z/) module.exports = Joi .object() @@ -46,6 +43,6 @@ module.exports = Joi }) .requiredKeys( 'image', - 'loc' - // 'timestamp' + 'loc', + 'timestamp' ) \ No newline at end of file diff --git a/schema/sequences.js b/schema/sequences.js new file mode 100644 index 0000000..9048b85 --- /dev/null +++ b/schema/sequences.js @@ -0,0 +1,12 @@ +'use strict'; + +const Joi = require('joi'); +const metadata = require('./metadata'); + +module.exports = Joi + .object() + .pattern( + // uuid regex; + /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, + Joi.array().items(metadata) + ); \ No newline at end of file diff --git a/test/adapters/sequencerSpec.js b/test/adapters/sequencerSpec.js index 744f9d6..c1abbe9 100644 --- a/test/adapters/sequencerSpec.js +++ b/test/adapters/sequencerSpec.js @@ -7,6 +7,7 @@ const Joi = require('joi'); /* schemas that define valid/expected function outputs */ const metadataSchema = require('../../schema').metadata; +const sequencesSchema = require('../../schema').sequences; /* seqeunce adapter components */ const meta = require('../../adapters/sequence/meta'); @@ -33,9 +34,11 @@ describe('sequence', () => { it ('given a path of images, generates a list of sequence objects', async () => { try { const paths = ['/testData/exif-gps-samples'].map(p => process.cwd() + p), - sequences = await sequenceAdapter(paths); + sequences = await sequenceAdapter(paths), + validation = Joi.validate(sequences, sequencesSchema); - console.log(sequences) + expect(validation.value).to.be.eql(sequences) + expect(validation.error).to.be.null; } catch (e) { console.error(e);