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 @@ + + + + + + + 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 { ///