Click here to Skip to main content
Click here to Skip to main content

Azure Animal Adoption Agent and Lost & Found

By , 11 May 2013
 

Please note

This article is an entry in our Windows Azure Developer Challenge. Articles in this sub-section are not required to be full articles so care should be taken when voting.

NOTE: Learn how to do convenient Two-way data binding to an enumerated property in a Windows Phone app!  (See below after the system architecture diagram).  

(Windows Azure Contest Entry)   

Table of Contents

Phase 1 - Project Overview

Why Azure?  

Azure has both software, platform, and infrastructure as a service features, making it the ultimate solution for creating high performance, reliable,  ubiquitous client/server solutions.  The Azure platform eliminates the vast amount of time and cost normally involved in distributed database application development,  allowing a developer like myself to concentrate my time on creating features & value for my users instead of wasting it on support code and infrastructure. 

(Note: I already have an Azure account via MSDN

Introduction   

Azure is the perfect platform for coordinating the search and notification services vital to the creation of a pet adoption agent that connects potential pet owners with the countless adorable kittens & puppies just waiting to give love.  By providing cloud based, easy to configure SQL storage Azure is perfect for storing the search criteria specified by eager pet parents-to-be via the FindAPet client app running on their Windows Phone.  Then Azure Mobile Services is leveraged to send out push notifications to them when pets that meet their stored preferences become available at animal shelters throughout the USA.  The PetFinder API provides ready access to an inventory of adoptable pets of nearly every type and breed and direct access to the animal shelters that house them.  It also provides the necessary animal and shelter search facilities necessary to complete the system, a system that will save the lives of countless innocent loving animals when it's finished.

An auxiliary feature of the system will be a Lost & Found service.  Owners of lost pets can enter a description of the pet they lost.  In a manner similar to Google Alerts, the system will run periodic sweeps of shelters within a 50 mile radius of the owner's home location.  If a pet matching the owner's description is found, a push notification will be sent to the owner with a link to the shelter that is holding the pet.  (Thank you Simon Jackson for the Lost & Found idea!

Overview 

The entry point into the system will be the FindAPet app (client) running on the Windows Phone platform using the MVVM Light framework.  It will provide a simple search interface that helps them find the kind of pet they are interested in adopting.  The screens below show a sample session where the user is looking to adopt a Calico kitten

The system then shows them a list of animal shelters nearby them.  Using the phone's Geolocation services the user's current location is used as the center point for a proximity search, as shown in the screenshot below where Boise, Idaho was identified as the the user's current location.  The user checks off the shelters that they are willing to travel to:

Select Shelters 

The user's pet and shelter preferences are stored in an Azure hosted SQL table.  As pets at the designated shelters become available, a push notification is sent to the user with the prospective pet's details and photo for easy viewing.  If they like the pet and are considering adopting it, they can add the pet to their wishlist, as shown in the screen shot below (note an adult cat is shown too since the user selected it in a search where the specified age was adult):

  

Push notifications will also act as reminders to notify the user when one of the candidate pets is close to their termination date, to make sure they don't miss the opportunity to save a life while finding a lifelong companion.  Finally, the system will leverage the Nokia Here Maps service to give them directions to the shelter where the chosen pet is at, so they can pick up their new loved one and return home together to share many happy memories.  

System Architecture


 

Two-way data binding to an enumerated property without creating new value converters or creating auxiliary properties (MVVM Light)!  

Code Project is about source code and so is this contest so I am including the source code for a helpful utility class that I created while working on the FindAPet Windows Phone client.  I became tired of creating auxiliary properties to provide types that were easy to data bind to, just to expose an enumerated property for binding.  Just as tedious was creating a new value converter for each enumeration type.  The attached source file gives you a class you can drop into your MVVM Light C# project.  The code provides a generic value converter for enumerated types so you can create the binding completely through the use of the Create New Data Binding dialog options without having to add any support code to make it happen.

With this code you now have a value converter for any ViewModel enumerated property that does two-way conversion between the Description attribute strings for the enumerated values and the enumeration constants they represent. This allows you to do two-way data binds to an enumerated property without having to do any plumbing code.  The only thing you have to do is set the Converter field to EnumToDescAttrConverter and put the fully qualified type name for the Enumeration type of the bound property in the ConverterParameter field (see picture below for an example).  If you have trouble determining the correct fully qualified Enum type name, just set a break point in the ConvertBack() method where an Exception will be thrown. Then call System.Reflection.Assembly.GetExecutingAssembly().DefinedTypes.ToList() in the Immediate Window to get a list of all currently defined System types in that execution context.  Find the correct fully qualified type name and paste it into the ConverterParameter field. 

Here's how it works.  Whenever the view accesses the bound enumerated property, EnumToDescAttrConverter gets the description attribute for the enum when its Convert() method is called:

// Consumer wants to convert an enum to a description 
//  attribute string.
public object Convert(
                 object value, 
                 Type targetType, 
                 object parameter, 
                 CultureInfo culture)
{
    // Since we don't know what the correct default 
    //  value should be, a NULL value is unacceptable.
    if (value == null)
        throw new ArgumentNullException(
            "(EnumToDescAttrConverter:Convert) The value is unassigned.");

    Enum e = (Enum)value;

    return e.GetDescription();
}

This gives list boxes and other view elements a nice human readable string to work with.  Now what happens when the view wants to update  an enumerated property with a new value?  For example, the user makes a List Box selection thus triggering a property set call because the List Box's SelectedItem property is bound to the enumerated property.  This is where the fully qualified type name entered as the ConverterParameter comes into play.  

The  EnumToDescAttrConverter.ConvertBack() method uses the fully qualified type name passed via the Parameter parameter to create the correctly typed concrete enum value using Reflection.  It then searches the description attributes for that enum type to find the enum value associated with the description given in the value parameter, and returns that enum value. 

// Convert an enumeration value in Description attribute form back to the appropriate enum value.
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
    // Since we don't know what the correct default value should be, a NULL value is unacceptable.
    if (value == null)
        throw new ArgumentNullException(
        	"(EnumToDescAttrConverter:ConvertBack) The value is unassigned.");

    string strValue = (string)value;

    // Parameter parameter must be set since it must contain the concrete Enum class name.
    if (parameter == null)
        throw new ArgumentNullException(
        	"(EnumToDescAttrConverter:ConvertBack) The Parameter parameter is unassigned.");

    string theEnumClassName = parameter.ToString();

    // Create an instance of the concrete enumeration class from the given class name.
    Enum e = (Enum)System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(theEnumClassName);

    if (e == null)
        throw new ArgumentException(
            "(EnumToDescAttrConverter:ConvertBack) Invalid enumeration class name: " 
            + theEnumClassName
            + ". Set a break point here and call "
            + "System.Reflection.Assembly.GetExecutingAssembly().DefinedTypes.ToList()"
            + " in the immediate window to find the right type.  Put that type into "
            + "the Converter parameter for the data bound element you are working with."
            );

    System.Type theEnumType = e.GetType();

    Enum eRet = null;

    // Now search for the enum value that is associated with the given description.
    foreach (MemberInfo memInfo in theEnumType.GetMembers())
    {
        object[] attrs = memInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);

        if (attrs != null && attrs.Length > 0)
        {
            if (((DescriptionAttribute)attrs[0]).Description == strValue)
            {
                // Ignore the case
                eRet = (Enum)Enum.Parse(theEnumType, memInfo.Name, true);
                break; // Found it.
            }
        }
    } // foreach (MemberInfo memInfo in typeof(TEnum).GetMembers())

    // If the string can not be converted to a valid enum value, throw an
    //  Exception.
    if (eRet == null)
        throw new ArgumentException(
            String.Format("{0} can not be converted to an enum value: ", strValue));

    return eRet;
}

Note: Use the ToDescriptionsList<>() method to conveniently grab the description attributes from an Enum  type to fill a List Box or other similar element.  Put the call to it in the property that returns a list of human friendly strings to the UI element bound to the enumerated property.  (For example, the ItemsSource property of a list box):

public static List<string> ToDescriptionsList<t>()
{
    // GetValues() is not available on Windows Phone.
    // return Enum.GetValues(typeof(T)).Cast<t>();
    List<string> listRet = new List<string>();

    foreach (var x in typeof(T).GetFields())
    {
        Enum e;

        if (x.IsLiteral)
        {
            e = (Enum)x.GetValue(typeof(Enum));

            listRet.Add(e.GetDescription());
        } // if (x.IsLiteral)
    } // foreach()

    return listRet;
}

Below is the enumerated type that represents the different kinds of animals the PetFinder API can search for.  This is an example of using the Description attribute to tie a human friendly string to an enum value: 

   

     // Example of an Enumerated type with Description Attributes
        // (barnyard, bird, cat, dog, horse, pig, reptile, smallfurry)
        // List of Animal types the breed list method accepts.  
        public enum EnumAnimalType
        {
            [Description("Barnyard")]
            barnyard,
            [Description("Birds")]
            bird,
            [Description("Cats & Kittens")]
            cat,
            [Description("Dogs & Puppies")]
            dog,
            [Description("Horses & Ponies")]
            horse,
            [Description("Pigs")]
            pig,
            [Description("Reptiles")]
            reptile,
            [Description("Other Small & Furry")]
            smallfurry
        }

Example of a Two-way data binding using the generic converter class, with the fully qualified type name I discovered via the Immediate Windows: 


 

Phase 2 - Build and Deploy an Azure Web Site Fast with WebMatrix 3 

Speed! I built and deployed this entire Lost & Found web site that can detect a cat's face in a photo in only one day!  Even better, with WebMatrix 3's Azure support you don't have to use TFS, GitHub, or any other repository to deploy your site.  Just edit your site locally and when you are ready, hit the Publish key!   You can even edit a remote site on Azure directly if you want! 

Also, the web site described in this article owes much to the wonderful KittyDar project, which is the source of the cat face detection technology used by the web site to ensure the quality of uploaded Lost & Found photos.   The web site has been live for a week now.  You can experiment with the web site and the cat face detection technology using the link below.  You do not need to register to use the web site, only to upload cat photos of your own:

FindAPet Lost & Found Web Site 

One of Azure's biggest benefits is the ability to create and deploy professional web sites at breakneck speed.   With WebMatrix 3's seamless integration with Azure, in most cases all you have to do to publish your web site to Azure or update it is to press the Publish button in WebMatrix 3!  In this section of my Code Project article for the Azure Developer Challenge, I will show you how I built a web site that accepts photos of a lost cat from worried pet owners, and then checks the quality of the photo to make sure it is suitable for use by others to identifying their cat easily.  You will find the WebMatrix 3 project replacement files attached as a download to this article.  The instructions for using them follow below.  

IMPORTANT: Remember to update the ReCAPTCHA keys in the source with the keys that belong to you, otherwise you will get strange errors on the account registration page! I have removed my keys and restored the placeholder strings PUBLIC_KEY and PRIVATE_KEY as per the Photo Gallery tutorial (covered later in the article).  Follow that tutorial's instructions carefully regarding the ReCAPTCHA keys and you will not have any problems using CAPTCHA on your site. 

Detecting Cat Faces and The Lost & Found Web Site

As mentioned in the project overview, the Azure Animal Adoption Agent will have a Lost & Found component that allows worried owners of missing pets to upload pictures of their loved ones, to help others find them.  The site will also be available to pet shelters so they can scan pictures of pets whose owners live close to the shelter, to see if they have picked up a stray that belongs to one of those owners.  The shelter employee can then contact the pet owner to give them the happy news.

Thanks to the KittyDar Cat Detector project, cat owners will have an advantage.  Since KittyDar only detects cat faces if the cat is looking at the camera in a portrait orientation, it can be used to assist cat owners in submitting a photo that is as helpful as possible in identifying their pet.  This stops the owner from uploading a photo that may have sentimental value but isn't very useful for identifying the cat.  In addition, KittyDar can detect if there are multiple cat faces in the photo.  In that case the owner will be instructed to use a different photo that only has their cat in the shot.  

Here are some example photos from the FindAPet web site that illustrate the benefits of the KittyDar cat face detector.  The red bounding rectangle shown in photos where a cat's face was detected successfully shows the location of the cat's face as determined by the detection engine: 

Photo 1 - Single cat face detected.  Photo is suitable for Lost & Found purposes

Photo 2 - Detection Failed.  Cat's Face is not Oriented Properly.  Unsuitable for Lost & Found Purposes 

KittyDar Detection Failed 

Photo 3 - Detection Succeeded but Too Many Cats.  Unsuitable for Lost & Found Purposes

KittyDar Too Many Cats 

WebMatrix 3 - Power Tool for Rapid Web Site Deployment with Azure 

WebMatrix 3 is the latest release of Microsoft's tool for rapid web site development.  The most powerful feature of this release is its close integration with Azure.  This allows you to create and deploy web sites effortlessly, even sites backed by databases like the FindAPet Lost & Found Cat web site.  With the right starting template WebMatrix 3 will  handle all the tedious tasks involved in creating and configuring your site on Azure.   

As the basis for my Lost & Found web site I used the WebMatrix 3 Photo Gallery template.  The attached download gives you everything you need to recreate the Lost & Found Cat web site on your Azure account.  But do read the sections below where I explain the various places I modified the code generated by the Photo Gallery Template.  You will learn the important locations of the code where you may want to make changes to adapt the project for your own needs.  You will also learn about a few fixes I needed to make to the default template code to make it work right.  If these fixes are not part of the template code at the time you read this, you can use this knowledge to save time implementing your own project based on the Photo Gallery Template.  If you just want to create your own KittyDar Lost & Found web site and are not interested in the details, then skip the Long Form Explanation section below and jump to the Short Form Explanation. 

Here are the detailed steps I took to create the web site. 


Long Form Explanation  

Detailed Steps To Reproduce the Lost & Found Photo Gallery with Cat Face Detector

IMPORTANT: Remember to update the ReCAPTCHA keys in Register.cshtml located in the Account folder with the keys that belong to you!  Replace the PUBLIC_KEY and PRIVATE_KEY string constants with your ReCAPTCHA keys as per the Photo Gallery tutorial.  Follow that tutorial's instructions carefully on the keys and you will not have any problems using CAPTCHA on your site.   

 Step 1 

Follow the instructions in this tutorial to get the WebMatrix 3 Photo Gallery template up and running:  

WebMatrix 3 Photo Gallery Tutorial 

At the time this was written, the error message for an unsuccessful CAPTCHA attempt displays before a CAPTCHA attempt is even made.  To correct this, replace the line in Register.cshtml that says:  

@Html.ValidationSummary("Account creation was not successful. Please correct the errors and try again.", excludeFieldErrors: true, htmlAttributes: null)
with
@if(IsPost == true )
{
    @Html.ValidationSummary( "Account creation was not successful. Please correct the errors and try again.", excludeFieldErrors: true, htmlAttributes: null)
}

 Step 2 

At the time this was written, the Photo Gallery template was referencing an old version of JQuery in the site layout file, _SiteLayout.cshtml.  If this is still the case, open that file and replace the line SCRIPT element in the HEAD element that references version 1.8.2 with the following snippet: 

<!-- The line below references an old version of jquery and was commented out -->
<!-- <script src="~/Scripts/jquery-1.8.2.min.js"></script>-->
<!-- The line references the newer version of jquery included in the Photo Gallery template -->
<script src="~/Scripts/jquery-2.0.0.min.js"></script>

 Step 3 

Publish the Photo Gallery web site to your Azure account and inspect it.  You should now be ready for the real fun.

 Step 3

Add the KittyDar Javascript files to your WebMatrix 3 project Scripts folder.  You can find these files in the Scripts folder found in the download attached to this article:

  • kittydar-0.1.0.min.js 
  • kittydar-0.1.6.js 
  • kittydar-demo.js 
  • kittydar-detection-worker.js 
These files provide the code necessary to detect cat faces in photos and to draw the bounding rectangles  on the photo while detection is taking place and after a detection has succeeded.

 Step 4 

 Open the Upload.cshtml web page file in WebMatrix and replace this header element:

<h1> Upload Lost Cat Photo </h1>
with
<h1>Upload Lost Cat Photo</h1>

<p>
    The photo or your lost cat that you upload will be placed in the
    <a class ="italic" href ="~/View/ @galleryId " title ="@ gallery.Name"> @gallery.Name </a> gallery.
</p>
<p> The photo needs to be a picture of your cat looking at the camera, preferably straight ahead and not looking up, down, or to the side.</p>

 Step 5 

Add the following snippet to View.cshtml which is found in the Photo folder, the web page that displays a single photo.  It adds the KittyDar support scripts to this page:

@section Head {
    <!-- KittyDar cat face detector script files: https://github.com/harthur/kittydar -->
    <script src ="~/Scripts/kittydar-demo.js"></script>
    <script src ="~/Scripts/kittydar-0.1.6.js"></script>
    <!-- CSS to format and align properly the elements used to show the KittyDar operation
         annotations and result. -->
    <link rel ="stylesheet" href ="kitydar.css"/>   
}

 Step 6 

Find the image element for the large photo.  Replace the existing IMG tag element with the snippet below.  This snippet is where the photo is displayed and also the bounding boxes and annotations generated by the KittyDar detection engine.  As KittyDar identifies the cat's face, you will see bounding boxes drawn on the image canvas until the face is found and the final bounding box is drawn, or the operation fails and the canvas will be cleared of bounding boxes.  These elements are managed by the kittydar-demo.js and kittydar-detection-worker.js scripts.  The inline HTML comments explain what each document element does: 

<!-- <img class="large-photo" alt="@Html.AttributeEncode(photo.FileTitle)" id="kittydar-viewer" src="@Href("~/Photo/Thumbnail", photo.Id, new { size="large" })" /> -->
    <div id ="viewer-container">
        <!-- This is where the uploaded cat photo is displayed.  It is also where any bounding rectangles that display the cat face detector's progress and final result are displayed -->
        <div class="kittydar-viewer" id ="kittydar-viewer"></div>
        <div id="viewer">
            <canvas id="preview">
            </canvas>
            <canvas id="annotations">
            </canvas>
        </div>
        <!-- This is where progress messages from the KittyDar detector are displayed as it searches for a cat face in the uploaded picture. -->
        <div class="kittydar-progress" id ="kittydar-progress">(none)</div>
    </div>
    <div id ="detection-result">
        <!-- This is where the result of the detection will be displayed, whether it succeeded or failed. -->
        <div class="kittydar-result" id ="kittydar-result">(none)</div>
    </div>

 Step 7 

Add the following snippet to just before the ending closing DIV tag.  It will run the KittyDar cat face detector on the currently displayed photo:  

<script> 
// Run the KittyDar cat detector on this photo
detectFromUrl("@Href( "~/Photo/Thumbnail", photo.Id, new { size="large" })");
</script>

 Step 8 

Add the following snippet to the _SiteLayout.cshtml file to make sure our custom HEAD elements in the view page are rendered: 

<!-- Render the head section if defined, like it is in View.cshtml -->
@RenderSection( "Head", required: false);

 Step 9 

Add kittydar.css to the Content folder.  It will make sure the KittyDar view elements are formatted propertly. 

 Step 10

Publish your web site to Azure using the WebMatrix 3 Publish button.  You're done! 
 


Short Form Explanation  

NOTE: If you followed the Long Form Explanation steps to modify your site, you do not need to follow these steps and can skip this section.  

 Step 1  

Follow the instructions in this tutorial to get the WebMatrix 3 Photo Gallery template up and running, but SKIP  the CAPTCHA setup steps since the file containing the CAPTCHA codes is about to be overwritten in the next step:  

WebMatrix 3 Photo Gallery Tutorial

 Step 2 

Make a backup of your WebMatrix 3 project files now.  Then, download the KittyDar-Photo-Gallery.zip file.  Open the compressed folder and select all the files and directories contained and copy them into the root directory of your web site that you created with the WebMatrix 3 Photo Gallery template.  The destination directory should contain the file Web.config, that's how you know you have the right target directory for the copy operation. 

 Step 3 

Go back to the Photo Gallery tutorial and return to the section titled  "Enabling CAPTCHA in the registration page".  Follow the instructions to setup CATPCHA properly on your site.  

 Step 4 

Publish your web site to Azure using the WebMatrix 3 Publish button.  That's it!  Now test your web site and make any desired changes you need to make. 

Test Your Web Site 

1. Run the project. 

2. Register for an account if you have not done so already.  This will be your test account.

3. Login to your test account.

4. Create a New Gallery called "Lost & Found"

5. Select the gallery and open it

6. Click on "Upload a Photo" to begin the real test.

7. Use the "Choose File" button to select a file that contains a picture of a cats face to upload.  If you don't have any cat pictures of your own, you can find plenty of public domain cat photos to test with at this link:  

Public Domain Cat Photos 

This in particular is a good test photo:  

Contented Cat
 

Make sure you have the rights to any photos you upload or that they are in the public domain since the photos will be publicly available on the Web! 

Easter Egg 

To see the Easter Egg on FindAPet, view any photo that contains more than one detected cat face on the main viewing page.  Better yet, just visit the link below:

Spooky cats! 

Resources 

WebMatrix 3 

Azure 

KittyDar 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

roscler
Software Developer Android Technologies, Inc.
United States United States
Member
Robert Oschler is a veteran artificial intelligence, robotics, natural language processing, and speech recognition programmer. His latest love is C#/.NET programming, especially on the Windows Phone platform. When not writing code you can find him playing guitar or watching the latest videos on MSDN's Channel 9. He is also a member of the incredible Nokia DVLUP program and owes much of his affection for Windows Phone programming to the wonderfully talented and enthusiastic Nokia Ambassadors.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberMihai MOGA10 May '13 - 18:22 
GeneralRe: My vote of 5professionalroscler11 May '13 - 7:56 
GeneralMy vote of 5memberMember 100274423 May '13 - 9:40 
GeneralRe: My vote of 5professionalroscler3 May '13 - 9:56 
GeneralMy vote of 5memberastumpp30 Apr '13 - 0:38 
GeneralRe: My vote of 5memberroscler30 Apr '13 - 0:47 
GeneralMy vote of 5memberShayne P Boyer29 Apr '13 - 14:39 
GeneralRe: My vote of 5memberroscler29 Apr '13 - 22:31 
QuestionGood luck with your project!memberArchieCoder28 Apr '13 - 18:29 
AnswerRe: Good luck with your project!memberroscler28 Apr '13 - 18:39 
GeneralRe: Good luck with your project!memberArchieCoder28 Apr '13 - 18:43 
GeneralRe: Good luck with your project!memberroscler28 Apr '13 - 18:46 
GeneralMy vote of 5memberRanjan.D27 Apr '13 - 10:39 
GeneralRe: My vote of 5memberroscler27 Apr '13 - 12:36 
QuestionNice ArticlememberSimon Jackson26 Apr '13 - 1:25 
AnswerRe: Nice Article [modified]memberroscler26 Apr '13 - 1:41 
QuestionRemove voting request.professional_Amy25 Apr '13 - 17:49 
AnswerRe: Remove voting request.memberroscler25 Apr '13 - 18:10 
GeneralRe: Remove voting request.professional_Amy25 Apr '13 - 18:16 
GeneralRe: Remove voting request.memberroscler25 Apr '13 - 18:20 
AnswerRemove "Please Vote" from titileprofessionalKarthik Harve25 Apr '13 - 17:43 
GeneralRe: Remove "Please Vote" from titilememberroscler25 Apr '13 - 18:12 
GeneralMy vote of 5memberContentqb25 Apr '13 - 3:55 
GeneralRe: My vote of 5memberroscler25 Apr '13 - 4:00 
GeneralRe: My vote of 5memberContentqb25 Apr '13 - 4:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130513.1 | Last Updated 12 May 2013
Article Copyright 2013 by roscler
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid