diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6e78514
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,238 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+build/
+bld/
+[Bb]in/
+[Oo]bj/
+[Oo]bjd/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+
+# Windows Azure Build Output
+csx/
+*.build.csdef
+
+# Windows Azure Emulator
+efc/
+rfc/
+
+# Windows Store app package directory
+AppPackages/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+[Ss]tyle[Cc]op.*
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+
+# FAKE - F# Make
+.fake/
+
+###########################
+# Android
+###########################
+
+.idea/
+local.properties
diff --git a/Emotion/Android/.gitignore b/Emotion/Android/.gitignore
new file mode 100644
index 0000000..d244d1b
--- /dev/null
+++ b/Emotion/Android/.gitignore
@@ -0,0 +1,7 @@
+.gradle
+local.properties
+.idea/
+.idea/libraries
+.DS_Store
+build/
+*.iml
diff --git a/Emotion/Android/ClientLibrary/build.gradle b/Emotion/Android/ClientLibrary/build.gradle
new file mode 100644
index 0000000..1b7886d
--- /dev/null
+++ b/Emotion/Android/ClientLibrary/build.gradle
@@ -0,0 +1,19 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.3.0'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
diff --git a/Emotion/Android/ClientLibrary/gradle.properties b/Emotion/Android/ClientLibrary/gradle.properties
new file mode 100644
index 0000000..5ef3cff
--- /dev/null
+++ b/Emotion/Android/ClientLibrary/gradle.properties
@@ -0,0 +1,26 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+signing.keyId=
+signing.password=
+signing.secretKeyRingFile=
+
+ossrhUsername=
+ossrhPassword=
+
diff --git a/Emotion/Android/ClientLibrary/gradle/wrapper/gradle-wrapper.jar b/Emotion/Android/ClientLibrary/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
Binary files /dev/null and b/Emotion/Android/ClientLibrary/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/Emotion/Android/ClientLibrary/gradle/wrapper/gradle-wrapper.properties b/Emotion/Android/ClientLibrary/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..0c71e76
--- /dev/null
+++ b/Emotion/Android/ClientLibrary/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/Emotion/Android/ClientLibrary/gradlew b/Emotion/Android/ClientLibrary/gradlew
new file mode 100644
index 0000000..91a7e26
--- /dev/null
+++ b/Emotion/Android/ClientLibrary/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/Emotion/Android/ClientLibrary/gradlew.bat b/Emotion/Android/ClientLibrary/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/Emotion/Android/ClientLibrary/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/Emotion/Android/ClientLibrary/lib/build.gradle b/Emotion/Android/ClientLibrary/lib/build.gradle
new file mode 100644
index 0000000..566f10f
--- /dev/null
+++ b/Emotion/Android/ClientLibrary/lib/build.gradle
@@ -0,0 +1,82 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "21.1.2"
+
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 21
+ }
+}
+
+dependencies {
+ compile 'com.google.code.gson:gson:2.3.1'
+ compile 'org.apache.directory.studio:org.apache.commons.io:2.4'
+}
+
+apply plugin: 'maven'
+apply plugin: 'signing'
+
+signing {
+ sign configurations.archives
+}
+
+// Group ID is the project name
+group = "com.microsoft.projectoxford"
+// Artifact name is the name of the technology
+archivesBaseName = "emotion"
+// Update your version
+version = "1.0.0"
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
+
+ repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
+ authentication(userName: ossrhUsername, password: ossrhPassword)
+ }
+
+ snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
+ authentication(userName: ossrhUsername, password: ossrhPassword)
+ }
+
+ pom.project {
+ // The readable name of the artifact
+ name 'Microsoft Project Oxford Emotion API Client Library'
+ packaging 'jar'
+
+ // optionally artifactId can be defined here
+
+ // Descriptions of the artifacts.
+ description 'This client library allows the use of Microsoft\'s state-of-the-art cloud-based algorithms to detect emotions based on facial expressions. See https://github.com/Microsoft/ProjectOxford-ClientSDK/tree/master/Emotion for more information.'
+
+ // Project URL
+ url 'https://github.com/Microsoft/ProjectOxford-ClientSDK'
+
+ // Github information
+ scm {
+ connection 'scm:git:https://github.com/Microsoft/ProjectOxford-ClientSDK'
+ developerConnection 'scm:git:https://github.com/Microsoft/ProjectOxford-ClientSDK'
+ url 'scm:git:https://github.com/Microsoft/ProjectOxford-ClientSDK'
+ }
+
+ licenses {
+ license {
+ name 'MIT'
+ url 'https://github.com/Microsoft/ProjectOxford-ClientSDK/blob/master/LICENSE.md'
+ }
+ }
+
+ developers {
+ developer {
+ id 'projectoxfordSDK'
+ name 'Project Oxford Client SDK'
+ email 'projectoxfordsdk@microsoft.com'
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Emotion/Android/ClientLibrary/lib/src/main/AndroidManifest.xml b/Emotion/Android/ClientLibrary/lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0c78969
--- /dev/null
+++ b/Emotion/Android/ClientLibrary/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/EmotionServiceClient.java b/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/EmotionServiceClient.java
new file mode 100644
index 0000000..7b293cc
--- /dev/null
+++ b/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/EmotionServiceClient.java
@@ -0,0 +1,49 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// ProjectOxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK-Windows
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+package com.microsoft.projectoxford.emotion;
+
+import com.microsoft.projectoxford.emotion.contract.FaceRectangle;
+import com.microsoft.projectoxford.emotion.contract.RecognizeResult;
+import com.microsoft.projectoxford.emotion.rest.EmotionServiceException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+public interface EmotionServiceClient {
+ public List recognizeImage(String url) throws EmotionServiceException;
+ public List recognizeImage(String url, FaceRectangle[] faceRectangles) throws EmotionServiceException;
+
+ public List recognizeImage(InputStream inputStream) throws EmotionServiceException, IOException;
+ public List recognizeImage(InputStream inputStream, FaceRectangle[] faceRectangles) throws EmotionServiceException, IOException;
+}
diff --git a/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/EmotionServiceRestClient.java b/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/EmotionServiceRestClient.java
new file mode 100644
index 0000000..d66e10b
--- /dev/null
+++ b/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/EmotionServiceRestClient.java
@@ -0,0 +1,122 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// ProjectOxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK-Windows
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+package com.microsoft.projectoxford.emotion;
+
+import com.google.gson.Gson;
+import com.microsoft.projectoxford.emotion.contract.FaceRectangle;
+import com.microsoft.projectoxford.emotion.contract.RecognizeResult;
+import com.microsoft.projectoxford.emotion.rest.EmotionServiceException;
+import com.microsoft.projectoxford.emotion.rest.WebServiceRequest;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class EmotionServiceRestClient implements EmotionServiceClient {
+ private static final String serviceHost = "https://api.projectoxford.ai/emotion/v1.0";
+ private static final String FACE_RECTANGLES = "faceRectangles";
+ private WebServiceRequest restCall = null;
+ private Gson gson = new Gson();
+
+ public EmotionServiceRestClient(String subscriptionKey) {
+ this.restCall = new WebServiceRequest(subscriptionKey);
+ }
+
+ @Override
+ public List recognizeImage(String url) throws EmotionServiceException {
+ return recognizeImage(url, null);
+ }
+
+ @Override
+ public List recognizeImage(String url, FaceRectangle[] faceRectangles) throws EmotionServiceException {
+ Map params = new HashMap<>();
+ String path = serviceHost + "/recognize";
+ if (faceRectangles != null && faceRectangles.length > 0) {
+ params.put(FACE_RECTANGLES, getFaceRectangleStrings(faceRectangles));
+ }
+ String uri = WebServiceRequest.getUrl(path, params);
+
+ params.clear();
+ params.put("url", url);
+
+ String json = (String) this.restCall.post(uri, params, null, false);
+ RecognizeResult[] recognizeResult = this.gson.fromJson(json, RecognizeResult[].class);
+
+ return Arrays.asList(recognizeResult);
+ }
+
+ @Override
+ public List recognizeImage(InputStream stream) throws EmotionServiceException, IOException {
+ return recognizeImage(stream, null);
+ }
+
+ @Override
+ public List recognizeImage(InputStream stream, FaceRectangle[] faceRectangles) throws EmotionServiceException, IOException {
+ Map params = new HashMap<>();
+ String path = serviceHost + "/recognize";
+ if (faceRectangles != null && faceRectangles.length > 0) {
+ params.put(FACE_RECTANGLES, getFaceRectangleStrings(faceRectangles));
+ }
+
+ String uri = WebServiceRequest.getUrl(path, params);
+
+ params.clear();
+ byte[] data = IOUtils.toByteArray(stream);
+ params.put("data", data);
+
+ String json = (String) this.restCall.post(uri, params, "application/octet-stream", false);
+ RecognizeResult[] recognizeResult = this.gson.fromJson(json, RecognizeResult[].class);
+
+ return Arrays.asList(recognizeResult);
+ }
+
+ private String getFaceRectangleStrings(FaceRectangle[] faceRectangles) {
+ StringBuffer sb = new StringBuffer();
+
+ boolean firstRectangle = true;
+ for (FaceRectangle faceRectangle : faceRectangles) {
+ if (firstRectangle) {
+ firstRectangle = false;
+ } else {
+ sb.append(';');
+ }
+ sb.append(String.format("%d,%d,%d,%d", faceRectangle.left, faceRectangle.top, faceRectangle.width, faceRectangle.height));
+ }
+ return sb.toString();
+ }
+}
diff --git a/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/contract/FaceRectangle.java b/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/contract/FaceRectangle.java
new file mode 100644
index 0000000..f7da113
--- /dev/null
+++ b/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/contract/FaceRectangle.java
@@ -0,0 +1,50 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// ProjectOxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK-Windows
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+package com.microsoft.projectoxford.emotion.contract;
+
+public class FaceRectangle {
+ public int width;
+
+ public int height;
+
+ public int left;
+
+ public int top;
+
+ public FaceRectangle(int left, int top, int width, int height) {
+ this.left = left;
+ this.top = top;
+ this.width = width;
+ this.height = height;
+ }
+}
diff --git a/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/contract/RecognizeResult.java b/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/contract/RecognizeResult.java
new file mode 100644
index 0000000..012fb4c
--- /dev/null
+++ b/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/contract/RecognizeResult.java
@@ -0,0 +1,42 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// ProjectOxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK-Windows
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+package com.microsoft.projectoxford.emotion.contract;
+
+import java.util.List;
+import java.util.UUID;
+
+public class RecognizeResult {
+ public FaceRectangle faceRectangle;
+
+ public Scores scores;
+}
diff --git a/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/contract/Scores.java b/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/contract/Scores.java
new file mode 100644
index 0000000..90ee5b7
--- /dev/null
+++ b/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/contract/Scores.java
@@ -0,0 +1,44 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// ProjectOxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK-Windows
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+package com.microsoft.projectoxford.emotion.contract;
+
+public class Scores {
+ public double anger;
+ public double contempt;
+ public double disgust;
+ public double fear;
+ public double happiness;
+ public double neutral;
+ public double sadness;
+ public double surprise;
+}
diff --git a/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/rest/EmotionServiceException.java b/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/rest/EmotionServiceException.java
new file mode 100644
index 0000000..5cef678
--- /dev/null
+++ b/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/rest/EmotionServiceException.java
@@ -0,0 +1,46 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// ProjectOxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK-Windows
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+package com.microsoft.projectoxford.emotion.rest;
+
+import com.google.gson.Gson;
+
+public class EmotionServiceException extends Exception {
+
+ public EmotionServiceException(String message) {
+ super(message);
+ }
+
+ public EmotionServiceException(Gson errorObject) {
+ super(errorObject.toString());
+ }
+}
diff --git a/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/rest/WebServiceRequest.java b/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/rest/WebServiceRequest.java
new file mode 100644
index 0000000..37b61e1
--- /dev/null
+++ b/Emotion/Android/ClientLibrary/lib/src/main/java/com/microsoft/projectoxford/emotion/rest/WebServiceRequest.java
@@ -0,0 +1,145 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// ProjectOxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK-Windows
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+package com.microsoft.projectoxford.emotion.rest;
+
+import com.google.gson.Gson;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Map;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+
+public class WebServiceRequest {
+ private static final String headerKey = "ocp-apim-subscription-key";
+ private HttpClient client = new DefaultHttpClient();
+ private String subscriptionKey;
+ private Gson gson = new Gson();
+
+ public WebServiceRequest(String key) {
+ this.subscriptionKey = key;
+ }
+
+ public Object post(String url, Map data, String contentType, boolean responseInputStream) throws EmotionServiceException {
+ return webInvoke("POST", url, data, contentType, responseInputStream);
+ }
+
+ private Object webInvoke(String method, String url, Map data, String contentType, boolean responseInputStream) throws EmotionServiceException {
+ HttpPost request = null;
+
+ request = new HttpPost(url);
+
+ boolean isStream = false;
+
+ /*Set header*/
+ if (contentType != null && !contentType.isEmpty()) {
+ request.setHeader("Content-Type", contentType);
+ if (contentType.toLowerCase().contains("octet-stream")) {
+ isStream = true;
+ }
+ } else {
+ request.setHeader("Content-Type", "application/json");
+ }
+
+ request.setHeader(headerKey, this.subscriptionKey);
+
+ try {
+ if (!isStream) {
+ String json = this.gson.toJson(data).toString();
+ StringEntity entity = new StringEntity(json);
+ request.setEntity(entity);
+ } else {
+ request.setEntity(new ByteArrayEntity((byte[]) data.get("data")));
+ }
+
+ HttpResponse response = this.client.execute(request);
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == 200) {
+ if(!responseInputStream) {
+ return readInput(response.getEntity().getContent());
+ }else {
+ return response.getEntity().getContent();
+ }
+ } else {
+ throw new Exception("Error executing POST request! Received error code: " + response.getStatusLine().getStatusCode());
+ }
+ } catch (Exception e) {
+ throw new EmotionServiceException(e.getMessage());
+ }
+ }
+
+ public static String getUrl(String path, Map params) {
+ StringBuffer url = new StringBuffer(path);
+
+ boolean start = true;
+ for (Map.Entry param : params.entrySet()) {
+ if (start) {
+ url.append("?");
+ start = false;
+ } else {
+ url.append("&");
+ }
+
+ try {
+ url.append(param.getKey() + "=" + URLEncoder.encode(param.getValue().toString(), "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ }
+
+ return url.toString();
+ }
+
+ private String readInput(InputStream is) throws IOException {
+ BufferedReader br = new BufferedReader(new InputStreamReader(is));
+ StringBuffer json = new StringBuffer();
+ String line;
+ while ((line = br.readLine()) != null) {
+ json.append(line);
+ }
+
+ return json.toString();
+ }
+}
diff --git a/Emotion/Android/ClientLibrary/settings.gradle b/Emotion/Android/ClientLibrary/settings.gradle
new file mode 100644
index 0000000..8c2a2e0
--- /dev/null
+++ b/Emotion/Android/ClientLibrary/settings.gradle
@@ -0,0 +1 @@
+include ':lib'
diff --git a/Emotion/Android/README.md b/Emotion/Android/README.md
new file mode 100644
index 0000000..8cb79b0
--- /dev/null
+++ b/Emotion/Android/README.md
@@ -0,0 +1,114 @@
+The client library
+==================
+
+The Emotion API client library is a thin Java client wrapper for Project Oxford
+Emotion REST APIs.
+
+The easiest way to consume the client library is to add com.microsoft.projectoxford.emotion package from Maven Central Repository.
+
+To find the latest version of client library, go to http://search.maven.org, and search for "com.microsoft.projectoxford".
+
+To add the client library dependency from build.gradle file, add the following line in dependencies.
+
+```
+dependencies {
+ //
+ // Use the following line to include client library from Maven Central Repository
+ // Change the version number from the search.maven.org result
+ //
+ compile 'com.microsoft.projectoxford:emotion:1.0.0'
+
+ // Your other Dependencies...
+}
+```
+
+To do add the client library dependency from Android Studio:
+1. From Menu, Choose File \> Project Structure
+2. Click on your app module
+3. Click on Dependencies tab
+4. Click "+" sign to add new dependency
+5. Pick "Library dependency" from the drop down list
+6. Type "com.microsoft.projectoxford" and hit the search icon from "Choose Library Dependency" dialog
+7. Pick the Project Oxford client library that you intend to use.
+8. Click "OK" to add the new dependency
+
+
+
+The sample
+==========
+
+This sample is an Android application to demonstrate the use of Project Oxford
+Emotion API.
+
+It demonstrates emotion detection from an image. It can identify people's faces and interpret their emotions.
+
+Requirements
+------------
+
+Android OS must be Android 4.1 or higher (API Level 16 or higher)
+
+Build the sample
+----------------
+
+You will
+need a [Microsoft Azure Account]() if you don't have one already.
+
+1. You must obtain a subscription key for Emotion API and Face API by following instructions in [Subscription
+key management](). Please note that Emotion API and Face API
+requires two different subscriptions.
+
+2. Start Android Studio and open project from Emotion \> Android \> Sample folder.
+
+3. In Android Studio -\> "Project" panel -\> "Android" view, open file
+ "app/res/values/strings.xml", and find the line
+ "Please\_add\_the\_emotion\_subscription\_key\_here;". Replace the
+ "Please\_add\_the\_emotion\_subscription\_key\_here" value with your Emotion subscription key
+ string from the first step. If you cannot find the file "strings.xml", it is
+ in folder "Sample\app\src\main\res\values\string.xml".
+
+4. In Android Studio -\> "Project" panel -\> "Android" view, open file
+ "app/res/values/strings.xml", and find the line
+ "Please\_add\_the\_face\_subscription\_key\_here;". Replace the
+ "Please\_add\_the\_face\_subscription\_key\_here" value with your Face subscription key
+ string from the first step. If you cannot find the file "strings.xml", it is
+ in folder "Sample\app\src\main\res\values\string.xml".
+
+5. In Android Studio, select menu "Build \> Make Project" to build the sample,
+ and "Run" to launch this sample app.
+
+
+
+
+Run the sample
+--------------
+
+In Android Studio, select menu "Run", and "Run app" to launch this sample app.
+
+Once the app is launched, click on buttons to use samples of between different
+scenarios, and follow the instructions on screen.
+
+Microsoft will receive the images you upload and may use them to improve Emotion
+API and related services. By submitting an image, you confirm you have consent
+from everyone in it.
+
+Contributing
+============
+We welcome contributions and are always looking for new SDKs, input, and
+suggestions. Feel free to file issues on the repo and we'll address them as we can. You can also learn more about how you can help on the [Contribution
+Rules & Guidelines]().
+
+For questions, feedback, or suggestions about Project Oxford services, feel free to reach out to us directly.
+
+- [Project Oxford support]()
+
+- [Forums]()
+
+- [Blog]()
+
+License
+=======
+
+All Project Oxford SDKs and samples are licensed with the MIT License. For more details, see
+[LICENSE]().
+
+Sample images are licensed separately, please refer to [LICENSE-IMAGE]().
diff --git a/Emotion/Android/Sample/.gitignore b/Emotion/Android/Sample/.gitignore
new file mode 100644
index 0000000..9c39e1d
--- /dev/null
+++ b/Emotion/Android/Sample/.gitignore
@@ -0,0 +1,7 @@
+.gradle
+/local.properties
+/.idea/*
+/.idea/libraries
+.DS_Store
+/build
+*.iml
diff --git a/Emotion/Android/Sample/app/.gitignore b/Emotion/Android/Sample/app/.gitignore
new file mode 100644
index 0000000..cc037c4
--- /dev/null
+++ b/Emotion/Android/Sample/app/.gitignore
@@ -0,0 +1,2 @@
+/build
+app.iml
diff --git a/Emotion/Android/Sample/app/build.gradle b/Emotion/Android/Sample/app/build.gradle
new file mode 100644
index 0000000..28bd93d
--- /dev/null
+++ b/Emotion/Android/Sample/app/build.gradle
@@ -0,0 +1,32 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "21.1.2"
+
+ defaultConfig {
+ applicationId "com.microsoft.projectoxford.emotionsample"
+ minSdkVersion 19
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ //
+ // Use the following line to include client library for Emotion API from Maven Central Repository
+ //
+ compile 'com.microsoft.projectoxford:emotion:1.0.0'
+
+ compile 'com.microsoft.projectoxford:face:1.0.0'
+ compile 'com.android.support:appcompat-v7:21.0.3'
+ compile 'com.google.code.gson:gson:2.5'
+}
diff --git a/Emotion/Android/Sample/app/gradle/wrapper/gradle-wrapper.jar b/Emotion/Android/Sample/app/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..05ef575
Binary files /dev/null and b/Emotion/Android/Sample/app/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/Emotion/Android/Sample/app/gradle/wrapper/gradle-wrapper.properties b/Emotion/Android/Sample/app/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..f23df6e
--- /dev/null
+++ b/Emotion/Android/Sample/app/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Oct 21 11:34:03 PDT 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip
diff --git a/Emotion/Android/Sample/app/gradlew b/Emotion/Android/Sample/app/gradlew
new file mode 100644
index 0000000..9d82f78
--- /dev/null
+++ b/Emotion/Android/Sample/app/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/Emotion/Android/Sample/app/gradlew.bat b/Emotion/Android/Sample/app/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/Emotion/Android/Sample/app/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/Emotion/Android/Sample/app/proguard-rules.pro b/Emotion/Android/Sample/app/proguard-rules.pro
new file mode 100644
index 0000000..80a3f9e
--- /dev/null
+++ b/Emotion/Android/Sample/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in C:\Users\v-junsli\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/Emotion/Android/Sample/app/src/androidTest/java/com/microsoft/projectoxford/emotionsample/ApplicationTest.java b/Emotion/Android/Sample/app/src/androidTest/java/com/microsoft/projectoxford/emotionsample/ApplicationTest.java
new file mode 100644
index 0000000..fcd778a
--- /dev/null
+++ b/Emotion/Android/Sample/app/src/androidTest/java/com/microsoft/projectoxford/emotionsample/ApplicationTest.java
@@ -0,0 +1,45 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// ProjectOxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK-Windows
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+package com.microsoft.projectoxford.emotionsample;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
diff --git a/Emotion/Android/Sample/app/src/main/AndroidManifest.xml b/Emotion/Android/Sample/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..3567f04
--- /dev/null
+++ b/Emotion/Android/Sample/app/src/main/AndroidManifest.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Emotion/Android/Sample/app/src/main/java/com/microsoft/projectoxford/emotionsample/MainActivity.java b/Emotion/Android/Sample/app/src/main/java/com/microsoft/projectoxford/emotionsample/MainActivity.java
new file mode 100644
index 0000000..706de49
--- /dev/null
+++ b/Emotion/Android/Sample/app/src/main/java/com/microsoft/projectoxford/emotionsample/MainActivity.java
@@ -0,0 +1,85 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// ProjectOxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+package com.microsoft.projectoxford.emotionsample;
+
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.support.v7.app.ActionBarActivity;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+
+public class MainActivity extends ActionBarActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ if (getString(R.string.subscription_key).startsWith("Please")) {
+ new AlertDialog.Builder(this)
+ .setTitle(getString(R.string.add_subscription_key_tip_title))
+ .setMessage(getString(R.string.add_subscription_key_tip))
+ .setCancelable(false)
+ .show();
+ }
+
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_main, menu);
+ return true;
+ }
+
+ public void activityRecognize(View v) {
+ Intent intent = new Intent(this, RecognizeActivity.class);
+ startActivity(intent);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/Emotion/Android/Sample/app/src/main/java/com/microsoft/projectoxford/emotionsample/RecognizeActivity.java b/Emotion/Android/Sample/app/src/main/java/com/microsoft/projectoxford/emotionsample/RecognizeActivity.java
new file mode 100644
index 0000000..542efad
--- /dev/null
+++ b/Emotion/Android/Sample/app/src/main/java/com/microsoft/projectoxford/emotionsample/RecognizeActivity.java
@@ -0,0 +1,344 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// ProjectOxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+package com.microsoft.projectoxford.emotionsample;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+
+import com.google.gson.Gson;
+import com.microsoft.projectoxford.emotion.EmotionServiceClient;
+import com.microsoft.projectoxford.emotion.EmotionServiceRestClient;
+import com.microsoft.projectoxford.emotion.contract.FaceRectangle;
+import com.microsoft.projectoxford.emotion.contract.RecognizeResult;
+import com.microsoft.projectoxford.emotion.rest.EmotionServiceException;
+import com.microsoft.projectoxford.emotionsample.helper.ImageHelper;
+
+import com.microsoft.projectoxford.face.FaceServiceRestClient;
+import com.microsoft.projectoxford.face.contract.Face;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+public class RecognizeActivity extends ActionBarActivity {
+
+ // Flag to indicate which task is to be performed.
+ private static final int REQUEST_SELECT_IMAGE = 0;
+
+ // The button to select an image
+ private Button mButtonSelectImage;
+
+ // The URI of the image selected to detect.
+ private Uri mImageUri;
+
+ // The image selected to detect.
+ private Bitmap mBitmap;
+
+ // The edit to show status and result.
+ private EditText mEditText;
+
+ private EmotionServiceClient client;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_recognize);
+
+ if (client == null) {
+ client = new EmotionServiceRestClient(getString(R.string.subscription_key));
+ }
+
+ mButtonSelectImage = (Button) findViewById(R.id.buttonSelectImage);
+ mEditText = (EditText) findViewById(R.id.editTextResult);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_recognize, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ public void doRecognize() {
+ mButtonSelectImage.setEnabled(false);
+
+ // Do emotion detection using auto-detected faces.
+ try {
+ new doRequest(false).execute();
+ } catch (Exception e) {
+ mEditText.append("Error encountered. Exception is: " + e.toString());
+ }
+
+ String faceSubscriptionKey = getString(R.string.faceSubscription_key);
+ if (faceSubscriptionKey.equalsIgnoreCase("Please_add_the_face_subscription_key_here")) {
+ mEditText.append("\n\nThere is no face subscription key in res/values/strings.xml. Skip the sample for detecting emotions using face rectangles\n");
+ } else {
+ // Do emotion detection using face rectangles provided by Face API.
+ try {
+ new doRequest(true).execute();
+ } catch (Exception e) {
+ mEditText.append("Error encountered. Exception is: " + e.toString());
+ }
+ }
+ }
+
+ // Called when the "Select Image" button is clicked.
+ public void selectImage(View view) {
+ mEditText.setText("");
+
+ Intent intent;
+ intent = new Intent(RecognizeActivity.this, com.microsoft.projectoxford.emotionsample.helper.SelectImageActivity.class);
+ startActivityForResult(intent, REQUEST_SELECT_IMAGE);
+ }
+
+ // Called when image selection is done.
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ Log.d("RecognizeActivity", "onActivityResult");
+ switch (requestCode) {
+ case REQUEST_SELECT_IMAGE:
+ if (resultCode == RESULT_OK) {
+ // If image is selected successfully, set the image URI and bitmap.
+ mImageUri = data.getData();
+
+ mBitmap = ImageHelper.loadSizeLimitedBitmapFromUri(
+ mImageUri, getContentResolver());
+ if (mBitmap != null) {
+ // Show the image on screen.
+ ImageView imageView = (ImageView) findViewById(R.id.selectedImage);
+ imageView.setImageBitmap(mBitmap);
+
+ // Add detection log.
+ Log.d("RecognizeActivity", "Image: " + mImageUri + " resized to " + mBitmap.getWidth()
+ + "x" + mBitmap.getHeight());
+
+ doRecognize();
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+
+ private List processWithAutoFaceDetection() throws EmotionServiceException, IOException {
+ Log.d("emotion", "Start emotion detection with auto-face detection");
+
+ Gson gson = new Gson();
+
+ // Put the image into an input stream for detection.
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ mBitmap.compress(Bitmap.CompressFormat.JPEG, 100, output);
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(output.toByteArray());
+
+ long startTime = System.currentTimeMillis();
+ // -----------------------------------------------------------------------
+ // KEY SAMPLE CODE STARTS HERE
+ // -----------------------------------------------------------------------
+
+ List result = null;
+ //
+ // Detect emotion by auto-detecting faces in the image.
+ //
+ result = this.client.recognizeImage(inputStream);
+
+ String json = gson.toJson(result);
+ Log.d("result", json);
+
+ Log.d("emotion", String.format("Detection done. Elapsed time: %d ms", (System.currentTimeMillis() - startTime)));
+ // -----------------------------------------------------------------------
+ // KEY SAMPLE CODE ENDS HERE
+ // -----------------------------------------------------------------------
+ return result;
+ }
+
+ private List processWithFaceRectangles() throws EmotionServiceException, com.microsoft.projectoxford.face.rest.ClientException, IOException {
+ Log.d("emotion", "Do emotion detection with known face rectangles");
+ Gson gson = new Gson();
+
+ // Put the image into an input stream for detection.
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ mBitmap.compress(Bitmap.CompressFormat.JPEG, 100, output);
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(output.toByteArray());
+
+ long timeMark = System.currentTimeMillis();
+ Log.d("emotion", "Start face detection using Face API");
+ FaceRectangle[] faceRectangles = null;
+ String faceSubscriptionKey = getString(R.string.faceSubscription_key);
+ FaceServiceRestClient faceClient = new FaceServiceRestClient(faceSubscriptionKey);
+ Face faces[] = faceClient.detect(inputStream, false, false, null);
+ Log.d("emotion", String.format("Face detection is done. Elapsed time: %d ms", (System.currentTimeMillis() - timeMark)));
+
+ if (faces != null) {
+ faceRectangles = new FaceRectangle[faces.length];
+
+ for (int i = 0; i < faceRectangles.length; i++) {
+ // Face API and Emotion API have different FaceRectangle definition. Do the conversion.
+ com.microsoft.projectoxford.face.contract.FaceRectangle rect = faces[i].faceRectangle;
+ faceRectangles[i] = new com.microsoft.projectoxford.emotion.contract.FaceRectangle(rect.left, rect.top, rect.width, rect.height);
+ }
+ }
+
+ List result = null;
+ if (faceRectangles != null) {
+ inputStream.reset();
+
+ timeMark = System.currentTimeMillis();
+ Log.d("emotion", "Start emotion detection using Emotion API");
+ // -----------------------------------------------------------------------
+ // KEY SAMPLE CODE STARTS HERE
+ // -----------------------------------------------------------------------
+ result = this.client.recognizeImage(inputStream, faceRectangles);
+
+ String json = gson.toJson(result);
+ Log.d("result", json);
+ // -----------------------------------------------------------------------
+ // KEY SAMPLE CODE ENDS HERE
+ // -----------------------------------------------------------------------
+ Log.d("emotion", String.format("Emotion detection is done. Elapsed time: %d ms", (System.currentTimeMillis() - timeMark)));
+ }
+ return result;
+ }
+
+ private class doRequest extends AsyncTask> {
+ // Store error message
+ private Exception e = null;
+ private boolean useFaceRectangles = false;
+
+ public doRequest(boolean useFaceRectangles) {
+ this.useFaceRectangles = useFaceRectangles;
+ }
+
+ @Override
+ protected List doInBackground(String... args) {
+ if (this.useFaceRectangles == false) {
+ try {
+ return processWithAutoFaceDetection();
+ } catch (Exception e) {
+ this.e = e; // Store error
+ }
+ } else {
+ try {
+ return processWithFaceRectangles();
+ } catch (Exception e) {
+ this.e = e; // Store error
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(List result) {
+ super.onPostExecute(result);
+ // Display based on error existence
+
+ if (this.useFaceRectangles == false) {
+ mEditText.append("\n\nRecognizing emotions with auto-detected face rectangles...\n");
+ } else {
+ mEditText.append("\n\nRecognizing emotions with existing face rectangles from Face API...\n");
+ }
+ if (e != null) {
+ mEditText.setText("Error: " + e.getMessage());
+ this.e = null;
+ } else {
+ if (result.size() == 0) {
+ mEditText.append("No emotion detected :(");
+ } else {
+ Integer count = 0;
+ Canvas faceCanvas = new Canvas(mBitmap);
+ faceCanvas.drawBitmap(mBitmap, 0, 0, null);
+ Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeWidth(5);
+ paint.setColor(Color.RED);
+
+ for (RecognizeResult r : result) {
+ mEditText.append(String.format("\nFace #%1$d \n", count));
+ mEditText.append(String.format("\t anger: %1$.5f\n", r.scores.anger));
+ mEditText.append(String.format("\t contempt: %1$.5f\n", r.scores.contempt));
+ mEditText.append(String.format("\t disgust: %1$.5f\n", r.scores.disgust));
+ mEditText.append(String.format("\t fear: %1$.5f\n", r.scores.fear));
+ mEditText.append(String.format("\t happiness: %1$.5f\n", r.scores.happiness));
+ mEditText.append(String.format("\t neutral: %1$.5f\n", r.scores.neutral));
+ mEditText.append(String.format("\t sadness: %1$.5f\n", r.scores.sadness));
+ mEditText.append(String.format("\t surprise: %1$.5f\n", r.scores.surprise));
+ mEditText.append(String.format("\t face rectangle: %d, %d, %d, %d", r.faceRectangle.left, r.faceRectangle.top, r.faceRectangle.width, r.faceRectangle.height));
+ faceCanvas.drawRect(r.faceRectangle.left,
+ r.faceRectangle.top,
+ r.faceRectangle.left + r.faceRectangle.width,
+ r.faceRectangle.top + r.faceRectangle.height,
+ paint);
+ count++;
+ }
+ ImageView imageView = (ImageView) findViewById(R.id.selectedImage);
+ imageView.setImageDrawable(new BitmapDrawable(getResources(), mBitmap));
+ }
+ mEditText.setSelection(0);
+ }
+
+ mButtonSelectImage.setEnabled(true);
+ }
+ }
+}
diff --git a/Emotion/Android/Sample/app/src/main/java/com/microsoft/projectoxford/emotionsample/helper/ImageHelper.java b/Emotion/Android/Sample/app/src/main/java/com/microsoft/projectoxford/emotionsample/helper/ImageHelper.java
new file mode 100644
index 0000000..5156fe3
--- /dev/null
+++ b/Emotion/Android/Sample/app/src/main/java/com/microsoft/projectoxford/emotionsample/helper/ImageHelper.java
@@ -0,0 +1,168 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// ProjectOxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+package com.microsoft.projectoxford.emotionsample.helper;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Defined several functions to load, draw, save, resize, and rotate images.
+ */
+public class ImageHelper {
+
+ // The maximum side length of the image to detect, to keep the size of image less than 4MB.
+ // Resize the image if its side length is larger than the maximum.
+ private static final int IMAGE_MAX_SIDE_LENGTH = 1280;
+
+ // Ratio to scale a detected face rectangle, the face rectangle scaled up looks more natural.
+ private static final double FACE_RECT_SCALE_RATIO = 1.3;
+
+ // Decode image from imageUri, and resize according to the expectedMaxImageSideLength
+ // If expectedMaxImageSideLength is
+ // (1) less than or equal to 0,
+ // (2) more than the actual max size length of the bitmap
+ // then return the original bitmap
+ // Else, return the scaled bitmap
+ public static Bitmap loadSizeLimitedBitmapFromUri(
+ Uri imageUri,
+ ContentResolver contentResolver) {
+ try {
+ // Load the image into InputStream.
+ InputStream imageInputStream = contentResolver.openInputStream(imageUri);
+
+ // For saving memory, only decode the image meta and get the side length.
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ Rect outPadding = new Rect();
+ BitmapFactory.decodeStream(imageInputStream, outPadding, options);
+
+ // Calculate shrink rate when loading the image into memory.
+ int maxSideLength =
+ options.outWidth > options.outHeight ? options.outWidth : options.outHeight;
+ options.inSampleSize = 1;
+ options.inSampleSize = calculateSampleSize(maxSideLength, IMAGE_MAX_SIDE_LENGTH);
+ options.inJustDecodeBounds = false;
+ imageInputStream.close();
+
+
+ // Load the bitmap and resize it to the expected size length
+ imageInputStream = contentResolver.openInputStream(imageUri);
+ Bitmap bitmap = BitmapFactory.decodeStream(imageInputStream, outPadding, options);
+ maxSideLength = bitmap.getWidth() > bitmap.getHeight()
+ ? bitmap.getWidth(): bitmap.getHeight();
+ double ratio = IMAGE_MAX_SIDE_LENGTH / (double) maxSideLength;
+ if (ratio < 1) {
+ bitmap = Bitmap.createScaledBitmap(
+ bitmap,
+ (int)(bitmap.getWidth() * ratio),
+ (int)(bitmap.getHeight() * ratio),
+ false);
+ }
+
+ return rotateBitmap(bitmap, getImageRotationAngle(imageUri, contentResolver));
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ // Return the number of times for the image to shrink when loading it into memory.
+ // The SampleSize can only be a final value based on powers of 2.
+ private static int calculateSampleSize(int maxSideLength, int expectedMaxImageSideLength) {
+ int inSampleSize = 1;
+
+ while (maxSideLength > 2 * expectedMaxImageSideLength) {
+ maxSideLength /= 2;
+ inSampleSize *= 2;
+ }
+
+ return inSampleSize;
+ }
+
+ // Get the rotation angle of the image taken.
+ private static int getImageRotationAngle(
+ Uri imageUri, ContentResolver contentResolver) throws IOException {
+ int angle = 0;
+ Cursor cursor = contentResolver.query(imageUri,
+ new String[] { MediaStore.Images.ImageColumns.ORIENTATION }, null, null, null);
+ if (cursor != null) {
+ if (cursor.getCount() == 1) {
+ cursor.moveToFirst();
+ angle = cursor.getInt(0);
+ }
+ cursor.close();
+ } else {
+ ExifInterface exif = new ExifInterface(imageUri.getPath());
+ int orientation = exif.getAttributeInt(
+ ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
+
+ switch (orientation) {
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ angle = 270;
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ angle = 180;
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ angle = 90;
+ break;
+ default:
+ break;
+ }
+ }
+ return angle;
+ }
+
+ // Rotate the original bitmap according to the given orientation angle
+ private static Bitmap rotateBitmap(Bitmap bitmap, int angle) {
+ // If the rotate angle is 0, then return the original image, else return the rotated image
+ if (angle != 0) {
+ Matrix matrix = new Matrix();
+ matrix.postRotate(angle);
+ return Bitmap.createBitmap(
+ bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+ } else {
+ return bitmap;
+ }
+ }
+
+}
diff --git a/Emotion/Android/Sample/app/src/main/java/com/microsoft/projectoxford/emotionsample/helper/SelectImageActivity.java b/Emotion/Android/Sample/app/src/main/java/com/microsoft/projectoxford/emotionsample/helper/SelectImageActivity.java
new file mode 100644
index 0000000..d085d59
--- /dev/null
+++ b/Emotion/Android/Sample/app/src/main/java/com/microsoft/projectoxford/emotionsample/helper/SelectImageActivity.java
@@ -0,0 +1,136 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// ProjectOxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK
+
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+package com.microsoft.projectoxford.emotionsample.helper;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.support.annotation.NonNull;
+import android.support.v7.app.ActionBarActivity;
+import android.view.View;
+import android.widget.TextView;
+
+import com.microsoft.projectoxford.emotionsample.R;
+
+import java.io.File;
+import java.io.IOException;
+
+// The activity for the user to select a image and to detect faces in the image.
+public class SelectImageActivity extends ActionBarActivity {
+ // Flag to indicate the request of the next task to be performed
+ private static final int REQUEST_TAKE_PHOTO = 0;
+ private static final int REQUEST_SELECT_IMAGE_IN_ALBUM = 1;
+
+ // The URI of photo taken with camera
+ private Uri mUriPhotoTaken;
+
+ // When the activity is created, set all the member variables to initial state.
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_select_image);
+ }
+
+ // Save the activity state when it's going to stop.
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putParcelable("ImageUri", mUriPhotoTaken);
+ }
+
+ // Recover the saved state when the activity is recreated.
+ @Override
+ protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ mUriPhotoTaken = savedInstanceState.getParcelable("ImageUri");
+ }
+
+ // Deal with the result of selection of the photos and faces.
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode)
+ {
+ case REQUEST_TAKE_PHOTO:
+ case REQUEST_SELECT_IMAGE_IN_ALBUM:
+ if (resultCode == RESULT_OK) {
+ Uri imageUri;
+ if (data == null || data.getData() == null) {
+ imageUri = mUriPhotoTaken;
+ } else {
+ imageUri = data.getData();
+ }
+ Intent intent = new Intent();
+ intent.setData(imageUri);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // When the button of "Take a Photo with Camera" is pressed.
+ public void takePhoto(View view) {
+ Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ if(intent.resolveActivity(getPackageManager()) != null) {
+ // Save the photo taken to a temporary file.
+ File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
+ try {
+ File file = File.createTempFile("IMG_", ".jpg", storageDir);
+ mUriPhotoTaken = Uri.fromFile(file);
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, mUriPhotoTaken);
+ startActivityForResult(intent, REQUEST_TAKE_PHOTO);
+ } catch (IOException e) {
+ setInfo(e.getMessage());
+ }
+ }
+ }
+
+ // When the button of "Select a Photo in Album" is pressed.
+ public void selectImageInAlbum(View view) {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("image/*");
+ if (intent.resolveActivity(getPackageManager()) != null) {
+ startActivityForResult(intent, REQUEST_SELECT_IMAGE_IN_ALBUM);
+ }
+ }
+
+ // Set the information panel on screen.
+ private void setInfo(String info) {
+ TextView textView = (TextView) findViewById(R.id.info);
+ textView.setText(info);
+ }
+}
diff --git a/Emotion/Android/Sample/app/src/main/res/drawable/button_background.xml b/Emotion/Android/Sample/app/src/main/res/drawable/button_background.xml
new file mode 100644
index 0000000..42aa180
--- /dev/null
+++ b/Emotion/Android/Sample/app/src/main/res/drawable/button_background.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Emotion/Android/Sample/app/src/main/res/layout/activity_main.xml b/Emotion/Android/Sample/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..2c2a533
--- /dev/null
+++ b/Emotion/Android/Sample/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
diff --git a/Emotion/Android/Sample/app/src/main/res/layout/activity_recognize.xml b/Emotion/Android/Sample/app/src/main/res/layout/activity_recognize.xml
new file mode 100644
index 0000000..f9d1e0e
--- /dev/null
+++ b/Emotion/Android/Sample/app/src/main/res/layout/activity_recognize.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Emotion/Android/Sample/app/src/main/res/layout/activity_select_image.xml b/Emotion/Android/Sample/app/src/main/res/layout/activity_select_image.xml
new file mode 100644
index 0000000..6c9dd09
--- /dev/null
+++ b/Emotion/Android/Sample/app/src/main/res/layout/activity_select_image.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Emotion/Android/Sample/app/src/main/res/menu/menu_main.xml b/Emotion/Android/Sample/app/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..b1cb908
--- /dev/null
+++ b/Emotion/Android/Sample/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,6 @@
+
diff --git a/Emotion/Android/Sample/app/src/main/res/menu/menu_recognize.xml b/Emotion/Android/Sample/app/src/main/res/menu/menu_recognize.xml
new file mode 100644
index 0000000..475ec4b
--- /dev/null
+++ b/Emotion/Android/Sample/app/src/main/res/menu/menu_recognize.xml
@@ -0,0 +1,7 @@
+
diff --git a/Emotion/Android/Sample/app/src/main/res/mipmap-hdpi/ic_launcher.png b/Emotion/Android/Sample/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/Emotion/Android/Sample/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/Emotion/Android/Sample/app/src/main/res/mipmap-mdpi/ic_launcher.png b/Emotion/Android/Sample/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/Emotion/Android/Sample/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/Emotion/Android/Sample/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/Emotion/Android/Sample/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/Emotion/Android/Sample/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/Emotion/Android/Sample/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/Emotion/Android/Sample/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/Emotion/Android/Sample/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/Emotion/Android/Sample/app/src/main/res/values-w820dp/dimens.xml b/Emotion/Android/Sample/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/Emotion/Android/Sample/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/Emotion/Android/Sample/app/src/main/res/values/colors.xml b/Emotion/Android/Sample/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..316f9e6
--- /dev/null
+++ b/Emotion/Android/Sample/app/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+
+ #66B2FF
+ #3399FF
+ #C0C0C0
+
diff --git a/Emotion/Android/Sample/app/src/main/res/values/dimens.xml b/Emotion/Android/Sample/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/Emotion/Android/Sample/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/Emotion/Android/Sample/app/src/main/res/values/strings.xml b/Emotion/Android/Sample/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..11e9113
--- /dev/null
+++ b/Emotion/Android/Sample/app/src/main/res/values/strings.xml
@@ -0,0 +1,28 @@
+
+ Project Oxford Emotion Samples
+
+
+
+
+ Please_add_the_emotion_subscription_key_here
+
+
+
+
+
+ Please_add_the_face_subscription_key_here
+
+
+ Subscription Key is Needed
+ Please put your two subscription keys for Emotion API/Face API to source file \"app/res/values/strings.xml\" to enable Emotion API and Face API calls.
+ Settings
+ AnalyzeActivity
+ RecognizeActivity
+ ThumbnailActivity
+
+
+ Select an Image
+ Take a Photo
+ From Gallery
+
+
diff --git a/Emotion/Android/Sample/app/src/main/res/values/styles.xml b/Emotion/Android/Sample/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..314f195
--- /dev/null
+++ b/Emotion/Android/Sample/app/src/main/res/values/styles.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
diff --git a/Emotion/Android/Sample/build.gradle b/Emotion/Android/Sample/build.gradle
new file mode 100644
index 0000000..1b7886d
--- /dev/null
+++ b/Emotion/Android/Sample/build.gradle
@@ -0,0 +1,19 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.3.0'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
diff --git a/Emotion/Android/Sample/gradle.properties b/Emotion/Android/Sample/gradle.properties
new file mode 100644
index 0000000..507106a
--- /dev/null
+++ b/Emotion/Android/Sample/gradle.properties
@@ -0,0 +1,28 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+// The following are used by Microsoft to upload built binaries to Maven Central Repository.
+// You can ignore them for your sample usage.
+
+signing.keyId=
+signing.password=
+signing.secretKeyRingFile=
+
+ossrhUsername=
+ossrhPassword=
diff --git a/Emotion/Android/Sample/gradle/wrapper/gradle-wrapper.jar b/Emotion/Android/Sample/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
Binary files /dev/null and b/Emotion/Android/Sample/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/Emotion/Android/Sample/gradle/wrapper/gradle-wrapper.properties b/Emotion/Android/Sample/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..0c71e76
--- /dev/null
+++ b/Emotion/Android/Sample/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/Emotion/Android/Sample/gradlew b/Emotion/Android/Sample/gradlew
new file mode 100644
index 0000000..91a7e26
--- /dev/null
+++ b/Emotion/Android/Sample/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/Emotion/Android/Sample/gradlew.bat b/Emotion/Android/Sample/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/Emotion/Android/Sample/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/Emotion/Android/Sample/settings.gradle b/Emotion/Android/Sample/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/Emotion/Android/Sample/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/Emotion/Android/SampleScreenshots/SampleRunning1.png b/Emotion/Android/SampleScreenshots/SampleRunning1.png
new file mode 100644
index 0000000..fb201a2
Binary files /dev/null and b/Emotion/Android/SampleScreenshots/SampleRunning1.png differ
diff --git a/Emotion/Android/SampleScreenshots/SampleRunning2.png b/Emotion/Android/SampleScreenshots/SampleRunning2.png
new file mode 100644
index 0000000..05d12a9
Binary files /dev/null and b/Emotion/Android/SampleScreenshots/SampleRunning2.png differ
diff --git a/Emotion/README.md b/Emotion/README.md
index 37948ab..57eecc0 100644
--- a/Emotion/README.md
+++ b/Emotion/README.md
@@ -11,6 +11,7 @@ Getting started
To get started, select the platform for which you're developing.
- [Windows]()
+- [Android]()
Contributing
============
diff --git a/Emotion/Windows/ClientLibrary/ClientLibrary.nuspec b/Emotion/Windows/ClientLibrary/Microsoft.ProjectOxford.Emotion.nuspec
similarity index 100%
rename from Emotion/Windows/ClientLibrary/ClientLibrary.nuspec
rename to Emotion/Windows/ClientLibrary/Microsoft.ProjectOxford.Emotion.nuspec
diff --git a/Emotion/Windows/ClientLibrary/Properties/AssemblyInfo.cs b/Emotion/Windows/ClientLibrary/Properties/AssemblyInfo.cs
index 90a3fd1..fdb2121 100644
--- a/Emotion/Windows/ClientLibrary/Properties/AssemblyInfo.cs
+++ b/Emotion/Windows/ClientLibrary/Properties/AssemblyInfo.cs
@@ -62,5 +62,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.4")]
-[assembly: AssemblyFileVersion("1.0.0.4")]
\ No newline at end of file
+[assembly: AssemblyVersion("1.0.1.1")]
+[assembly: AssemblyFileVersion("1.0.1.1")]
diff --git a/Face/Android/Sample/app/src/main/res/layout/activity_main.xml b/Face/Android/Sample/app/src/main/res/layout/activity_main.xml
index cab2b7f..046ed18 100644
--- a/Face/Android/Sample/app/src/main/res/layout/activity_main.xml
+++ b/Face/Android/Sample/app/src/main/res/layout/activity_main.xml
@@ -177,4 +177,9 @@
+
diff --git a/README.md b/README.md
index 53883e1..8e5e679 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,7 @@ Project Oxford APIs are divided into three areas:
##### Language
- **[Spell Check APIS]() (Preview):** Detect and correct common and uncommon spelling errors via the Bing document index
- **[LUIS, Language Understanding Intelligent Services]() (Preview):** Understand natural language commands tailored to your application
+- **[Web Language Model APIs]() (Preview):** Leverage the power of language models trained on web-scale data
To learn more and sign-up for Project Oxford, [visit our
website](). You must obtain [subscription
diff --git a/Speech/SpeechToText/Windows/SpeechToText-WPF-Sample.sln b/Speech/SpeechToText/Windows/SpeechToText-WPF-Sample.sln
index d60ff1b..3fd3213 100644
--- a/Speech/SpeechToText/Windows/SpeechToText-WPF-Sample.sln
+++ b/Speech/SpeechToText/Windows/SpeechToText-WPF-Sample.sln
@@ -1,26 +1,20 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.23107.0
+VisualStudioVersion = 14.0.24720.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpeechToText-WPF-Sample", "samples\SpeechRecognitionServiceExample\SpeechToText-WPF-Sample.csproj", "{8380449D-B908-4AA5-BD72-EB8401205DCE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
- Debug|x86 = Debug|x86
Release|x64 = Release|x64
- Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8380449D-B908-4AA5-BD72-EB8401205DCE}.Debug|x64.ActiveCfg = Debug|x64
{8380449D-B908-4AA5-BD72-EB8401205DCE}.Debug|x64.Build.0 = Debug|x64
- {8380449D-B908-4AA5-BD72-EB8401205DCE}.Debug|x86.ActiveCfg = Debug|x86
- {8380449D-B908-4AA5-BD72-EB8401205DCE}.Debug|x86.Build.0 = Debug|x86
{8380449D-B908-4AA5-BD72-EB8401205DCE}.Release|x64.ActiveCfg = Release|x64
{8380449D-B908-4AA5-BD72-EB8401205DCE}.Release|x64.Build.0 = Release|x64
- {8380449D-B908-4AA5-BD72-EB8401205DCE}.Release|x86.ActiveCfg = Release|x86
- {8380449D-B908-4AA5-BD72-EB8401205DCE}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Video/Windows/README.md b/Video/Windows/README.md
index c0911bf..1cc0d6f 100644
--- a/Video/Windows/README.md
+++ b/Video/Windows/README.md
@@ -4,6 +4,54 @@ The client library
The Video API client library is a thin C\# client wrapper for Project Oxford Video
REST APIs.
+The sample
+==========
+
+This sample is a Windows WPF application to demonstrate the use of Project
+Oxford Video API.
+
+It demonstrates Video API by specifying a local video file.
+
+Build the sample
+----------------
+
+1. Start Microsoft Visual Studio 2015 and select File \> Open \>
+ Project/Solution.
+
+2. Starting in the folder where you clone the repository, go to Video \> Windows
+ \> Sample-WPF Folder.
+
+3. Double-click the Visual Studio 2015 Solution (.sln) file
+ VideoAPI-WPF-Samples.
+
+4. Press Ctrl+Shift+B, or select Build \> Build Solution.
+
+Run the sample
+--------------
+
+After the build is complete, press F5 to run the sample.
+
+First, you will
+need a [Microsoft Azure Account]() if you don't have one already.
+
+You must obtain a subscription key for Video API by following instructions in [Subscription
+key management]().
+
+Locate the text edit box saying "Paste your subscription key here to start" on
+the top right corner. Paste your subscription key. You can choose to persist
+your subscription key in your machine by clicking "Save Key" button. When you
+want to delete the subscription key from the machine, click "Delete Key" to
+remove it from your machine.
+
+Click on "Select Scenario" to use samples of different scenarios, and
+follow the instructions on screen.
+
+Microsoft will receive the videos you upload and may use them to improve Video
+API and related services. By submitting a video, you confirm you have consent
+from everyone in it.
+
+
+
Contributing
============
We welcome contributions and are always looking for new SDKs, input, and
diff --git a/Video/Windows/Sample-WPF/App.config b/Video/Windows/Sample-WPF/App.config
new file mode 100644
index 0000000..2d2a12d
--- /dev/null
+++ b/Video/Windows/Sample-WPF/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/Video/Windows/Sample-WPF/App.xaml b/Video/Windows/Sample-WPF/App.xaml
new file mode 100644
index 0000000..be623cc
--- /dev/null
+++ b/Video/Windows/Sample-WPF/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/Video/Windows/Sample-WPF/App.xaml.cs b/Video/Windows/Sample-WPF/App.xaml.cs
new file mode 100644
index 0000000..71ed96b
--- /dev/null
+++ b/Video/Windows/Sample-WPF/App.xaml.cs
@@ -0,0 +1,44 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// Project Oxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK-Windows
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Windows;
+
+namespace VideoAPI_WPF_Samples
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+ }
+}
diff --git a/Video/Windows/Sample-WPF/Assets/Microsoft-logo_rgb_c-gray.png b/Video/Windows/Sample-WPF/Assets/Microsoft-logo_rgb_c-gray.png
new file mode 100644
index 0000000..7846308
Binary files /dev/null and b/Video/Windows/Sample-WPF/Assets/Microsoft-logo_rgb_c-gray.png differ
diff --git a/Video/Windows/Sample-WPF/FaceTrackingPage.xaml b/Video/Windows/Sample-WPF/FaceTrackingPage.xaml
new file mode 100644
index 0000000..8ba210b
--- /dev/null
+++ b/Video/Windows/Sample-WPF/FaceTrackingPage.xaml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+ Detect and track faces in a video
+
+ Please click [Load Video] to specify the video to detect.
+
+
+ Results will be shown in right panel. For processing progress, please see the status.
+
+
+
+
+
diff --git a/Video/Windows/Sample-WPF/FaceTrackingPage.xaml.cs b/Video/Windows/Sample-WPF/FaceTrackingPage.xaml.cs
new file mode 100644
index 0000000..e542cb6
--- /dev/null
+++ b/Video/Windows/Sample-WPF/FaceTrackingPage.xaml.cs
@@ -0,0 +1,178 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// Project Oxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK-Windows
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+
+// -----------------------------------------------------------------------
+// KEY SAMPLE CODE STARTS HERE
+// Use the following namespace for VideoServiceClient
+// -----------------------------------------------------------------------
+using Microsoft.ProjectOxford.Video.Contract;
+// -----------------------------------------------------------------------
+// KEY SAMPLE CODE ENDS HERE
+// -----------------------------------------------------------------------
+
+namespace VideoAPI_WPF_Samples
+{
+ public partial class FaceTrackingPage : Page
+ {
+ private static readonly TimeSpan QueryWaitTime = TimeSpan.FromSeconds(20);
+ private const string LogIdentifier = "Face Tracking";
+
+ public FaceTrackingPage()
+ {
+ InitializeComponent();
+ Resources.Add("_internalDataContext", _dataContext);
+ }
+
+ // -----------------------------------------------------------------------
+ // KEY SAMPLE CODE STARTS HERE
+ // -----------------------------------------------------------------------
+ private async Task DetectFaces(string subscriptionKey, string filePath)
+ {
+ _dataContext.IsWorking = true;
+ _dataContext.SourceUri = null;
+ _dataContext.ResultText = null;
+
+ Helpers.Log(LogIdentifier, "Start face tracking");
+ Microsoft.ProjectOxford.Video.VideoServiceClient client =
+ new Microsoft.ProjectOxford.Video.VideoServiceClient(subscriptionKey);
+
+ using (FileStream originalStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
+ {
+ byte[] bytes = new byte[originalStream.Length];
+ await originalStream.ReadAsync(bytes, 0, (int)originalStream.Length);
+
+ // Creates a video operation of face tracking
+ Helpers.Log(LogIdentifier, "Start uploading video");
+ Operation operation = await client.CreateOperationAsync(bytes, OperationType.TrackFace);
+ Helpers.Log(LogIdentifier, "Uploading video done");
+
+ // Starts querying service status
+ OperationResult result = await client.GetOperationResultAsync(operation);
+ while (result.Status != OperationStatus.Succeeded && result.Status != OperationStatus.Failed)
+ {
+ Helpers.Log(LogIdentifier, "Server status: {0}, wait {1} seconds...", result.Status, QueryWaitTime.TotalSeconds);
+ await Task.Delay(QueryWaitTime);
+ result = await client.GetOperationResultAsync(operation);
+ }
+ Helpers.Log(LogIdentifier, "Finish processing with server status: " + result.Status);
+
+ // Processing finished, checks result
+ if (result.Status == OperationStatus.Succeeded)
+ {
+ // Gets output JSON
+ Helpers.Log(LogIdentifier, "Downloading result done");
+ _dataContext.SourceUri = new Uri(filePath);
+ _dataContext.ResultText = Helpers.FormatJson(result.ProcessingResult);
+ _dataContext.FrameHighlights = GetHighlights(result.ProcessingResult).ToList();
+ }
+ else
+ {
+ // Failed
+ Helpers.Log(LogIdentifier, "Fail reason: " + result.Message);
+ }
+ _dataContext.IsWorking = false;
+ }
+ }
+ // -----------------------------------------------------------------------
+ // KEY SAMPLE CODE ENDS HERE
+ // -----------------------------------------------------------------------
+
+ ///
+ /// This method parses the JSON output, and converts to a sequence of time frames with highlight regions. One highlight region reprensents a tracked face in the frame.
+ ///
+ /// JSON output of face tracking result.
+ /// Sequence of time frames with highlight regions.
+ private static IEnumerable GetHighlights(string json)
+ {
+ FaceTracking faceTrackingResult = Helpers.FromJson(json);
+
+ if (faceTrackingResult.FacesDetected == null) yield break;
+
+ float timescale = (float)faceTrackingResult.Timescale;
+
+ Rect invisibleRect = new Rect(new Point(0, 0), new Size(0, 0)); // Uses this rectangle if a specific face is not showing in one frame
+
+ foreach (Fragment fragment in faceTrackingResult.Fragments)
+ {
+ FaceEvent[][] events = fragment.Events;
+ if (events == null || events.Length == 0)
+ {
+ // If 'Events' is empty, there isn't any face detected in this fragment
+ Rect[] rects = new Rect[faceTrackingResult.FacesDetected.Length];
+ for (int i = 0; i < rects.Length; i++) rects[i] = invisibleRect;
+
+ yield return new FrameHighlight() {Time = fragment.Start/timescale, HighlightRects = rects};
+ }
+ else
+ {
+ long interval = fragment.Interval.GetValueOrDefault();
+ long start = fragment.Start;
+ int i = 0;
+ foreach (FaceEvent[] evt in events)
+ {
+ Rect[] rects = faceTrackingResult.FacesDetected.Select(face =>
+ {
+ FaceEvent faceRect = evt.FirstOrDefault(x => x.Id == face.FaceId);
+ if (faceRect == null) return invisibleRect;
+
+ // Creates highlight region at the location of the tracked face
+ return new Rect(new Point(faceRect.X, faceRect.Y), new Size(faceRect.Width, faceRect.Height));
+ }).ToArray();
+
+ yield return new FrameHighlight() {Time = (start + interval*i)/timescale, HighlightRects = rects};
+
+ i++;
+ }
+ }
+ }
+ }
+
+ private async void LoadImageButton_Click(object sender, RoutedEventArgs e)
+ {
+ string originalFilePath = Helpers.PickFile();
+ if (originalFilePath == null) return;
+
+ await DetectFaces(Helpers.SubscriptionKey, originalFilePath);
+ }
+
+ private readonly VideoResultDataContext _dataContext = new VideoResultDataContext();
+ }
+}
diff --git a/Video/Windows/Sample-WPF/Helpers.cs b/Video/Windows/Sample-WPF/Helpers.cs
new file mode 100644
index 0000000..e6c0a3d
--- /dev/null
+++ b/Video/Windows/Sample-WPF/Helpers.cs
@@ -0,0 +1,85 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// Project Oxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK-Windows
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Windows;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace VideoAPI_WPF_Samples
+{
+ static class Helpers
+ {
+ public static string SubscriptionKey
+ {
+ get
+ {
+ MainWindow window = (MainWindow)Application.Current.MainWindow;
+ return window._scenariosControl.SubscriptionKey;
+ }
+ }
+
+ public static string PickFile()
+ {
+ MainWindow window = (MainWindow)Application.Current.MainWindow;
+
+ Microsoft.Win32.OpenFileDialog openDlg = new Microsoft.Win32.OpenFileDialog();
+ openDlg.Filter = "Video files (*.mp4, *.mov, *.wmv)|*.mp4;*.mov;*.wmv";
+
+ bool? result = openDlg.ShowDialog(window);
+ if (!result.GetValueOrDefault(false)) return null;
+
+ return openDlg.FileName;
+ }
+
+ public static T FromJson(string json)
+ {
+ return JsonConvert.DeserializeObject(json, new JsonSerializerSettings()
+ {
+ DateFormatHandling = DateFormatHandling.IsoDateFormat,
+ NullValueHandling = NullValueHandling.Ignore,
+ ContractResolver = new CamelCasePropertyNamesContractResolver()
+ });
+ }
+
+ public static string FormatJson(string json)
+ {
+ return JsonConvert.SerializeObject(FromJson(json), Formatting.Indented);
+ }
+
+ public static void Log(string identifier, string message, params object[] args)
+ {
+ MainWindow window = (MainWindow)Application.Current.MainWindow;
+ window.Log(string.Format("[{0}] {1}", identifier, string.Format(message, args)));
+ }
+ }
+}
diff --git a/Video/Windows/Sample-WPF/MainWindow.xaml b/Video/Windows/Sample-WPF/MainWindow.xaml
new file mode 100644
index 0000000..9421c9d
--- /dev/null
+++ b/Video/Windows/Sample-WPF/MainWindow.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/Video/Windows/Sample-WPF/MainWindow.xaml.cs b/Video/Windows/Sample-WPF/MainWindow.xaml.cs
new file mode 100644
index 0000000..67d959a
--- /dev/null
+++ b/Video/Windows/Sample-WPF/MainWindow.xaml.cs
@@ -0,0 +1,73 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// Project Oxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK-Windows
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Windows;
+using SampleUserControlLibrary;
+
+namespace VideoAPI_WPF_Samples
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ public partial class MainWindow : Window
+ {
+ public SampleScenarios ScenarioControl
+ {
+ get
+ {
+ return _scenariosControl;
+ }
+ }
+
+ public MainWindow()
+ {
+ InitializeComponent();
+
+ //
+ // Initialize SampleScenarios User Control with titles and scenario pages
+ //
+ _scenariosControl.SampleTitle = "Video API";
+ _scenariosControl.SampleScenarioList = new Scenario[]
+ {
+ new Scenario { Title = "Video Stabilization", PageClass = typeof(StabilizationPage) },
+ new Scenario { Title = "Motion Tracking", PageClass = typeof(MotionDetectionPage) },
+ new Scenario { Title = "Face Tracking", PageClass = typeof(FaceTrackingPage) },
+ };
+ }
+
+ public void Log(string message)
+ {
+ _scenariosControl.Log(message);
+ }
+ }
+}
diff --git a/Video/Windows/Sample-WPF/MotionDetectionPage.xaml b/Video/Windows/Sample-WPF/MotionDetectionPage.xaml
new file mode 100644
index 0000000..433095e
--- /dev/null
+++ b/Video/Windows/Sample-WPF/MotionDetectionPage.xaml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+ Detect motion in a video
+
+ Please click [Load Video] to specify the video to detect.
+
+
+ Results will be shown in right panel. For processing progress, please see the status.
+
+
+
+
+
diff --git a/Video/Windows/Sample-WPF/MotionDetectionPage.xaml.cs b/Video/Windows/Sample-WPF/MotionDetectionPage.xaml.cs
new file mode 100644
index 0000000..e0ee132
--- /dev/null
+++ b/Video/Windows/Sample-WPF/MotionDetectionPage.xaml.cs
@@ -0,0 +1,178 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// Project Oxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK-Windows
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+
+// -----------------------------------------------------------------------
+// KEY SAMPLE CODE STARTS HERE
+// Use the following namespace for VideoServiceClient
+// -----------------------------------------------------------------------
+using Microsoft.ProjectOxford.Video.Contract;
+// -----------------------------------------------------------------------
+// KEY SAMPLE CODE ENDS HERE
+// -----------------------------------------------------------------------
+
+namespace VideoAPI_WPF_Samples
+{
+ public partial class MotionDetectionPage : Page
+ {
+ private static readonly TimeSpan QueryWaitTime = TimeSpan.FromSeconds(20);
+ private const string LogIdentifier = "Motion Detection";
+
+ public MotionDetectionPage()
+ {
+ InitializeComponent();
+ Resources.Add("_internalDataContext", _dataContext);
+ }
+
+ // -----------------------------------------------------------------------
+ // KEY SAMPLE CODE STARTS HERE
+ // -----------------------------------------------------------------------
+ private async Task DetectMotion(string subscriptionKey, string originalFilePath)
+ {
+ _dataContext.IsWorking = true;
+ _dataContext.SourceUri = null;
+ _dataContext.ResultText = null;
+
+ Helpers.Log(LogIdentifier, "Start motion detection");
+ Microsoft.ProjectOxford.Video.VideoServiceClient client =
+ new Microsoft.ProjectOxford.Video.VideoServiceClient(subscriptionKey);
+
+ using (FileStream originalStream = new FileStream(originalFilePath, FileMode.Open, FileAccess.Read))
+ {
+ byte[] bytes = new byte[originalStream.Length];
+ await originalStream.ReadAsync(bytes, 0, (int)originalStream.Length);
+
+ // Creates a video operation of motion detection
+ Helpers.Log(LogIdentifier, "Start uploading video");
+ Operation operation = await client.CreateOperationAsync(bytes, OperationType.DetectMotion);
+ Helpers.Log(LogIdentifier, "Uploading video done");
+
+ // Starts querying service status
+ OperationResult result = await client.GetOperationResultAsync(operation);
+ while (result.Status != OperationStatus.Succeeded && result.Status != OperationStatus.Failed)
+ {
+ Helpers.Log(LogIdentifier, "Server status: {0}, wait {1} seconds...", result.Status, QueryWaitTime.TotalSeconds);
+ await Task.Delay(QueryWaitTime);
+ result = await client.GetOperationResultAsync(operation);
+ }
+ Helpers.Log(LogIdentifier, "Finish processing with server status: " + result.Status);
+
+ // Processing finished, checks result
+ if (result.Status == OperationStatus.Succeeded)
+ {
+ // Gets output JSON
+ _dataContext.SourceUri = new Uri(originalFilePath);
+ _dataContext.ResultText = Helpers.FormatJson(result.ProcessingResult);
+ _dataContext.FrameHighlights = GetHighlights(result.ProcessingResult).ToList();
+ }
+ else
+ {
+ // Failed
+ Helpers.Log(LogIdentifier, "Fail reason: " + result.Message);
+ }
+ _dataContext.IsWorking = false;
+ }
+ }
+ // -----------------------------------------------------------------------
+ // KEY SAMPLE CODE ENDS HERE
+ // -----------------------------------------------------------------------
+
+ ///
+ /// This method parses the JSON ouput, and converts to a sequence of time frames with highlight region. A full video highlight is created if there is motion detected in the frame.
+ ///
+ /// JSON output of motion detection result.
+ /// Sequence of time frames with highlight regions.
+ private static IEnumerable GetHighlights(string json)
+ {
+ MotionDetectionResult motionDetectionResult = Helpers.FromJson(json);
+
+ double timescale = motionDetectionResult.Timescale;
+
+ if (motionDetectionResult.Regions == null) yield break;
+
+ List regionIds = motionDetectionResult.Regions.Select(x => x.Id).ToList();
+
+ Rect hasMotionRect = new Rect(new Point(0, 0), new Size(1, 1)); // Uses this full-frame rectangle to represent motion is detected in one frame
+ Rect noMotionRect = new Rect(new Point(0, 0), new Size(0, 0)); // Uses this empty rectangle to represent motion is not detected
+
+ foreach (Fragment fragment in motionDetectionResult.Fragments)
+ {
+ if (fragment.Events == null || fragment.Events.Length == 0)
+ {
+ // If 'Events' is empty, there isn't any motion detected in this fragment
+ Rect[] rects = new Rect[regionIds.Count];
+ for (int i = 0; i < rects.Length; i++) rects[i] = noMotionRect;
+
+ yield return new FrameHighlight() { Time = fragment.Start / timescale, HighlightRects = rects.ToArray() };
+ }
+ else
+ {
+ long interval = fragment.Interval.GetValueOrDefault();
+
+ for (int i = 0; i < fragment.Events.Length; i++)
+ {
+ double currentTime = (fragment.Start + interval*i)/timescale;
+
+ MotionEvent[] evts = fragment.Events[i];
+
+ Rect[] rects = regionIds.Select(id =>
+ {
+ MotionEvent evt = evts.FirstOrDefault(x => x.RegionId == id);
+ if (evt == null) return noMotionRect;
+ return hasMotionRect;
+ }).ToArray();
+
+ yield return new FrameHighlight() {Time = currentTime, HighlightRects = rects};
+ }
+ }
+ }
+ }
+
+ private async void LoadImageButton_Click(object sender, RoutedEventArgs e)
+ {
+ string originalFilePath = Helpers.PickFile();
+ if (originalFilePath == null) return;
+
+ await DetectMotion(Helpers.SubscriptionKey, originalFilePath);
+ }
+
+ private readonly VideoResultDataContext _dataContext = new VideoResultDataContext();
+ }
+}
diff --git a/Video/Windows/Sample-WPF/Properties/AssemblyInfo.cs b/Video/Windows/Sample-WPF/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..4ab4ab0
--- /dev/null
+++ b/Video/Windows/Sample-WPF/Properties/AssemblyInfo.cs
@@ -0,0 +1,86 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// Project Oxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK-Windows
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Microsoft.ProjectOxford.Video")]
+[assembly: AssemblyDescription("Microsoft.ProjectOxford.Video")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("Microsoft ProjectOxford")]
+[assembly: AssemblyCopyright("Copyright © 2015 Microsoft")]
+[assembly: AssemblyTrademark("Microsoft")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+//In order to begin building localizable applications, set
+//CultureYouAreCodingWith in your .csproj file
+//inside a . For example, if you are using US english
+//in your source files, set the to en-US. Then uncomment
+//the NeutralResourceLanguage attribute below. Update the "en-US" in
+//the line below to match the UICulture setting in the project file.
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
+
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Video/Windows/Sample-WPF/StabilizationPage.xaml b/Video/Windows/Sample-WPF/StabilizationPage.xaml
new file mode 100644
index 0000000..372de37
--- /dev/null
+++ b/Video/Windows/Sample-WPF/StabilizationPage.xaml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+ Stabilize a video
+
+ Please click [Load Video] to specify the video to detect.
+
+
+ Results will be shown in right panel. For processing progress, please see the status.
+
+
+
+
diff --git a/Video/Windows/Sample-WPF/StabilizationPage.xaml.cs b/Video/Windows/Sample-WPF/StabilizationPage.xaml.cs
new file mode 100644
index 0000000..f7355b5
--- /dev/null
+++ b/Video/Windows/Sample-WPF/StabilizationPage.xaml.cs
@@ -0,0 +1,140 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// Project Oxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK-Windows
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+
+// -----------------------------------------------------------------------
+// KEY SAMPLE CODE STARTS HERE
+// Use the following namespace for VideoServiceClient
+// -----------------------------------------------------------------------
+using Microsoft.ProjectOxford.Video.Contract;
+// -----------------------------------------------------------------------
+// KEY SAMPLE CODE ENDS HERE
+// -----------------------------------------------------------------------
+
+namespace VideoAPI_WPF_Samples
+{
+ public partial class StabilizationPage : Page
+ {
+ private static readonly TimeSpan QueryWaitTime = TimeSpan.FromSeconds(20);
+ private const string LogIdentifier = "Video Stabilization";
+
+ public StabilizationPage()
+ {
+ InitializeComponent();
+ Resources.Add("_internalDataContext", _dataContext);
+ }
+
+ // -----------------------------------------------------------------------
+ // KEY SAMPLE CODE STARTS HERE
+ // -----------------------------------------------------------------------
+ private async Task StabilizeVideo(string subscriptionKey, string originalFilePath)
+ {
+ _dataContext.IsWorking = true;
+ _dataContext.SourceUri = null;
+ _dataContext.ResultUri = null;
+
+ Helpers.Log(LogIdentifier, "Start stabilizing");
+ Microsoft.ProjectOxford.Video.VideoServiceClient client =
+ new Microsoft.ProjectOxford.Video.VideoServiceClient(subscriptionKey);
+
+ using (FileStream originalStream = new FileStream(originalFilePath, FileMode.Open, FileAccess.Read))
+ {
+ byte[] bytes = new byte[originalStream.Length];
+ await originalStream.ReadAsync(bytes, 0, (int) originalStream.Length);
+
+ // Creates a video operation of video stabilization
+ Helpers.Log(LogIdentifier, "Start uploading video");
+ Operation operation = await client.CreateOperationAsync(bytes, OperationType.Stabilize);
+ Helpers.Log(LogIdentifier, "Uploading video done");
+
+ // Starts querying service status
+ OperationResult result = await client.GetOperationResultAsync(operation);
+ while (result.Status != OperationStatus.Succeeded && result.Status != OperationStatus.Failed)
+ {
+ Helpers.Log(LogIdentifier, "Server status: {0}, wait {1} seconds...", result.Status, QueryWaitTime.TotalSeconds);
+ await Task.Delay(QueryWaitTime);
+ result = await client.GetOperationResultAsync(operation);
+ }
+ Helpers.Log(LogIdentifier, "Finish processing with server status: " + result.Status);
+
+ // Processing finished, checks result
+ if (result.Status == OperationStatus.Succeeded)
+ {
+ // Downloads generated video
+ string tmpFilePath = Path.GetTempFileName() + Path.GetExtension(originalFilePath);
+ Helpers.Log(LogIdentifier, "Start downloading result video");
+ using (Stream resultStream = await client.GetResultVideoAsync(result.ResourceLocation))
+ using (FileStream stream = new FileStream(tmpFilePath, FileMode.Create))
+ {
+ byte[] b = new byte[2048];
+ int length = 0;
+ while ((length = await resultStream.ReadAsync(b, 0, b.Length)) > 0)
+ {
+ await stream.WriteAsync(b, 0, length);
+ }
+ }
+ Helpers.Log(LogIdentifier, "Downloading result video done");
+
+ _dataContext.SourceUri = new Uri(originalFilePath);
+ _dataContext.ResultUri = new Uri(tmpFilePath);
+ }
+ else
+ {
+ // Failed
+ Helpers.Log(LogIdentifier, "Fail reason: " + result.Message);
+ }
+
+ _dataContext.IsWorking = false;
+ }
+ }
+
+ // -----------------------------------------------------------------------
+ // KEY SAMPLE CODE ENDS HERE
+ // -----------------------------------------------------------------------
+
+ private async void LoadImageButton_Click(object sender, RoutedEventArgs e)
+ {
+ string originalFilePath = Helpers.PickFile();
+ if (originalFilePath == null) return;
+
+ await StabilizeVideo(Helpers.SubscriptionKey, originalFilePath);
+ }
+
+ private readonly VideoResultDataContext _dataContext = new VideoResultDataContext();
+ }
+}
diff --git a/Video/Windows/Sample-WPF/VideoAPI-WPF-Samples.csproj b/Video/Windows/Sample-WPF/VideoAPI-WPF-Samples.csproj
new file mode 100644
index 0000000..da6bf4b
--- /dev/null
+++ b/Video/Windows/Sample-WPF/VideoAPI-WPF-Samples.csproj
@@ -0,0 +1,137 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {06BA103B-2A6C-4A9F-85EE-CF0DFB7C6042}
+ WinExe
+ Properties
+ VideoAPI_WPF_Samples
+ VideoAPI-WPF-Samples
+ v4.6
+ 512
+ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 4
+ true
+
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll
+ True
+
+
+
+
+
+
+
+
+
+
+ 4.0
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ FaceTrackingPage.xaml
+
+
+ MotionDetectionPage.xaml
+
+
+
+ VideoResultControl.xaml
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ App.xaml
+ Code
+
+
+ StabilizationPage.xaml
+
+
+ MainWindow.xaml
+ Code
+
+
+ Designer
+ MSBuild:Compile
+
+
+
+
+ Code
+
+
+
+
+
+
+
+
+
+ {735929f0-f8f1-4d93-b027-5d034fa7892b}
+ SampleUserControlLibrary
+
+
+ {5714988e-4873-4c64-8cad-852381e59a95}
+ Video.SDK
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Video/Windows/Sample-WPF/VideoAPI-WPF-Samples.sln b/Video/Windows/Sample-WPF/VideoAPI-WPF-Samples.sln
new file mode 100644
index 0000000..326878a
--- /dev/null
+++ b/Video/Windows/Sample-WPF/VideoAPI-WPF-Samples.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.24720.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleUserControlLibrary", "..\..\..\SampleUtil\Windows\SampleUserControlLibrary\SampleUserControlLibrary.csproj", "{735929F0-F8F1-4D93-B027-5D034FA7892B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VideoAPI-WPF-Samples", "VideoAPI-WPF-Samples.csproj", "{06BA103B-2A6C-4A9F-85EE-CF0DFB7C6042}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Video.SDK", "..\ClientLibrary\Video.SDK.csproj", "{5714988E-4873-4C64-8CAD-852381E59A95}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {735929F0-F8F1-4D93-B027-5D034FA7892B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {735929F0-F8F1-4D93-B027-5D034FA7892B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {735929F0-F8F1-4D93-B027-5D034FA7892B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {735929F0-F8F1-4D93-B027-5D034FA7892B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {06BA103B-2A6C-4A9F-85EE-CF0DFB7C6042}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {06BA103B-2A6C-4A9F-85EE-CF0DFB7C6042}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {06BA103B-2A6C-4A9F-85EE-CF0DFB7C6042}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {06BA103B-2A6C-4A9F-85EE-CF0DFB7C6042}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5714988E-4873-4C64-8CAD-852381E59A95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5714988E-4873-4C64-8CAD-852381E59A95}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5714988E-4873-4C64-8CAD-852381E59A95}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5714988E-4873-4C64-8CAD-852381E59A95}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/Video/Windows/Sample-WPF/VideoResultControl.xaml b/Video/Windows/Sample-WPF/VideoResultControl.xaml
new file mode 100644
index 0000000..b856de1
--- /dev/null
+++ b/Video/Windows/Sample-WPF/VideoResultControl.xaml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Original:
+ Result:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Video/Windows/Sample-WPF/VideoResultControl.xaml.cs b/Video/Windows/Sample-WPF/VideoResultControl.xaml.cs
new file mode 100644
index 0000000..bd06669
--- /dev/null
+++ b/Video/Windows/Sample-WPF/VideoResultControl.xaml.cs
@@ -0,0 +1,272 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// Project Oxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK-Windows
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Media;
+using System.Windows.Shapes;
+using System.Windows.Threading;
+
+namespace VideoAPI_WPF_Samples
+{
+ ///
+ /// Interaction logic for VideoResultControl.xaml
+ ///
+ public partial class VideoResultControl : UserControl
+ {
+ private static readonly Brush HighlightRectangleBrush = new SolidColorBrush(Color.FromRgb(255, 0, 0));
+ private static readonly int HighlightRectangleThickness = 2;
+ private static readonly TimeSpan HighlightTimerInterval = TimeSpan.FromSeconds(0.03);
+
+
+ public static DependencyProperty SourceUriProperty =
+ DependencyProperty.Register("SourceUri", typeof(Uri), typeof(VideoResultControl), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, VideoSourceChanged));
+ public static DependencyProperty ResultUriProperty =
+ DependencyProperty.Register("ResultUri", typeof(Uri), typeof(VideoResultControl));
+ public static DependencyProperty ResultTextProperty =
+ DependencyProperty.Register("ResultText", typeof(string), typeof(VideoResultControl));
+ public static DependencyProperty IsWorkingProperty =
+ DependencyProperty.Register("IsWorking", typeof(bool), typeof(VideoResultControl));
+ public static DependencyProperty IsVideoResultProperty =
+ DependencyProperty.Register("IsVideoResult", typeof(bool), typeof(VideoResultControl));
+ public static DependencyProperty FrameHighlightsProperty =
+ DependencyProperty.Register("FrameHighlights", typeof(IList), typeof(VideoResultControl));
+
+ private readonly DispatcherTimer _highlightTimer = new DispatcherTimer();
+ private int _highlightIndex;
+
+ public VideoResultControl()
+ {
+ InitializeComponent();
+
+ _highlightTimer.Interval = HighlightTimerInterval;
+ _highlightTimer.Tick += HighlightTimerOnTick;
+ }
+
+ ///
+ /// Represents the video Uri of orginal video player (left panel)
+ ///
+ public Uri SourceUri
+ {
+ get { return (Uri)GetValue(SourceUriProperty); }
+ set { SetValue(SourceUriProperty, value); }
+ }
+
+ ///
+ /// Represents the video Uri of result video player (right panel)
+ ///
+ public Uri ResultUri
+ {
+ get { return (Uri)GetValue(ResultUriProperty); }
+ set { SetValue(ResultUriProperty, value); }
+ }
+
+ ///
+ /// Represents the text of result text box (right panel)
+ ///
+ public string ResultText
+ {
+ get { return (string)GetValue(ResultTextProperty); }
+ set { SetValue(ResultTextProperty, value); }
+ }
+
+ ///
+ /// Indicates whether to show a waiting screen and disable all operations
+ ///
+ public bool IsWorking
+ {
+ get { return (bool)GetValue(IsWorkingProperty); }
+ set { SetValue(IsWorkingProperty, value); }
+ }
+
+ ///
+ /// Indicates whether to show result video player, otherwise, result text box is shown
+ ///
+ public bool IsVideoResult
+ {
+ get { return (bool)GetValue(IsVideoResultProperty); }
+ set { SetValue(IsVideoResultProperty, value); }
+ }
+
+ ///
+ /// A sequence of time frames with highlight information, which will be used to draw during video playing
+ ///
+ public IList FrameHighlights
+ {
+ get { return (IList)GetValue(FrameHighlightsProperty); }
+ set { SetValue(FrameHighlightsProperty, value); }
+ }
+
+ private void ButtonPlay_OnClick(object sender, RoutedEventArgs e)
+ {
+ StopVideo();
+ PlayVideo();
+ }
+
+ private void ButtonStop_OnClick(object sender, RoutedEventArgs e)
+ {
+ StopVideo();
+ }
+
+ private void PlayVideo()
+ {
+ originalVideo.Play();
+ resultVideo.Play();
+
+ Debug.Assert(!_highlightTimer.IsEnabled);
+
+ if (FrameHighlights != null && FrameHighlights.Count > 0)
+ {
+ // Creates highlight controls before playing
+ int numOfRects = FrameHighlights.First().HighlightRects.Length;
+
+ for (int i = 0; i < numOfRects; i++)
+ {
+ rectangleAreas.Children.Add(new Rectangle()
+ {
+ Visibility = Visibility.Hidden,
+ StrokeThickness = HighlightRectangleThickness,
+ Stroke = HighlightRectangleBrush
+ });
+ }
+
+ _highlightIndex = 0;
+ _highlightTimer.Start();
+ }
+ }
+
+ private void StopVideo()
+ {
+ originalVideo.Stop();
+ resultVideo.Stop();
+ _highlightTimer.Stop();
+ rectangleAreas.Children.Clear();
+ }
+
+ private void HighlightTimerOnTick(object sender, EventArgs eventArgs)
+ {
+ double currentSecond = originalVideo.Position.TotalSeconds;
+
+ // finds the neareast time frame
+ while (_highlightIndex < FrameHighlights.Count && FrameHighlights[_highlightIndex].Time <= currentSecond)
+ _highlightIndex++;
+
+ if (_highlightIndex > FrameHighlights.Count) return;
+ if (_highlightIndex == 0) return;
+
+ Rect[] positions = FrameHighlights[_highlightIndex - 1].HighlightRects;
+ for (int i = 0; i < positions.Length; i++)
+ {
+ // relayouts highlight controls based on video player size
+ Rectangle rect = (Rectangle) rectangleAreas.Children[i];
+ rect.Visibility = Visibility.Hidden;
+
+ double w = originalVideoHolder.ActualWidth;
+ double h = originalVideoHolder.ActualHeight;
+ double vw = (double) originalVideo.NaturalVideoWidth;
+ double vh = (double) originalVideo.NaturalVideoHeight;
+
+ if (h > 0 && vh > 0)
+ {
+ double vr = vw/vh;
+ double offsetX = Math.Max((w - h*vr)/2, 0);
+ double offsetY = Math.Max((h - w/vr)/2, 0);
+
+ double realWidth = w - 2*offsetX;
+ double realHeight = h - 2*offsetY;
+
+ Canvas.SetLeft(rect, offsetX + positions[i].X*realWidth);
+ Canvas.SetTop(rect, offsetY + positions[i].Y*realHeight);
+ rect.Width = positions[i].Width*realWidth;
+ rect.Height = positions[i].Height*realHeight;
+ rect.Visibility = Visibility.Visible;
+ }
+ }
+ }
+
+ private static void VideoSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
+ {
+ VideoResultControl thiz = (VideoResultControl)obj;
+ thiz.StopVideo();
+ }
+ }
+
+ public class BoolToVisibilityConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType,
+ object parameter, CultureInfo culture)
+ {
+ return (value is bool && (bool)value) ? Visibility.Visible : Visibility.Hidden;
+ }
+
+ public object ConvertBack(object value, Type targetType,
+ object parameter, CultureInfo culture)
+ {
+ return ((Visibility)value == Visibility.Visible);
+ }
+ }
+
+ public class InvertBoolToVisibilityConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType,
+ object parameter, CultureInfo culture)
+ {
+ return (value is bool && (bool)value) ? Visibility.Hidden : Visibility.Visible;
+ }
+
+ public object ConvertBack(object value, Type targetType,
+ object parameter, CultureInfo culture)
+ {
+ return ((Visibility)value != Visibility.Visible);
+ }
+ }
+
+ public class FrameHighlight
+ {
+ ///
+ /// Start time (in seconds) of the frame
+ ///
+ public double Time { get; set; }
+
+ ///
+ /// Rectangles of highlight regions in the frame
+ ///
+ public Rect[] HighlightRects { get; set; }
+ }
+}
diff --git a/Video/Windows/Sample-WPF/VideoResultDataContext.cs b/Video/Windows/Sample-WPF/VideoResultDataContext.cs
new file mode 100644
index 0000000..5c53029
--- /dev/null
+++ b/Video/Windows/Sample-WPF/VideoResultDataContext.cs
@@ -0,0 +1,101 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+//
+// Project Oxford: http://ProjectOxford.ai
+//
+// Project Oxford SDK Github:
+// https://github.com/Microsoft/ProjectOxfordSDK-Windows
+//
+// Copyright (c) Microsoft Corporation
+// All rights reserved.
+//
+// MIT License:
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace VideoAPI_WPF_Samples
+{
+ class VideoResultDataContext : INotifyPropertyChanged
+ {
+ ///
+ /// Uri of original video
+ ///
+ public Uri SourceUri
+ {
+ get { return _sourceUri; }
+ set { _sourceUri = value; OnPropertyChanged(nameof(SourceUri)); }
+ }
+
+ ///
+ /// Uri of output video
+ ///
+ public Uri ResultUri
+ {
+ get { return _resultUri; }
+ set { _resultUri = value; OnPropertyChanged(nameof(ResultUri)); }
+ }
+
+ ///
+ /// Indicates whether service is working
+ ///
+ public bool IsWorking
+ {
+ get { return _isWorking; }
+ set { _isWorking = value; OnPropertyChanged(nameof(IsWorking)); }
+ }
+
+ ///
+ /// Stores result text from service
+ ///
+ public string ResultText
+ {
+ get { return _resultText; }
+ set { _resultText = value; OnPropertyChanged(nameof(ResultText)); }
+ }
+
+ ///
+ /// Uses this structure to display highlight rectangles at a specific time
+ ///
+ public IList FrameHighlights
+ {
+ get { return _frameHighlights; }
+ set { _frameHighlights = value; OnPropertyChanged(nameof(FrameHighlights)); }
+ }
+
+
+ private Uri _sourceUri;
+ private Uri _resultUri;
+ private bool _isWorking;
+ private string _resultText;
+ private IList _frameHighlights;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected void OnPropertyChanged(string propertyName)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
diff --git a/Video/Windows/Sample-WPF/packages.config b/Video/Windows/Sample-WPF/packages.config
new file mode 100644
index 0000000..8857349
--- /dev/null
+++ b/Video/Windows/Sample-WPF/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/Video/Windows/SampleScreenshots/SampleRunning.png b/Video/Windows/SampleScreenshots/SampleRunning.png
new file mode 100644
index 0000000..8dd96ce
Binary files /dev/null and b/Video/Windows/SampleScreenshots/SampleRunning.png differ
diff --git a/Vision/Android/Sample/app/src/main/res/layout/activity_main.xml b/Vision/Android/Sample/app/src/main/res/layout/activity_main.xml
index 2a1f1bd..6e1cd54 100644
--- a/Vision/Android/Sample/app/src/main/res/layout/activity_main.xml
+++ b/Vision/Android/Sample/app/src/main/res/layout/activity_main.xml
@@ -35,6 +35,11 @@
android:layout_gravity="center_horizontal"
android:onClick="activityThumbnail" />
+
diff --git a/Vision/Windows/Sample-Console/Sample/Program.cs b/Vision/Windows/Sample-Console/Sample/Program.cs
index de145d2..5b79d2d 100644
--- a/Vision/Windows/Sample-Console/Sample/Program.cs
+++ b/Vision/Windows/Sample-Console/Sample/Program.cs
@@ -35,7 +35,7 @@
using System.Configuration;
using System.IO;
-namespace Microsoft.ProjectOXford.Vision.Sample
+namespace Microsoft.ProjectOxford.Vision.Sample
{
///
diff --git a/Vision/Windows/Sample-Console/Sample/VisionHelper.cs b/Vision/Windows/Sample-Console/Sample/VisionHelper.cs
index 0f33b0c..939f01d 100644
--- a/Vision/Windows/Sample-Console/Sample/VisionHelper.cs
+++ b/Vision/Windows/Sample-Console/Sample/VisionHelper.cs
@@ -38,7 +38,7 @@
using Microsoft.ProjectOxford.Vision;
using Microsoft.ProjectOxford.Vision.Contract;
-namespace Microsoft.ProjectOXford.Vision.Sample
+namespace Microsoft.ProjectOxford.Vision.Sample
{
///