Windows Phone 8.1 for Newbies – The complete reference of Map Control in Windows Phone 8.1 WinRT

As Windows Phone came up with Windows Phone 8.1 update last year it came up with 2 different platforms to work on , silverlight and WinRT, thus making it a bit tough to keep up with the latest api documentation and maps are not different.

As I have written before how to use Maps following MVVM on Windows Phone 8, I’m going to keep today’s one as a bit of a reference to that and will just post segments that you can use as a code sample.

Instantiating a Map in Windows Phone 8.1:

Quiet frankly this is the easiest job here to do actually. Let’s go ahead and start taking things on screenshot. First we need to open a new project targeting Windows Phone 8.1 and name it MapTest. Then I went to my Toolbox and dragged a MapControl in the middle of the Main Grid. I have already changed my map controls HorizontalAlignment and VerticalAlignment to “stretch’ thus allowing the MapControl to be stretched out to the fullest for the whole app.


<Maps:MapControl HorizontalAlignment="Stretch"  VerticalAlignment="Stretch"/>

Usually a map driven app needs an AuthenticationToken to authenticate the map services but we will focus on it later. Let’s go ahead to solution explorer and open Package.appxmanifest and go to Capabilities tab and make sure Location and Internet both of the checkboxes are checked just to make it sure that your app is map capable.

Map -1

Now we have a green light to use Maps. If you are  a fella from Windows Phone 8 development you’ll see now, you don’t have ID_CAP_MAP here to make an app enable to use maps. You only need it to have location capability.

Getting Your Own Location:

Getting your own location is still as straightforward as it was before in Windows Phone 8 era. All you have to do is use the same Geolocator class and it looks like the following:

Geolocator locator = new Geolocator();
Geoposition position = await locator.GetGeopositionAsync();

Now the question remains now is how would you be able to show it on a Map. Now, Windows Phone 8.1 gives you a lot of  options to choose from actually here. You can use three specific things here:

  1. MapIcon
  2. XAML controls
  3. Shapes

Using MapIcon:

The first trial we are going to make is using the MapIcon as it is most straight forward. MapIcon is essentially derived from MapElement class, it can be configured by providing a new image to it or it provides a default one. And the declaration is pretty straight forward too.


MapIcon icon = new MapIcon();
icon.Location = position.Coordinate.Point;
icon.Title = "My Location";
MyMap.MapElements.Add(icon);

Now, if you want to provide a specific Image as your MapIcon, include the image in the Assets folder and let’s assume the image name is MapIcon.png. All you have to do is change the Image property of your MapIcon object.

icon.Image = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///Assets/MapIcon.png"));

What people actually misinterpret here is usually they compare PushPin with MapIcon and they do have a valid reason to do so because you can achieve the same result by using both of them but in actuality it doesn’t work like so. MapIcons are actually a set of Icons that define different purposes on the map. These are lightweight, so please chose one if you want to use these to show a specific purpose. Now our purpose was to show our current location.

Now let’s put together a method named GetMyLocation() and put all these together and see what happens.


private async void GetMyLocation()
        {
            Geolocator locator = new Geolocator();
            Geoposition position = await locator.GetGeopositionAsync();

            MapIcon icon = new MapIcon();
            icon.Location = position.Coordinate.Point;
            icon.Title = "My Location";
            icon.Image = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///Assets/ImageIcon.png"));
            MyMap.MapElements.Add(icon);

            MyMap.Center = position.Coordinate.Point;
            MyMap.DesiredPitch = 0;

            await MyMap.TrySetViewAsync(position.Coordinate.Point, 15);
           
        }

Now, as this goes, you’ll see Ive done some more works too. I’ve changed the center property of the map control because after adding the MapIcon you need your map to focus on that point and this is how you change the center of your map. And the next thing I did is setting DesiredPitch to 0. We will see more on this property later. You definitely have noticed by now that the method is an asynchronous method because the process of locating your position is asynchronous and we’re using our favourite await keyword to make locator.GetGeopositionAsync() to work synchronously inside the method. The last thing you want is to focus to the selected location (My Location) on the map. So Im setting the map view in an async manner using  MyMap.TrySetViewAsync function and it takes two parameters, the first one being the point (the geolocation of you) you want to set your view into and the second is a zoom level of the map. It is actually setting the MyMap.ZoomLevel property that ranges from 1 – 20. Higher number means you’re more zoomed thus more closer to the ground. I’ve also used a custom MapIcon.png with a size of 65×65 pixels. Please be careful on this one as the size you chose is the size you get on the Map. Please choose a size that looks good for your purpose. Now lets put the GetMyLocation() method OnNavigatedTo method and thus you get a smooth zooming animation.

Map -2

Now, couple of things that are missing here is a progress bar showing that the location is being detected and a try catch that detects any exception detecting the location. Plus we need to put our location detection invocation in a button so we can tap it anytime to load our current location anytime. For that we are going to take help of Application Bar.

Now Let’s add an app bar in the bottom like below:


<Page.BottomAppBar>
<CommandBar ClosedDisplayMode="Minimal" Opacity="0.7">
<AppBarButton Label="Find Me!" Icon="Target" />
</CommandBar>
</Page.BottomAppBar>

Now on the AppBarButton_Tapped event handler put GetMyLocation call. It would possibly look like the following:


private void AppBarButton_Tapped(object sender, TappedRoutedEventArgs e)
{
GetMyLocation();

}

Now, let’ wrap up the location detection process under a try-catch just to make sure that if anything goes wrong, you’re covered. And as we’d need to show progress please put the following method in MainPage.xaml too.


private async void ToggleProgressBar( bool toggle, string message="")
{
StatusBarProgressIndicator progressbar = StatusBar.GetForCurrentView().ProgressIndicator;
if(toggle)
{
progressbar.Text = message;
await progressbar.ShowAsync();
}
else
{
await progressbar.HideAsync();
}

}

Now all you have to do is call it to show ProgressIndicator in StatusBar, you can even provide the message in the method call.

Now GetMyLocation would look like the following:

private async void GetMyLocation()
        {
            ToggleProgressBar(true,"Getting your location...");

            try
            {
                Geolocator locator = new Geolocator();
                Geoposition position = await locator.GetGeopositionAsync();

                MapIcon icon = new MapIcon();
                icon.Location = position.Coordinate.Point;
                icon.Title = "My Location";
                icon.Image = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///Assets/ImageIcon.png"));
                MyMap.MapElements.Add(icon);

                MyMap.Center = position.Coordinate.Point;
                MyMap.DesiredPitch = 0;

                await MyMap.TrySetViewAsync(position.Coordinate.Point, 15);
            }
            catch (Exception ex)
            {
                MessageDialog ErrorDialog = new MessageDialog(ex.Message);
                ErrorDialog.ShowAsync();
            }

            ToggleProgressBar(false);
        }

Using XAML Controls/Shapes on Map:

Using XAML controls over Map is something I really love because I was pretty fond of the PushPin control provided in Windows Phone Toolkit for Windows Phone 8. Usually this approach is more suitable for MVVM approached apps though. And as per this tutorial goes, we’d not got over MVVM just yet (We will cover that too). So, let’s see how you add XAML controls or shapes in a map from codebehind.

Instead of using MapIcon all you have to do know is generate a XAML control/Shape in background and add it on the children list of the map control rather than the MapElements list.

So, just take off the MapIcon Segment and use this instead:


private async void GetMyLocation()
        {
            ToggleProgressBar(true,"Loading");

            try
            {
                Geolocator locator = new Geolocator();
                Geoposition position = await locator.GetGeopositionAsync();

                var pushpin = new Windows.UI.Xaml.Shapes.Ellipse();
                pushpin.Fill=new SolidColorBrush(Colors.Red);
                pushpin.Height=50;
                pushpin.Width=50;

                MyMap.Children.Add(pushpin);
     
                MyMap.DesiredPitch = 0;

                MapControl.SetLocation(pushpin,position.Coordinate.Point);
                MapControl.SetNormalizedAnchorPoint(pushpin, new Point(0.5, 0.5));

                await MyMap.TrySetViewAsync(position.Coordinate.Point, 15,0,0,MapAnimationKind.Bow);
            }
            catch (Exception ex)
            {
                MessageDialog ErrorDialog = new MessageDialog(ex.Message);
                ErrorDialog.ShowAsync();
            }

            ToggleProgressBar(false);
 
        }

Now, before moving to the next topic,  I’d like to address some points in this approach.

  1. You can create your control in codebehind as you want to build it, you can use possibly any XAML control starting from containers like Grid and StackPanels to Shapes like rectangle and Ellipse.
  2. Select a proper size of the shape if you want to use it and it’s better than MapIcons in one sense is that MapIcons are not guaranteed to be viewed.
  3. MapIcon objects are usually added in the MapElements list but this is added in the Map Children collection.
  4. You can define your datatemplate behind and even hook events (We will see this on MVVM approach

You have to select a proper anchor point for your Xaml/Shape pushpin too. It’s actually nothing but a point object in a 2d space. I want my anchor point to be in the center of the sphere. So if I put my sphere in a 2d space and 1 is the max limit in both X and Y axis, you’d find out that (0.5, 0.5) is the center of the sphere. But this 2d co-ordinate system is actually a tad different. Kindly allow me to demonstrate in the following picture:

Map -4

So, according to this rule, now you can set your anchor point properly according to your xaml control. Just put it on an imaginary 2d grid and find your desired point. left bottom would be (0,1) and right bottom would be (1,1).

If you look closely you will also see we have changed our TrySetViewAsync invocation with some more parameters. Those are heading, pitch (We kept both of them at 0) and the last one is MapAnimationKind that allows you to define what kind of animation you want when setting the view. I personally love the Bow one thus I used that one and it looks like below:

Map -3

Changing The Map Properties:

The map control itself comes with a lot of properties equipped, let’s see how we can change these for a chance. The first two things we’d love to change is the pitch/tilt and the rotation/heading. For that we’d need to change our layout a bit. Let’s keep the map in the background and put 2 sliders (one horizontal and one vertical) for changing rotation and pitch of the map.

We have introduced 3 rows and columns in the main grid just to keep the whole map in the background and the sliders floating in the top. The horizontal slider would change the Maps rotation and the Vertical Slider would change the Maps pitch. Now the main grid looks like the following:


<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="50"></RowDefinition>
</Grid.RowDefinitions>

<Grid.ColumnDefinitions>
<ColumnDefinition Width="25"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>

</Grid.ColumnDefinitions>

<Maps:MapControl Grid.RowSpan="3" Grid.ColumnSpan="3" x:Name="MyMap"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch">

</Maps:MapControl>

<Slider x:Name="RotationSlider" Grid.Row="0" Grid.Column="1" Maximum="360"></Slider>
<Slider x:Name="PitchSlider" Grid.Row="1" Grid.Column="2" Orientation="Vertical" Maximum="65"  ></Slider>
</Grid>

And it looks on the emulator like this:

Map -6

Now the first thing you’d notice here is I’ve named the sliders accordingly. The horizontal one is called RotationSlider and the vertical one is called PitchSlider. Now, usually what we do is invoke the ValueChanged event and change the Heading or the DesiredPitch of the map. But there is a smarter way to do that. You can create two element bindings to the MyMap and bind the value of the sliders to the specific properties. If you watch carefully you will see that I have set the RotationSlider maximum value to 360 as that is the maximum rotation value and PitchSlider Maximum to 65 as that is the maximum tilt the MapControl allows. Now replace your sliders with the following binding:


<Slider x:Name="RotationSlider" Grid.Row="0" Grid.Column="1" Maximum="360" Value="{Binding Heading, ElementName=MyMap, Mode=TwoWay}" ></Slider>
<Slider x:Name="PitchSlider" Grid.Row="1" Grid.Column="2" Orientation="Vertical" Maximum="65" Value="{Binding DesiredPitch, ElementName=MyMap, Mode=TwoWay}" ></Slider>

Please make it sure the Binding Mode is set to TwoWay so when the map updates the sliders updates too.  And without even writing a single backend code the feature is done!

Map -5

So, that’s a creative use of element binding. Now you could’ve done it even from code backend. All you had to do is go to properties of PitchSlider and RotationSlider and create the ValueChanged method stub.  The methods would’ve looked like the following:


private void PitchSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
MyMap.DesiredPitch = PitchSlider.Value;
}

private void RotationSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
MyMap.Heading = RotationSlider.Value;
}

Let’s move on and see some of the other features we’d like to test too. Let’s add two checkboxes in the bottom of the map to enable Traffic Flow and Land Marks. We added a horizontal stackpanel and added two checkboxes along with it.


<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<CheckBox x:Name="TrafficCheck" Content="Traffic Flow" Foreground="Green"></CheckBox>
<CheckBox x:Name="LandmarksCheck" Content="Land Marks" Foreground="Green"></CheckBox>
</StackPanel>

Now, we can use a bit more creative element binding or use Checked and Unchecked event to change the respective map control properties. Let’s see how we can bind it using element binding again.


<Maps:MapControl TrafficFlowVisible="{Binding IsChecked, ElementName=TrafficCheck, Mode=TwoWay}"
LandmarksVisible="{Binding IsChecked, ElementName=LandmarksCheck, Mode=TwoWay}"
Grid.RowSpan="3" Grid.ColumnSpan="3" x:Name="MyMap"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">

Now, watch carefully the binding written in TrafficFlowVisible property and LandMarksVisible property. All I did here is binded them with the appropriate checkbox IsChecked property and kept the binding mode two way. So, anytime these properties get changed even from codebehind we’ll see the change on the checkbox and vice versa. Traffic Flow is a cool feature actually. See the next gif to get an idea:

Map -7

Pretty cool huh!

Now you can even write it on the codebehind. You have to go to the properties of any of the checkbox and create the checked and unchecked property stub so you can toggle the maps TrafficFlowVisible feature. From code behind it will look like this:


private void TrafficCheck_Checked(object sender, RoutedEventArgs e)
{
MyMap.TrafficFlowVisible = true;
}

private void TrafficCheck_Unchecked(object sender, RoutedEventArgs e)
{
MyMap.TrafficFlowVisible = false;
}

and from the xaml:


<CheckBox x:Name="TrafficCheck" Content="Traffic Flow" Foreground="Green" Checked="TrafficCheck_Checked" Unchecked="TrafficCheck_Unchecked"></CheckBox>

I still prefer the Element binding because that is extremely easy. There’s one more property named PedestrianFeaturesVisible property. You can try that following the same way here.

Map Appearance:

Map Control is even cool enough to let you select the Map Appearance too! Let’s modify the bottom stackpanel so it can hold more buttons and added a grid with two columns. We added one combobox in each column so we can change map color scheme and map style.

So the bottom stackpanel looks like this now:


<StackPanel Grid.Row="2" Grid.Column="1">
<StackPanel  Orientation="Horizontal">
<CheckBox x:Name="TrafficCheck" Content="Traffic Flow" Foreground="Green"></CheckBox>
<CheckBox x:Name="LandmarksCheck" Content="Land Marks" Foreground="Green"></CheckBox>
</StackPanel>
<Grid Background="#00F9F9F9" >
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="0" PlaceholderText="Map Color" x:Name="MapColorBox" Width="150"  Background="#FF23D1B1" SelectionChanged="MapColorBox_SelectionChanged" >
<ComboBoxItem  IsSelected="True" Content="Light" ></ComboBoxItem>
<ComboBoxItem Content="Dark" ></ComboBoxItem>
</ComboBox>
<ComboBox Grid.Column="1" PlaceholderText="Map Style" x:Name="MapStyleBox" Width="150"  Background="#FF23D1B1" HorizontalAlignment="Right" SelectionChanged="MapStyleBox_SelectionChanged" >
<ComboBoxItem  IsSelected="True" Content="None" ></ComboBoxItem>
<ComboBoxItem Content="Road" ></ComboBoxItem>
<ComboBoxItem Content="Aerial" ></ComboBoxItem>
<ComboBoxItem Content="AerialWithRoads" ></ComboBoxItem>
<ComboBoxItem Content="Terrain" ></ComboBoxItem>
</ComboBox>
</Grid>
</StackPanel>

We have added light and dark as the Map Color Scheme in the MapColorBox combobox and we have added 5 different Map Styles on MapStyleBox combobox. Each of the combobox has SelectionChanged event handler hooked up. Let’s see how MapColorBox_SelectionChanged looks like in MainPage.xaml.cs


        private void MapColorBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (MapColorBox!=null)
            {
                string selectedColor = ((ComboBoxItem)MapColorBox.SelectedItem).Content.ToString();

                switch (selectedColor)
                {
                    case "Light":
                        MyMap.ColorScheme = MapColorScheme.Light;
                        break;
                    case "Dark":
                        MyMap.ColorScheme = MapColorScheme.Dark;
                        break;
                    default:
                        MyMap.ColorScheme = MapColorScheme.Light;
                        break;
                }
            }

        }

You can see that based on the SelectedItem on MapColorBox combobox we have assigned MapColorScheme of MyMap to MapColorScheme.Light or MapColorScheme.Dark

Map -8

Now, let’s focus on the MapStyleBox_SelectionChanged in the MainPage.xaml.cs


        private void MapStyleBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (MapStyleBox != null)
            {
                string selectedStyle = ((ComboBoxItem)MapStyleBox.SelectedItem).Content.ToString();

                switch (selectedStyle)
                {
                    case "None":
                        MyMap.Style = MapStyle.None;
                        break;
                    case "Road":
                        MyMap.Style = MapStyle.Road;
                        break;
                    case "Aerial":
                        MyMap.Style = MapStyle.Aerial;
                        break;
                    case "AerialWithRoads":
                        MyMap.Style = MapStyle.AerialWithRoads;
                        break;
                    case "Terrain":
                        MyMap.Style = MapStyle.Terrain;
                        break;
                    default:
                        MyMap.Style = MapStyle.None;
                        break;
                }
            }
        }

This looks like the previous one too. All we did here is based on the MapStyleBox SelectedItem we have assigned MyMap.Style to appropriate MapStyle.

Map -9

Change Map Tile Source:

Now usually what we see is either bing or here maps on Windows Phone 8.1 Map Control. In some cases people might need to access different tile source like openstreet map. For the ones who doesn’t understand what a tilesource is, every map system is nothing but a series of square shaped images/tiles shown for a range of geo coordinate boundary.  It’s a grid of images shown based on the maps zoom level. When you zoom out or zoom in or roam around in the map, based on your X,Y and zoom level the map requests tiles of images to show you.

If you want to add/change a tiles layer, you have to change the TileSources collection of the map control. You can set the Zindex, tile pixel size and visibility. You can even forbid/allow stretching of the tiles while a higher resolution tile is being downloaded. You can define layer type of the tile source like BackgroundReplacement, BackgroundOverlay and RoadOverlay.

Let’s replace our default map tiles with openstreetmap tiles.


private void SetTileSourceToOSM()
{
var httpsource = new HttpMapTileDataSource("http://a.tile.openstreetmap.org/{zoomlevel}/{x}/{y}.png");
var tilesource = new MapTileSource(httpsource)
{
Layer = MapTileLayer.BackgroundReplacement
};
MyMap.Style = MapStyle.None;
MyMap.TileSources.Add(tilesource);
}

Now all we have to do is call this on our OnNavigatedTo method


protected override void OnNavigatedTo(NavigationEventArgs e)
{
SetTileSourceToOSM();
GetMyLocation();

}

And after the app is loaded, your map will look like this. But remember as we set our layer to BackgroundReplacement we will not have any kind of map styles or other features like TrafficFlow anymore.

You will also observe the loading of the map is pretty slow as the tiles are loaded from one source. So, we can balance the load by pointing to three different domains so the maps may load faster. All we have to do is rotate the subdomain of openstreet map in between a,b and c


private void SetTileSourceToOSM()
{
var next = "a";
var httpsource = new HttpMapTileDataSource("http://a.tile.openstreetmap.org/{zoomlevel}/{x}/{y}.png");

httpsource.UriRequested += (src, args) => {
next = GetNextDomain(next);
args.Request.Uri = new Uri("http://" + next + ".tile.openstreetmap.org/" +
args.ZoomLevel + "/" + args.X + "/" + args.Y + ".png");

};

var tilesource = new MapTileSource(httpsource)
{
Layer = MapTileLayer.BackgroundOverlay
};

MyMap.Style = MapStyle.None;
MyMap.TileSources.Add(tilesource);
}

private string GetNextDomain(string next)
{
string[] domains={"a", "b", "c"};

for(int count=0; count<domains.Length; count++)
{
if (domains[count] == next)
return domains[(count + 1) % domains.Length];
}

return next;
}

Here in the GetNextDomain method you will see that all it’s doing is it is actually rotating between the three subdomains of openstreet map.

And it looks like below:

Map -12

Map Events:

Maps in Windows Phone 8.1 comes with much easier touch events. Touch events now comes along with GeoPoint and local UI coordinates related to the touch point. All these comes along with MapTapped, MapDoubleTapped and MapHolding events.  To check if a location is visible on current map window you can use IsLocationInView().

We are going to try the MapTapped event for now. Select the MapControl, go over properties, go to event handlers and double click on the MapTapped event handler to create the method stub for that.

Now let’s go ahead and write the method down:


private async void MyMap_MapTapped(MapControl sender, MapInputEventArgs args)
{
Geopoint point = new Geopoint(args.Location.Position);

MapLocationFinderResult FinderResult =
await MapLocationFinder.FindLocationsAtAsync(point);

String format = "{0}, {1}, {2}";

if (FinderResult.Status == MapLocationFinderStatus.Success)
{
var selectedLocation=FinderResult.Locations.First();

string message= String.Format(format, selectedLocation.Address.Town, selectedLocation.Address.District, selectedLocation.Address.Country);
await ShowMessage(message);

}

}

Now here we have a sample reverse geocode query. And you can definitely see this is much easier here. You have to use MapLocationFinder class and it has a FindLocationsAtAsync method to get back with a geocoordinate based on your tap on the map. Actually it usually returns a list and we picked the first on the list and printed out the town, district and country on a messagedialog.

you can definitely check how the thing works on the phone

Map -11

 

 

Authenticating a Maps App:

The last thing that is left to do is authenticating a map app. You will see unless you provide a map control a map service AuthenticationToken it keeps asking it in the bottom of the map.

The details on that process is available here.

Our next tutorial would be on Routing , GeoFencing and Maps using MVVM.

The source code is uploaded here!

Stay frosty!

 

Advertisements

A little bit of fun with face detection in Windows Phone 8

To start off

If anybody is actually looking for Face Detection Libraries for Windows Phone and haven’t tried this one, this is for them. It’s a neat library popping off facedetecionwp7 library by Julia Schwarz. And you can do pretty cool stuffs with it with pretty less hassle.

Let’s have a little fun

Let’s go ahead and get ourselves started with a Windows Phone 8 Project. Our job would be try this library and detect faces in a picture and have some fun with it, say overlay the face with the classic troll face with the laugh. 😛

Troll

Now, no matter what that sounds, let’s give it a shot. I’ve jotted down a very simple GUI to do this one. And it’s as following:

FaceDetection1

If you want to have a closer look at the XAML it will look the following:

<Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

       
        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="Face Detection in Windows Phone" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
            
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Button x:Name="CaptureImageButton" Content="Capture Image" HorizontalAlignment="Center" Margin="0" VerticalAlignment="Top" Width="267" Click="CaptureImageButton_Click"/>
            <Image Name="facesPic" HorizontalAlignment="Center" Height="388" Margin="0,170,0,0" VerticalAlignment="Top" Width="427"/>
        </Grid>

        
    </Grid>

Now, you can definitely see that this is a very basic UI. We have a Button with a click event CaptureImageButton_Click and a Image named facesPic.

Now, before we get our hands dirty, let’s go ahead and add reference to a library we will need. We will need WritableBitmapEx from here. This lib is already available on Nuget too, so you can get it from there too.

After you added that in your project reference, you will need a bunch of libraries from FaceDetectionWP8 to make this things work. You can get the classes on there from the following link too.

After you download the .zip , unzip it and include all the classes inside in your project. I added them under a separate folder named FaceDetector.

Let’s move to our MainPage.xaml.cs

Now, the first thing to do is to pick a picture and the best thing to use for that is PhotoChooserTask . Let’s hook up the following snippet in CaptureImageButton_Click event.

private void CaptureImageButton_Click(object sender, RoutedEventArgs e)
        {
            PhotoChooserTask photo = new PhotoChooserTask();
            photo.Completed += new EventHandler&lt;PhotoResult&gt;(photoChooserTask_Completed);
            photo.ShowCamera = true;
            photo.Show();
        }

Now, the next thing to write is definitely photoChooserTask_Completed event handler. Let’s go ahead and write it. First thing to do would be get the chosen photo into a  WritableBitmap as we are going to do some changes on it.

if(e.TaskResult==TaskResult.OK)
{
    BitmapImage SourceBitmap = new BitmapImage();
    SourceBitmap.SetSource(e.ChosenPhoto);
    WriteableBitmap SourceWritableBitmap = new WriteableBitmap(SourceBitmap);

}

Now, the next thing we need to do is downsample the image a bit as most of the Windows Phone devices pack a pretty powerful camera and often the pictures are oversampled. I personally suggest you downsize the image a bit too. I din’t do it in this demo but if you want a faster performance, you really should try that too. I added the following code segment inside the if block.

byte[] downsampledImage = new byte[SourceWritableBitmap.PixelWidth / _downsampleFactor * SourceWritableBitmap.PixelHeight / _downsampleFactor];

Utils.DownSample(SourceWritableBitmap.ToByteArray(), SourceWritableBitmap.PixelWidth, SourceWritableBitmap.PixelHeight, ref downsampledImage, _downsampleFactor);

SourceWritableBitmap = SourceWritableBitmap.FromByteArray(downsampledImage);

The code segment is pretty straight forward here now. The Utils class here is from the FaceDetectionWP8 library. The first argument for Utils.DownSample used here is a Byte Array of the SourceWritableBitmap and it used the WritableBitmapEx we added here before. The second one is the width of the source bitmap and the third one is the height. The last argument passed here is the downsample factor. This was defined as the following in the MainPage class.

int _downsampleFactor = 2;

And then the SourceWritableBitmap is reinitiazed with a downsampled version of it’s own.

The next thing to do would be getting the faces detected of course. 😀 So, why wait? I added the following sinppet for that.

List<FaceDetector.Rectangle> faces = new List<FaceDetector.Rectangle>();
faces = _detector.getFaces(SourceWritableBitmap, 2f, 1.25f, 0.1f, 1, false, true);

I used the defaults here except the last argument that determines whether it should detect multiple faces and I kept it true.  You can have fun with the other params if you want to tweak your haar cascade detection parameters here.

here comes the fun part, now we are going to replace the face/faces in the picture with the troll laugh picture. :P. We can see it returns the faces as a List<FaceDetector.Rectangle>. All we have to do now is paint the Troll laugh in the rectangles returned by the method. fun, huh?

Now, before replacing the Source Image with the Troll Laugh, we need to load it in another WritableBitmap so we can blend the both of them.

StreamResourceInfo MaskImageSri = Application.GetResourceStream(new Uri("Images/Troll.png", UriKind.Relative));
BitmapImage MaskImageBitmap = new BitmapImage();
MaskImageBitmap.SetSource(MaskImageSri.Stream);

WriteableBitmap MaskWritableBitmap = new WriteableBitmap(MaskImageBitmap);

As we got our Troll.png, why don’t we paste it on the source bitmap?

foreach (var r in faces)
{
   int x = Convert.ToInt32(r.X);
   int y = Convert.ToInt32(r.Y);
   int width = Convert.ToInt32(r.Width);
   int height = Convert.ToInt32(r.Height);                    

   System.Windows.Rect destRect = new Rect(x, y, width, height);
   System.Windows.Rect srcRect = new Rect(0, 0, MaskWritableBitmap.PixelWidth, MaskWritableBitmap.PixelHeight);

   SourceWritableBitmap.Blit(destRect, MaskWritableBitmap, srcRect);

}
   SourceWritableBitmap.Invalidate();
   facesPic.Source = SourceWritableBitmap;

Now, this is pretty straight forward too. All we need is we iterated over the faces and we created to separate rect structs. One is from the MaskWritableBitmap so we can get the rect of the portion of the image we are pasting, in this case, the full one and the destination rect is the face rectangle on the source image we are pasting this troll laugh on.

The next thing to do is invoke Blit() method as this one is responsible to blend these two WritableBitmaps. It comes with the WritableBitmapEx library we added before. The first parameter is the destination rectangle where the source rectangle will be pasted. In this case the face rectangle that will be replaced by the troll laughter image. The second parameter would be the WritableBitmap where the rect that would be posted in the source is found. Here this one is the Troll Image and the last one is the rect in the Troll Image that would be transferred to the main bitmap.

We called it on SourceWritableBitmap as we want to paste the troll Laugh on it. And when all is said and done we set facesPic source to the SourceWritableBitmap.

Now all we have to do is build and test the app. And the result is as following:

FaceDetectionScreenshot

So, what are waiting for? Test the demo project here of the article and have fun!

সোজা সাপ্টা ম্যাশিন লার্নিং – কে নিয়ারেস্ট নেইবর (K Nearest Neighbour)

সত্যি কথা বলতে কি, আমরা সবাই একটা বেশ লম্বা থিসিস করে আসি চতুর্থ বর্ষে, আমি জানিনা অন্যদের কথা, কিন্তু অনেক কিছুই আমি ঠিকমতো বুঝিনাই, তাই ঠিকমতো বোঝার ইচ্ছা টা যায়নাই। সেই চেষ্টায় ভাবলাম, যেটুকু বুঝি , লিখে ফেলি। ম্যাশিন লার্নিং জিনিসটা ভারিক্কী শোনালেও জিনিস টার কাজ একটাই, একটা গবেট প্রকৃতির ম্যাশিন কে আপনি কোন নির্দিষ্ট ব্যাপারে ঠিক যে ভাবে চিন্তা করেন সেভাবে তাকে ভাবতে শিখানো বা অন্তত কাছাকাছি কিছু একটা চেষ্টা করা।

ম্যাশিন লার্নিং এ আমার মতো যারা নবিশ তাদের জন্য কিছু বলতে হলে বলা লাগে, যদি ম্যাশিন লার্নিং কে একটু ক্ল্যাসিফাই করার চেষ্টা করি তাহলে দুটো বড়সড় ভাগ পাবেন। প্রথমটার নাম সুপারভাইজড লার্নিং, আরেকটা আনসুপারভাইজড লার্নিং। কোনটা কি এগুলোর সংজ্ঞা নিয়ে ভটভট করার মতো জ্ঞান আমার নাই। তাই সোজা সহজ ভাবে বলি। যদি একটা ম্যাশিন কে শুরু থেকে কিছু শেখানো হয়, তারপর সে কিছু ঠিকঠাক মতো করতে পারে, সেটা দাঁড়ায় সুপারভাইজড লার্নিং এর দলে। বাকিটা হচ্ছে আনসুপারভাইজড।

ম্যাশিন লার্নিং থেকে ম্যাশিন শব্দ টা সরিয়ে ফেলুন, মনে করুন আপনি একটা ম্যাশিন। একটু সুবিধাজনক একটা পরিবেশ চিন্তা করতে গেলে আপনাকে একটু বোকা হতে হবে। ম্যাশিন সাধারণত বোকাসোকা হয়। তাহলে কি করা? চলুন ফেরত যাই ছোটবেলায়। একদম যখন ছোট ছিলেন আপনি তখন নিশ্চই আপনি এতো কিছু জানতেন না। তো সেই হিসাবে সেই সময়টায় আপনি এখনকার একটা বোকা ম্যাশিন এর মতোই ছিলেন। এখন বাকি থাকলো লার্নিং শব্দটা। সহজ কথায় শিক্ষা। আসুন ছোটবেলার আপনাকে কিছু শেখানো যাক।

ভয় নেই। বেশিদূর যাওয়া লাগবেনা। আপনার মার কথাই মনে করুন। আপনার মাই সম্ভবত আপনাকে শিখিয়েছেন সব কিছু্। চিন্তা করুন, আপনার মা আপনাকে চিনতে শিখিয়েছে কোনটা কি? ধরে নিলাম আপনার মা আপনাকে চিনিয়েছে আপেল, ধরে নিলাম, প্রথম আপেলটির রং ছিলো হালকা লাল। পরেরদিন আপনার মা আপনাকে দিলো একটি হালকা সবুজ রঙের আপেল, পরেরদিন একটু গাড় রঙের আরেকটি। চিন্তা করুন একবার, খুবই সাধারণ মনে হওয়া এই ঘটনাটি আসলে ম্যাশিন লার্নিং এর একটি অসাধারন উদাহরণ। আপনি (ম্যাশিন) প্রতিদিন দেখেছেন একটি ভিন্ন আপেল, ভিন্ন রং, ভিন্ন আকার এবং হতে পারে একটু ভিন্ন স্বাদের। কিন্তু আপনি জেনেছেন সব ই আপেল। যদি এমন হতো, প্রতিদিন এক ই রঙের, আকারের আপেল দেখতেন, আপনি হয়তো কোনদিন বিশ্বাস করতে পারতেন নাম আপেল অন্য আকার বা রঙের হয়। আপনার মা খুব নিভৃতে আপনাকে শিখিয়েছেন একটি সাধারণ আপেল দেখতে কেমন হয়। এটাকে সহজ কথায় Generalization বলে। যে কারণগুলো একটা আপেল থেকে বাকিগুলোকে আলাদা করেছে সেগুলোই Feature. একটু ভেবে দেখুন, যদি আমরা গোলকার আকৃতি এবং লাল রং কে আপেলকে অন্য সব কিছু থেকে আলাদা করার Feature হিসেবে ঘোষনা করি, তাহলে আপেলটা দিনশেষে কিছু লাল বলের সাথে গিয়ে জমা হতে পারে। এটিকে বলে Over generalization. আবার উল্টোটি হলে under generalization  ও হয়ে যেতে পারে। 🙂

সুতরাং বোঝাই যাচ্ছে ঘাপলাটা কোথায়। আপনাকে আপেল থেকে অন্য কিছু আলাদা করার জন্যে ঠিক সেই Feature গুলোই গ্রহণ করতে হবে যেগুলো over বা under generalization তৈরী করবেননা। এর মানে হচ্ছে গোলাকার আকৃতি Feature হিসেবে খুব একটা সুবিধার না, অনেক কিছুই গোলাকার হতে পারে। কিভাবে ভালো Feature বের করতে হয় সেটা নিয়ে আমরা ভটভট পরে করবো কোন এক দিন। আসেন কোন কিছু নিয়ে ভটভট করি। আমাদের আজকের লক্ষ্য K-Nearest Neighbor Algorithm দেখা, এটি আসলে একটি Classification Algorithm, সহজ কথায় যেগুলো দিয়ে অনেক কিছু থেকে জিনিসপত্র ভিন্ন ভিন্ন প্রকারে ভাগ করা হয়।

K-Nearest Neighbor Algorithm

K-Nearest Neighbour এর নাম ই যথেষ্ট আসলে এটি কি করে সেটা বোঝানোর জন্যে। সহজ বাংলায় এটি কোন কিছুকে ক্ল্যাসিফাই করে অনেকটা সে জিনিসটার আশেপাশের জিনিসগুলো কোন প্রকারের তার উপর থেকে। ধরুন আপনি একটি গোলাকার জিনিস অনেকগুলো আপেল আর বলের মধ্যে রাখলেন । কে নিয়ারেস্ট নেইবর যেটা বলে সেটা হচ্ছে  আপনার রাখা নতুন বস্তুটি আশেপাশের অধিকাংশ বস্তু যে প্রকারের সেই প্রকারের মধ্যে পরে। যদি আশেপাশের অধিকাংশ জিনিস বল হয়, তাহলে সেটি বল অথবা সেটি আপেল। আজগুবি ঠেকলেও ভুলে যাবেন না, আপনি কিন্তু যে কোন জায়গায় আপনার অজানা জিনিসটি বসাতে পারবেন না। বসাবেন আসলে তার ফিচারের ভিত্তিতে। যদি সেটি সত্যি আপেল হয় এবং আপনার ফিচার ঠিকমতো নির্ণয় করা হয়ে থাকে তাহলে সেটি অবস্থান নেবে আপেলদের পাশেই। তার মানে এই নাম এই অ্যালগরিদম একেবারেই ভুল করেনা। তবে সেটা নিয়ে পরে কথা বলি না হয়।

ছোট্ট একটি উদাহরণ:

মনে করুন, আপনার কাছে এইরকম একটি ডাটাসেট আছে।

Rooms Area Type
1 350 apartment
2 300 apartment
3 300 apartment
4 250 apartment
4 500 apartment
4 400 apartment
5 450 apartment
7 850 house
7 900 house
7 1200 house
8 1500 house
9 1300 house
8 1240 house
10 1700 house
9 1000 house
1 800 flat
3 900 flat
2 700 flat
1 900 flat
2 1150 flat
1 1000 flat
2 1200 flat
1 1300 flat

দেখেই বোঝা যাচ্ছে এটি আকার এবং রুম সংখ্যার ভিত্তিতে আপনার বাসাটি অ্যাপার্টমেন্ট, ফ্ল্যাট না হাউজ সেটার প্রকারভেদ। তাহলে এখানে সহজ কথায় রুম সংখ্যা এবং আকার আমাদের ফিচার। এই স্যাম্পল সেটটি যদি আমরা কোন ম্যাশিন কে শিখাই, তাহলে এটার উপর ভিত্তি করে সে নতুন কোন নোড এই কোন ক্যাটাগরিতে পরে সেটা আপনাকে জানিয়ে দেবে। প্রশ্ন হচ্ছে কিভাবে। আমরা যেটা করবো এখন সেটি হচ্ছে একটি দ্বিমাত্রিক কার্তেসীয় স্থানাংক ব্যবস্হায় X অক্ষ বরাবর Room সংখ্যা এবং Y অক্ষ বরাবর বসাবো Area. এখন এই ছকে নতুন কোন বাসা যখন আসবে তখন সেটিও হবে আসলে একটি ডাটা পয়েন্ট মাত্র। আমরা সেটির আশে পাশের K সংখ্যক ডাটা পয়েন্ট গুলো থেকে দেখে নিবো সেটি কোন প্রকারে পরে। সহজ বাংলায় আমাদের অ্যালগরিদম টা দাঁড়ায় এরকম :

১. স্যাম্পল ডাটা সহ যে সকল Mystery Point (যেগুলো কোন প্রকারে পরে আমরা জানিনা)  বসিয়ে দিন ছকে
২. বের করে নিন Mystery Point গুলো থেকে সকল পয়েন্ট এর দূরত্ব। ৩. এরপর বেছে নিন কাছের K সংখ্যক প্রতিবেশী পয়েন্ট কে।
৪. k সংখ্যক প্রতিবেশীদের সর্বোচ্চ সংখ্যক প্রতিবেশী যে প্রকারের আপনার Mystery Point ও সেই প্রকারের।

আসুন আমরা ছোট্ট একটা কোডে চলে যাই।

কোড:

আমি কোড করার প্ল্যাটফরম হিসেবে নিয়েছি উইন্ডোজ ফোন ৮ এবং আমি চার্ট এর জন্যে ব্যবহার করছি আমার পছন্দের Telerik Rad Controls. আপনারা চাইলে স্প্যারো টুলকিট ব্যবহার করতে পারেন। এটি ফ্রি এবং চমৎকার। চাইলে যে কোন ল্যাংগুয়েজে কোড করে ফেলতে পারেন। আমি এখানে অসম্ভব crude code করে গেছি এবং চেষ্টা করে গেছি কোড বড় হলেও বোধগম্য রাখার। আপনি আপনার মতো সাজিয়ে নেবেন। 🙂

আমরা প্রথমে Node  এবং NodeList নামের দুটি ক্লাস নিয়ে কাজ করবো।

class Node শুরুতে দেখতে অনেকটাই এরকম:

public class Node
{
public int Rooms { get; set; }
public int Area { get; set; }
private NodeType _type = NodeType.unknown;
public NodeType Type {
get
{ return _type; }
set
{
_type = value;
SetForeground();

}
}

private void SetForeground()
{
if (_type == NodeType.apartment)
{
Foreground = new SolidColorBrush(Colors.Red);
}
else if (_type == NodeType.flat)
{
Foreground = new SolidColorBrush(Colors.Green);
}
else if (_type == NodeType.house)
{
Foreground = new SolidColorBrush(Colors.Blue);
}
else
{
Foreground = new SolidColorBrush(Colors.White);
}
}

public SolidColorBrush Foreground { get; set; }

public ObservableCollection Neighbours { get; set; }

public double Distance { get; set; }

public Node()
{

}

}

এখানে প্রথমে যেটা চোখে পরে, সেটা হচ্ছে শুরুতেই Rooms এবং Area বলে দেয়া শুরুতে। এই দুটো আমাদের ফিচার, একটি Node একটি বাসাকে প্রকাশ করে এখানে। সুতরাং এগুলো শুরুতেই ডিফাইন করা। এরপর দেখা যাচ্ছে NodeType বলে দেয়া এবং NodeType এর ভিত্তিতে একটি Foreground বলে দেয়া। এটি করা হয়েছে শুরু চার্ট তৈরী করার সুবিধার্থে। আসুন দেখে নেই NodeType enum টি কিরকম।

public enum NodeType
{
[Description("apartment")]
apartment,
[Description("house")]
house,
[Description("flat")]
flat,
[Description("unknown")]
unknown
}

স্পষ্টতই দেখা যচ্ছে টাইপ গুলো আমরা আমাদের স্যাম্পল ডাটার মতো বলে নিয়েছি। শুধু unknown টাইপটি আমাদের Mystery Point এর জন্যে। যাতে আমরা চার্টে আলাদা করতে পারি কোনটার টাইপ আমি জানিনা। এবার আসুন NodeList ক্লাসটি কিরকম দেখে নেই

public class NodeList
    {
        private int MinAreas { get; set; }
        private int MinRooms { get; set; }

        private int MaxAreas { get; set; }

        private int MaxRooms { get; set; }

        public ObservableCollection<Node> Nodes { get; set; }
        public int K { get; set; }

        public NodeList()
        {
            MinAreas = MinRooms = 100000;
            MaxAreas = MaxRooms = 0;
        }

    }

এখানে দেখা যাচ্ছে MinAreas, MaxAreas এবং MinRooms, MaxRooms নামের কয়েকটি প্রোপার্টি ডিফাইন করা। এগুলো আরো সহজেই করা যায়। কিন্তু কাজের এবং বোঝার সুবিধার্থে আলাদা করে লেখা। যে দুটি প্রোপার্টি জরুরী তা হচ্ছে Nodes এবং K . Nodes হচ্ছে আপনার ম্যাশিনের শেখা সমগ্র নোড গুলো (উপরের বাসার লিস্ট) এবং K হচ্ছে কতজন প্রতিবেশী নোড কে আমরা বিবেচনায় আনবো সেটির সংখ্যা। আপনি চাইলে Nodes কালেকশনটি কনস্ট্রাকটর ডিপেন্ডেন্সি তে ঢুকিয়ে দিতে পারেন। কিন্তু আমি আগেই বলেছি, এই পুরো লেখার উদ্দেশ্য বোঝানো, তাই কোডের এই দুরবস্থা। 😛
আসুন আর একটু আগানো যাক। এখন আমাদের Mystery Point থেকে সকল পয়েন্ট এর দূরত্ব বের করার কথা। কিন্তু তার আগে কিছু কথা বলা প্রয়োজন।

নরমালাইজেশন

একটু ভালো করে দেখুন, আমাদের দেয়া স্যাম্পল ডাটাতে রুম সংখ্যা ১ থেকে ১০ এর মধ্যে এবং রুম সাইজ ২৫০ তে ১৭০০ এর মধ্যে। তার মানে আমাদের x অক্ষ বরাবর বিন্দুগুলোর দূরত্ব Y অক্ষ বরাবর বিন্দুগুলোর দূরত্ব হতে গড়ে কম হবে। এটিতে যেটা হতে পারে, একটি বাসা ছকে তার সর্ব্বোচ্চ কাছাকাছি বিন্দু হতে অপেক্ষাকৃত দূরে সরে যেতে পারে এবং ভুল বিন্দুর কাছে X অক্ষ বরাবর কাছে চলে আসতে পারে। ফলে আপনার অ্যালগরিদম এর ফলাফল ক্ষতিগ্রস্ত হতে পারে। আসুন সব ভ্যালু গুলোকে আমরা ০ হতে ১ এর মধ্যে নিয়ে আসি। সহজ বাংলায় আমরা আমাদের গ্রাফটিকে বর্গাকার করে নিচ্ছি যাতে X অক্ষ এবং Y অক্ষ এর গুরুত্ব সমান থাকে। আপনি যদি চান আপনার ক্ল্যাসিফিকেশনে রুম সংখ্যা বেশি গুরুত্ব পাবে। তাহলে আপনি যেকোন অক্ষের দুরত্ব কমিয়ে আনতে পারেন। একে Weighting বলে। সেটা না হয় পরে।

দুই অক্ষের সকল ভ্যালু ০ হতে ১ এর মধ্যে আনতে হলে দুই অক্ষের সর্ব্বোচ্চ এবং সর্বনিম্ন মান আমাদের জানতে হবে এবং সেটাকে অক্ষভেদে মানের পার্থক্য দিয়ে ভাগ দিতে হবে। আসুন কোড দেখে বুঝে নেই:

        private void CalculateRanges()
        {
            if(Nodes!=null && Nodes.Count>0)
            {

                foreach (var node in Nodes)
                {
                    if (node.Rooms < MinRooms)
                        MinRooms = node.Rooms;
                    if (node.Rooms > MaxRooms)
                        MaxRooms = node.Rooms;

                    if (node.Area < MinAreas)
                        MinAreas = node.Area;
                    if (node.Area > MaxAreas)
                        MaxAreas = node.Area;
                }
            }
        }

CalculateRanges() মেথডটি NodeList ক্লাসে যোগ করা। দেখেই বোঝা যাচ্ছে খুবই আনাড়ি কোড কিন্তু বোঝার জন্যে এটি অসাধারণ। খেয়াল করে দেখুন CalculateRanges() শুধুমাত্র room এবং area (আমাদের x এবং y অক্ষ) এর সর্ব্বোচ্চ এবং সর্বনিম্ন মান খুঁজে বের করছে। আর কিছু নয়। এখন যেহেতু আমাদের হাতে দুই অক্ষের সর্ব্বোচ্চ এবং সর্বিনম্ন মান আছে সেহেতু আমরা মূল অ্যালগরিদমে প্রবেশ করতে পারি।

public void DetermineUnknown()
        {
            //start the calculation of range;
            this.CalculateRanges();

            foreach (var node in Nodes)
            {
                if(node.Type == NodeType.unknown)
                {
                    node.Neighbours = new ObservableCollection<Node>();
                    foreach (var NNode in Nodes)
                    {
                        if (NNode.Type == NodeType.unknown)
                            continue;
                        node.Neighbours.Add(NNode);

                    }

                    //Measure Distance Now

                    node.MeasureDistances(MaxRooms - MinRooms, MaxAreas - MinAreas);

                    //Sort it out
                    node.Neighbours = new ObservableCollection<Node>(node.Neighbours.OrderBy(x => x.Distance).ToList());

                    //Guess the type
                    node.GuessType(this.K);
                }
            }
        }

এক বস্তা জিনিস এক সাথে লিখে ফেললাম, এই তো? আসুন আস্তে আস্তে আগাই। আমরা CalculateRanges() আগেই দেখেছি। শুরুতেই লুপে ঘুরছে আমাদের সব স্যাম্পল ডাটা (যেখানে আমাদের Classification না জানা Mystery Points গুলো, মানে যে বাসাগুলো কোন প্রকারের আমরা জানিনা সেগুলোও আছে এবং অবশ্যই স্যাম্পল ডাটা সেট ও আছে)। দেখা হয়েছে NodeType unknown কিনা, হলে সেটিই সেই বাসা যেটির প্রকার আমরা জানিনা, তার মানে সেটি Mystery Point গুলোর একটি। আসুন সেটিকে classify এর ব্যবস্থা করি। শুরুতে ই লাগবে সকল প্রতিবেশী সকল নোড হতে দূরত্ব্। না হলে কাছের গুলো বের করবেন কিভাবে। সেজন্যে MeasureDistances() ব্যবহার করা। দেখে আসি মেথডটি দেখতে কেমন হতে পারে। খেয়াল করে দেখুন, আরগুমেন্ট দুটি হচ্ছে Rooms এবং Areas এর মানের পার্থক্য। এটি আমাদের Normalize করতে সাহায্য করবে।

        internal void MeasureDistances(int RoomRange, int AreaRange)
        {
            foreach (var neighbour in Neighbours)
            {
                double delta_rooms = neighbour.Rooms - this.Rooms;
                delta_rooms = delta_rooms / (double)RoomRange;

                double delta_area = neighbour.Area - this.Area;
                delta_area = delta_area / (double)AreaRange;

                neighbour.Distance = Math.Sqrt(delta_rooms * delta_rooms + delta_area * delta_area);

            }
        }

এখানে দূরত্ব বের করার আগে Normalization করো হয়েছে। তার মানে আমরা প্রতিটি প্রতিবেশীর Room এবং Area এর মানের পার্থক্য কে Room এবং Area এর সর্বোচ্চ এবং সর্বনিম্ন মানের পার্থক্য দিয়ে ভাগ দিয়ে এই মানগুলোকে ০ হতে ১ এর মধ্যে নিয়ে আসছি। এর পর আমরা পিথাগোরাসীয় দূরত্ব বের করছি। একটি জিনিস লক্ষ্য করে দেখুন। আমাদের ফিচার সংখ্যা দুইটি হওয়ায় আমরা দ্বিমাত্রিক কার্তেসীয় স্থানাংক ব্যবস্থায় আমরা বিন্দুগুলোকে X অক্ষ এবং Y অক্ষে আলাদা করে লিখেছি। এইজন্য দ্বিমাত্রিক দুটি বিন্দুর দূরত্ব বের করার সূত্র হচ্ছে Math.Sqrt(x * x + y * y)
যদি আপনার ফিচার তিনটি হতো তাহলে ছকটি ত্রিমাত্রিক স্থানাংক ব্যবস্থায় চলে আসতো। তখন দুই বিন্দুর দূরত্ব হতো Math.Sqrt(x * x + y * y+ z*z)
এইভাবে n সংখ্যক ফিচারের জন্য n মাত্রিক স্থানাংক ব্যবস্থায় দুই বিন্দুর দূরত্ব হতো Math.Sqrt(x * x + y * y+ z*z+ …..+ n*n)
আপনি চাইলে একটি n মাত্রিক স্থানাংক ব্যবস্থাকে কমিয়ে এনে দ্বিমাত্রিক ব্যবস্থায় প্রোজেক্ট করতে পারেন। তবে সেগুলো অন্য কোন দিন। যেটা আমি এখানে বোঝাতে চেয়েছিলাম সেটি হচ্ছে এটিই KNN এর ক্ষমতার পরিচায়ক, আপনার ফিচার সংখ্যা যাই হোক না কেন, সেটি আরামসে কাজ করতে পারে। তবে মনে রাখবেন বেশি ফিচার মানেই ভালো ক্ল্যাসিফিকেশন তা নয়। তাতে সেই generalization সংক্রান্ত জটিলতায় পড়তে পারেন।

তো MeasureDistances() এর পরে আমাদের কাজ হচ্ছে দূরত্বের ভিত্তিতে প্রতিটি নোডের প্রতিবেশীর লিস্ট কে সর্ট করা। কারণ আমরা k সংখ্যক কাছের প্রতিবেশী নিয়ে আগ্রহী। এর পরেই চলে আসে Mystery Point টি যেটির প্রকার আমরা জানিনা (যে বাসাটি ফ্ল্যাট, অ্যাপার্টমেন্ট না হাউজ আমরা জানিনা) সেটির প্রকার খুঁজে বের করা। সেই কাজটি করে GuessType()

internal void GuessType(int k)
        {
            int[] TypeVotes = new int[4]; //There are three types;

            var EligibleNeighbours = this.Neighbours.Take(k).ToList();

            foreach(var eligibleNode in EligibleNeighbours)
            {
                TypeVotes[(int)eligibleNode.Type] ++;

            }

            NodeType GuessedType = (NodeType)TypeVotes.Max();

            MessageBox.Show("Guessed type for node is " + GuessedType.ToString());

            this.Type = GuessedType;
        }

আমরা চলে এসেছি প্রায় শেষে, দেখুন এটি কোন নোডের প্রতিবেশী হতে খুঁজে বের করে সর্বোচ্চ কাছের k সংখ্যক প্রতিবেশী কে । এবং এদের থেকে সর্বোচ্চ সংখ্যক প্রতিবেশী যে প্রকারের আপনার Mystery Point টি ও সেই প্রকারের বলে ঘোষনা করে। মানে এখানেই আপনি জেনে যাবেন আপনার বাসাটি কোন প্রকারের।

উইন্ডোজ ফোনে উদাহরণ:

‌উইন্ডোজ ফোনে আমি একটি ছোট্ট MVVM অ্যাপ বানিয়ে পরীক্ষা করেছি অ্যালগরিদম টি। পুরো কোড শেষে দেয়া আছে। তাই MainviewModel এর কিছু অংশ আমি তুলে দিচ্ছি:

        public MainViewModel()
        {

            TestKnnCommand = new RelayCommand(TestKnnAction);
            LoadSamples();
            AddASample();
        }

        private void TestKnnAction()
        {
            NodeCollection.DetermineUnknown();
        }

        private void LoadSamples()
        {

            using(StreamReader reader= new StreamReader("SampleData.txt"))
            {
                var data = reader.ReadToEnd();
                List<Node> datArray = JsonConvert.DeserializeObject<List<Node>>(data, new StringToEnumConverter());
                var _sampleCollection = new ObservableCollection<Node>(datArray);

                NodeCollection = new NodeList();
                NodeCollection.K = 3;
                NodeCollection.Nodes = _sampleCollection;

            }

        }

        private void AddASample()
        {
            Node newNode = new Node() { Area = 500, Rooms = 2, Type = NodeType.unknown };
            NodeCollection.Nodes.Add(newNode);
        }

আমি শুধু শুরুতে লোড করে নিয়েছি আমার স্যাম্পল ডাটা গুলো। এরপর যোগ করে নিয়েছি একটি অজানা Mystery Point , মানে এমন একটি বাসা যেটি কোন প্রকারের আমি জানিনা। K এখানে ৩, তার অর্থ হচ্ছে আমি ৩ টি কাছের প্রতিবেশী ব্যবহার করবো classification এর জন্যে।
TestKnnAction() একটি action যেটি একটি বাটন হতে command এর সাহায্যে invoke করা হয়। এবং এটি অজানা পয়েন্ট টির ক্ল্যাসিফিকেশন বের করার জন্য DetermineUnknown() ব্যবহার করে। আপনি চাইলে একাধিক স্যাম্পল যোগ করে অ্যালগরিদম টি চালিয়ে দেখতে পারেন।

জেনে রাখা ভালো:

অন্য সকল ক্লাসিফিকেশন অ্যালগরিদম এর মতোই এটিতেও সমস্যা আছে। আপনার ডাটা সেট যদি পৃথক করার যোগ্য (Separable) হয় তবেই এটি বেশ ভালো কাজ করে। আর যদি তা না হয় আপনি এমন একটি ছক পাবেন যেটায় বিন্দু গুলো সব জায়গায় ছড়িয়ে আছে এবং ফলশ্রুতিতে আপনি ভালো ফলাফল পাবেন না। বরং আপনি যদি ছকে দেখেন বিন্দুগুলো cluster বা গুচ্ছে গুচ্ছে বিভক্ত তাহলে বুঝবেন এটি Separable বা পৃথক করার যোগ্য। আরেকটি জিনিস, নোডসংখ্যা বাড়লে আপনার ক্যালকুলেশন টাইম ও বাড়বে। তাই আপনি চাইলে Pruning করতে পারেন। যেমন যে বাসার Room সংখ্যা ২ তার জন্যে যে সকল বাসা যাদের room সংখ্যা ৬ এর অধিক তাদের সাথে দূরত্ব বের করা অর্থহীন।

আশা করি সবার ভালো লাগবে। অ্যাপ এর দুটি স্ক্রিনশট তুলে দিলাম।

Initial KNN Graph

classification result

কোডটুকু পাওয়া যাবে এখানে। যদিও আপনার Telerik Rad Controls for Windows Phone 8 এর লাইসেন্স থাকা লাগবে এটি বিল্ড করার জন্যে। তবুও দিয়ে দিলাম : http://1drv.ms/1DOTVu8