diff --git a/components/celebrity_recognition.js b/components/celebrity_recognition.js
new file mode 100644
index 0000000..c2ab7fd
--- /dev/null
+++ b/components/celebrity_recognition.js
@@ -0,0 +1,285 @@
+console.log('IMPORTING CELEBRITY RECOGNITION')
+
+// define style rules to be programtically loaded
+var style = document.createElement('style');
+style.innerHTML = `
+
+
+`;
+document.getElementsByTagName('head')[0].appendChild(style);
+
+
+
+// define component
+Vue.component('celebrity-recognition-viz', {
+ props: ['json_data', 'video_info'],
+ data: function () {
+ return {
+ confidence_threshold: 0.5,
+ interval_timer: null,
+ ctx: null
+ }
+ },
+ computed: {
+
+ celebrity_tracks: function () {
+ `
+ Extract just the logo tracking data from json
+ `
+
+ if (!this.json_data.annotation_results) return []
+
+ const tracksWithCelebrities = []
+ for (let index = 0; index < this.json_data.annotation_results.length; index++) {
+ if ('celebrity_recognition_annotations' in this.json_data.annotation_results[index] && 'celebrity_tracks' in this.json_data.annotation_results[index].celebrity_recognition_annotations) {
+ const celebrityTracks = this.json_data.annotation_results[index].celebrity_recognition_annotations.celebrity_tracks
+ celebrityTracks.forEach(track => {
+ if ('celebrities' in track) {
+ track.celebrities.forEach(() => {
+ tracksWithCelebrities.push(track)
+ })
+ }
+ })
+ }
+ }
+ return tracksWithCelebrities
+ },
+
+ indexed_celebrity_tracks: function () {
+ `
+ Create a clean list of celebrity tracking data with realisied nullable fields
+ and scaled bounding boxes ready to be drawn by the canvas
+ `
+
+ const indexed_tracks = []
+
+ this.celebrity_tracks.forEach(element => {
+ const detected_celebrity = new Celebrity_Detected(element, this.video_info.height, this.video_info.width, this.confidence_threshold)
+ if (detected_celebrity.segments.length > 0)
+ indexed_tracks.push(detected_celebrity)
+ })
+
+ return indexed_tracks
+ },
+
+ object_track_segments: function () {
+ `
+ create the list of cronological time segments that represent just when objects are present on screen
+ `
+ const segments = {}
+
+ this.indexed_celebrity_tracks.forEach(object_tracks => {
+
+ if (!(object_tracks.name in segments))
+ segments[object_tracks.name] = { 'segments': [], 'count': 0 }
+
+
+ object_tracks.segments.forEach(celebrity_segemnt => {
+
+
+ segments[object_tracks.name].count++
+
+ var added = false
+
+ for (let index = 0; index < segments[object_tracks.name].length; index++) {
+
+ const segment = segments[object_tracks.name].segments[index]
+ if (celebrity_segemnt.start_time < segment[1]) {
+ segments[object_tracks.name].segments[index][1] = Math.max(segments[object_tracks.name].segments[index][1], celebrity_segemnt.end_time)
+ added = true
+ break
+ }
+ }
+
+ if (!added)
+ segments[object_tracks.name].segments.push([celebrity_segemnt.start_time, celebrity_segemnt.end_time])
+
+
+
+ })
+
+ })
+
+ return segments
+ }
+ },
+ methods: {
+ segment_style: function (segment) {
+ return {
+ left: ((segment[0] / this.video_info.length) * 100).toString() + '%',
+ width: (((segment[1] - segment[0]) / this.video_info.length) * 100).toString() + '%'
+ }
+ },
+ segment_clicked: function (segment_data) {
+ this.$emit('segment-clicked', { seconds: segment_data[0] - 0.5 })
+ }
+ },
+ template: `
+
+
+
+ Confidence threshold
+
+ {{confidence_threshold}}
+
+
+
No celebrity detection data in JSON
+
+
+
+
+
{{key}} ({{segments.count}})
+
+
+
+
+ `,
+ mounted: function () {
+ console.log('mounted component')
+ var canvas = document.getElementById("my_canvas")
+ this.ctx = canvas.getContext("2d")
+ this.ctx.font = "20px Roboto"
+ const ctx = this.ctx
+
+ const component = this
+
+ this.interval_timer = setInterval(function () {
+ console.log('running')
+ const object_tracks = component.indexed_celebrity_tracks
+
+ draw_bounding_boxes(object_tracks, ctx)
+ }, 1000 / 30)
+ },
+ beforeDestroy: function () {
+ console.log('destroying component')
+ clearInterval(this.interval_timer)
+ this.ctx.clearRect(0, 0, 800, 500)
+ }
+})
+
+
+
+class Celebrity_Frame {
+
+ constructor(json_data, video_height, video_width) {
+
+ this.time_offset = nullable_time_offset_to_seconds(json_data.time_offset)
+
+ this.box = {
+ 'x': (json_data.normalized_bounding_box.left || 0) * video_width,
+ 'y': (json_data.normalized_bounding_box.top || 0) * video_height,
+ 'width': ((json_data.normalized_bounding_box.right || 0) - (json_data.normalized_bounding_box.left || 0)) * video_width,
+ 'height': ((json_data.normalized_bounding_box.bottom || 0) - (json_data.normalized_bounding_box.top || 0)) * video_height
+ }
+ }
+}
+
+
+
+class Celebrity_Track {
+ constructor(json_data, video_height, video_width) {
+
+ this.start_time = nullable_time_offset_to_seconds(json_data.face_track.segment.start_time_offset)
+ this.end_time = nullable_time_offset_to_seconds(json_data.face_track.segment.end_time_offset)
+ this.confidence = json_data.confidence
+
+ this.frames = []
+
+ json_data.face_track.timestamped_objects.forEach(frame => {
+ this.frames.push(new Celebrity_Frame(frame, video_height, video_width))
+ })
+ }
+
+ has_frames_for_time(seconds) {
+ return ((this.start_time <= seconds) && (this.end_time >= seconds))
+ }
+
+ most_recent_real_bounding_box(seconds) {
+
+ for (let index = 0; index < this.frames.length; index++) {
+ if (this.frames[index].time_offset > seconds) {
+ if (index > 0)
+ return this.frames[index - 1].box
+ else
+ return null
+ }
+ }
+ return null
+ }
+
+ most_recent_interpolated_bounding_box(seconds) {
+
+ for (let index = 0; index < this.frames.length; index++) {
+ if (this.frames[index].time_offset > seconds) {
+ if (index > 0) {
+ if ((index == 1) || (index == this.frames.length - 1))
+ return this.frames[index - 1].box
+
+ // create a new interpolated box between the
+ const start_box = this.frames[index - 1]
+ const end_box = this.frames[index]
+ const time_delt_ratio = (seconds - start_box.time_offset) / (end_box.time_offset - start_box.time_offset)
+
+ const interpolated_box = {
+ 'x': start_box.box.x + (end_box.box.x - start_box.box.x) * time_delt_ratio,
+ 'y': start_box.box.y + (end_box.box.y - start_box.box.y) * time_delt_ratio,
+ 'width': start_box.box.width + (end_box.box.width - start_box.box.width) * time_delt_ratio,
+ 'height': start_box.box.height + (end_box.box.height - start_box.box.height) * time_delt_ratio
+ }
+ return interpolated_box
+
+ } else
+ return null
+ }
+ }
+ return null
+ }
+
+ current_bounding_box(seconds, interpolate = true) {
+
+ if (interpolate)
+ return this.most_recent_interpolated_bounding_box(seconds)
+ else
+ return this.most_recent_real_bounding_box(seconds)
+ }
+}
+
+
+class Celebrity_Detected {
+ constructor(track, video_height, video_width, confidence_threshold) {
+
+ this.segments = []
+
+ track.celebrities.forEach(celebrity => {
+ // Some outputs do not contain a confidence
+ if (!celebrity.confidence || celebrity.confidence > confidence_threshold) {
+ this.name = celebrity.celebrity.display_name
+ this.id = celebrity.celebrity.name
+ this.segments.push(new Celebrity_Track(track, video_height, video_width))
+ }
+ })
+ }
+
+ has_frames_for_time(seconds) {
+
+ for (let index = 0; index < this.segments.length; index++) {
+ if (this.segments[index].has_frames_for_time(seconds))
+ return true
+ }
+ return false
+ }
+
+ current_bounding_box(seconds, interpolate = true) {
+
+ for (let index = 0; index < this.segments.length; index++) {
+ if (this.segments[index].has_frames_for_time(seconds))
+ return this.segments[index].current_bounding_box(seconds, interpolate)
+ }
+ return null
+ }
+}
\ No newline at end of file
diff --git a/index.html b/index.html
index 84f1e7e..398e19e 100644
--- a/index.html
+++ b/index.html
@@ -248,6 +248,11 @@ Try with your data
v-on:shot-clicked="jump_video">
+
+
+
@@ -264,6 +269,7 @@ Try with your data
+