Fluent.Interface



Multi-Threading, Silverlight Sockets & Visualisation Part 3

Part 2 of this Silverlight visualisation instalment discussed how data could be streamed over a socket for rendering on the client.

To recap, Part 1 discussed how the Training Set data was loaded from file and stored in memory.  Well the data structure is as follows:

    private void SetImage(Stream stream)
    {
        BitmapImage bi = new BitmapImage();
        bi.SetSource(stream);
        this.Image1.Source = null;
        this.Image1.Source = bi;
    }
    [XmlType("T")]
    public struct TrianglePoints
    {
        public int P1 { get; set; }
        public int P2 { get; set; }
        public int P3 { get; set; }
    }

    [XmlType("P")]
    public struct TrianglePoint
    {
        public float X { get; set; }
        public float Y { get; set; }
    }

    /// <summary>
    /// A container for all the data that needs to be sent over the wire to render the image
    /// </summary>
    [XmlType("D")]
    public class TrainingSetData
    {
        public int Index { get; set; }
        public TrianglePoint[] Points { get; set; }
        public TrianglePoints[] Triangles { get; set; }
        public int Height { get; set; }
        public int Width { get; set; }
        [XmlIgnore]
        public byte[] Image { get; set; }
        public string ImageUrl { get; set; }
        public string ImageContentType { get; set; }
    }

Notice these classes have been attributed so that can be XML serialized, but that the Image blob is ignored given it is sent over as a separate block.  In addition the Socket Server that broadcasts the training set data has a configuration flag to disable streaming the image at all.  In which case the client can download the image via the specified Url.

Silverlight supports loading an image in a number of ways

1.       Relative or Absoulte Uri’s.

2.       Set directly via resource stream (loaded from memory, file etc).

3.       Downloading on demand using the asynchronous web client.

Option 1 is not appropriate given my images are dynamic and change on each request, so I have implemented Option 2 (when image is streamed over socket), and Option3 (when downloading) as the following code illustrates:

    private void RenderImage(TrainingSetData data, byte[] image)
    {
        try
        {
            if (image == null || image.Length == 0)
            {
                // Fire off a request to the web service to download the image
                if (!String.IsNullOrEmpty(data.ImageUrl))
                {
                    WebClient wc = new WebClient();
                    wc.OpenReadCompleted += new OpenReadCompletedEventHandler(Image_OpenReadCompleted);
                    wc.OpenReadAsync(new Uri(data.ImageUrl, UriKind.Absolute), data.Index);
                    System.Diagnostics.Debug.WriteLine("Image Requested");
                }
                else
                {
                    System.Diagnostics.Debug.WriteLine("Empty Image Url");
                }
            }
            else
            {
                // Load the image setting the content type, so it knows how to read the file
                using (MemoryStream ms = new MemoryStream(image))
                {
                    StreamResourceInfo resource = new StreamResourceInfo(ms, null);
                    SetImage(resource.Stream);
                }
                System.Diagnostics.Debug.WriteLine("Image Streamed");
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine("Error rendering image: " + ex);
        }
    }
    void Image_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
    {
        if (e.Error == null)
        {
            try
            {
                StreamResourceInfo sri = new StreamResourceInfo(e.Result as Stream, null);
                SetImage(sri.Stream);
                System.Diagnostics.Debug.WriteLine("Image downloaded");
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Error downloading image: " + ex);
            }
        }
    }

The ImageUrl in this case is served up by the WCF service as configured by the Socket Service:

<appSettings>

  <add key="ModelFilename" value="..\..\..\..\data\face\models\tim_face.smd" />

  <add key="Loader.VisualisationServiceUri" value="http://localhost:1236/VisualisationService.svc" />

</appSettings>

This WCF service uses the .NET 3.5 extensions to serve up the images in a RESTful manner

    [ServiceContract]
    public interface IVisualisationService
    {
        [OperationContract]
        [WebGet(UriTemplate = "image/{index}")]
        Stream GetImage(string index);
    }

And the following implementation:

    public class VisualisationService : IVisualisationService
    {
        /// <summary>
        /// Gets the image.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <returns></returns>
        public Stream GetImage(string index)
        {
            // Pull the model filename from config
            string modelFilename = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, 
                ConfigurationManager.AppSettings["ModelFilename"]));
            int i = Int32.Parse(index);

            // Load the training data
            VisualisationLoader loader = new VisualisationLoader();
            TrainingSetData d = loader.Read(modelFilename)[i];
            MemoryStream ms = new MemoryStream(d.Image, false);

            // Set the content type and return stream
            WebOperationContext.Current.OutgoingResponse.ContentType = d.ImageContentType;
            return ms;
        }
    }

After applying the appropriate webHttpBinding configuration, the only other consider is to include a crossdomain.xml file in the web server root, otherwise Silverlight will through a protocol error.

<?xml version="1.0"?>

<!–
http://www.mysite.com/crossdomain.xml
–>

<cross-domain-policy>

  <allow-access-from domain="*" />

</cross-domain-policy>

It is also worth pointing out that you must be running under a web project to test download of Images.  See this blog post for this and more helpful tips.

From my testing send the image stream directly over the socket resulted in better performance, but the delay in ‘downloading’ the image once the process fired up was not significant.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: