From 4ef4feb4a17489648dadc2c4ee8e08a36e980c4c Mon Sep 17 00:00:00 2001 From: Max Grossman Date: Mon, 6 Aug 2018 09:12:57 -0400 Subject: [PATCH] update sequences schema for test passing, start on making sequences code its own class ref #10 --- adapters/sequence/sequence.js | 114 ++++++++++++++++++++++++++++++++++ schema/sequences.js | 11 ++-- test/sequence/adapter.js | 17 ++--- test/sequence/handler.js | 8 +-- 4 files changed, 133 insertions(+), 17 deletions(-) create mode 100644 adapters/sequence/sequence.js diff --git a/adapters/sequence/sequence.js b/adapters/sequence/sequence.js new file mode 100644 index 0000000..78497b6 --- /dev/null +++ b/adapters/sequence/sequence.js @@ -0,0 +1,114 @@ +'use strict'; + +Promise = require('bluebird') + +const readdir = require('fs-extra').readdir; +const path = require('path'); +const ExifTool = require('exiftool-vendored'); +const exif = new ExifTool(); +const uuidv4 = require('uuid/v4'); +const dayjs = require('dayjs'); + +class Sequence { + constructor () {} + + build (source, type, minCutDist, maxCutDist, maxDelta, seqSize, userId) { + return fromSource(source, type).then(images => { + const params = { + maxDist: maxCutDist, + minDist: minCutDist, + maxDelta: maxDelta, + size: seqSize + } + return this.cut(flatten(images), params).then(sequences => { + return sequences.map(sequence => { + sequence.userId = userId; + return sequence; + }) + .catch((err) => { throw err; }); + }) + .catch((err) => { throw err; }) + }) + .catch((err) => { throw err; }) + } + + cut(images, params) { + return Promise + .map(images, (image) => meta(image)) + .then(metas => split(metas, params)) + } + + fromSource(source, type) { + if (type === 'directory') { + return Promise.map(source, (dir) => { + return readdir(dir) + .then(files => files.map(f => path.join(p, f))) + }) + } + } + + makeDate() { + return dayjs(tags.GPSDateTime.toString()); + } + + makeLoc(tags) { + return { + lon: Number(tags.GPSLongitude), + lat: Number(tags.GPSLatitude) + } + } + + meta(image) { + return exif.read(image).then(tags => { + return { + image: image, + loc: makeLoc(tags), + timestamp: makeDate(tags), + id: uuidv4() + } + }) + .catch(err => { throw err; }); + } + + split(metas, params) { + const sortedMetas = metas.sort((a, b) => a.timestamp - b.timestamp); + const pelIndex = sortedMetas.length - 2; + const sequences = []; + const maxDist = params.maxDist; + const maxDelta = params.maxDelta; + const maxSize = params.maxSize; + const minDist = params.minDist; + let currentSequence = []; + + sortedMetas.slice(0 , pelIndex).forEach((meta, i) => { + const partnerMeta = sortedMetas[i + 1], + distance = calcDistance(meta.loc, partnerMeta.loc), + 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 = []; + + } + + // add a uuid then add it to the sequence! + meta.id = uuidv4(); + currentSequence.push(meta); + + } + }) + + if (currentSequence.length > 0) addSequence(sequences, currentSequence); + return sequences; + } +} + +module.exports = Sequence; \ No newline at end of file diff --git a/schema/sequences.js b/schema/sequences.js index 9048b85..1d54024 100644 --- a/schema/sequences.js +++ b/schema/sequences.js @@ -4,9 +4,10 @@ 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) + .array() + .items( + Joi.object().keys({ + sequenceId: Joi.string().regex(/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i), + sequence: Joi.array().items(metadata) + }) ); \ No newline at end of file diff --git a/test/sequence/adapter.js b/test/sequence/adapter.js index e3c574f..cb0cbd0 100644 --- a/test/sequence/adapter.js +++ b/test/sequence/adapter.js @@ -19,13 +19,13 @@ Promise = require('bluebird'); describe('sequence', () => { it('meta reads then selializes an image\'s exif metadata', async () => { try { - const image = './testData/exif-gps-samples/DSCN0010.JPG', - metadata = await meta(image), - validation = Joi.validate(metadata, metadataSchema); + const image = './testData/exif-gps-samples/DSCN0010.JPG'; + const metadata = await meta(image) + const validation = Joi.validate(metadata, metadataSchema); expect(validation.value).to.be.eql(metadata); expect(validation.error).to.be.null; - + return; } catch (e) { console.error(e); @@ -33,12 +33,13 @@ describe('sequence', () => { }) it ('given a path of images, generates a list of sequence objects', async () => { try { - const paths = ['/testData/exif-gps-samples', '/testData/danbjoseph'].map(p => process.cwd() + p), - sequences = await sequenceAdapter(paths), - validation = Joi.validate(sequences, sequencesSchema); + const paths = ['/testData/exif-gps-samples', '/testData/danbjoseph'].map(p => process.cwd() + p); + const sequences = await sequenceAdapter(paths); + const validation = Joi.validate(sequences, sequencesSchema); + expect(validation.value).to.be.eql(sequences) expect(validation.error).to.be.null; - + return; } catch (e) { console.error(e); diff --git a/test/sequence/handler.js b/test/sequence/handler.js index c440474..6cb5a74 100644 --- a/test/sequence/handler.js +++ b/test/sequence/handler.js @@ -8,7 +8,8 @@ const uuidv4 = require('uuid/v4'); const server = require('../server'); const mergeDefaults = require('../helpers').mergeDefaults; -const oldUserPayload = require('../../testData/payloads').postUser; +const users = require('../../db/seeds/users'); +const danbjoseph = users[0]; const routes = [ // require('../../routes/sequence').get, require('../../routes/sequence').post @@ -20,11 +21,10 @@ before(async () => await server.liftOff(routes)) describe('post', () => { it('replies 200 when sequence post is successful', async () => { try { - const userId = (await db('Users').select('id').where({ name: 'danbjoseph' }))[0].id, - request = mergeDefaults({ + const request = mergeDefaults({ method: 'POST', payload: ['/testData/danbjoseph'].map(p => process.cwd() + p), - url: `/sequence?userId=${userId}` + url: `/sequence?userId=${danbjoseph.id}` }), r = await server.inject(request), statusCode = r.statusCode;