Skip to content

Commit

Permalink
Add DataView Type Register (supporting loading in-memory image and ot…
Browse files Browse the repository at this point in the history
…her in-memory custom types) (#3263)
  • Loading branch information
wschin authored May 28, 2019
1 parent a1b5eaa commit 02a857a
Show file tree
Hide file tree
Showing 14 changed files with 1,013 additions and 44 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.ML;
using Microsoft.ML.Data;

namespace Samples.Dynamic
{
class CustomMappingWithInMemoryCustomType
{
static public void Example()
{
var mlContext = new MLContext();
// Build in-memory data.
var tribe = new List<AlienHero>() { new AlienHero("ML.NET", 2, 1000, 2000, 3000, 4000, 5000, 6000, 7000) };

// Build a ML.NET pipeline and make prediction.
var tribeDataView = mlContext.Data.LoadFromEnumerable(tribe);
var pipeline = mlContext.Transforms.CustomMapping(AlienFusionProcess.GetMapping(), contractName: null);
var model = pipeline.Fit(tribeDataView);
var tribeTransformed = model.Transform(tribeDataView);

// Print out prediction produced by the model.
var firstAlien = mlContext.Data.CreateEnumerable<SuperAlienHero>(tribeTransformed, false).First();
Console.WriteLine($"We got a super alien with name {firstAlien.Name}, age {firstAlien.Merged.Age}, " +
$"height {firstAlien.Merged.Height}, weight {firstAlien.Merged.Weight}, and {firstAlien.Merged.HandCount} hands.");

// Expected output:
// We got a super alien with name Super Unknown, age 4002, height 6000, weight 8000, and 10000 hands.

// Create a prediction engine and print out its prediction.
var engine = mlContext.Model.CreatePredictionEngine<AlienHero, SuperAlienHero>(model);
var alien = new AlienHero("TEN.LM", 1, 2, 3, 4, 5, 6, 7, 8);
var superAlien = engine.Predict(alien);
Console.Write($"We got a super alien with name {superAlien.Name}, age {superAlien.Merged.Age}, " +
$"height {superAlien.Merged.Height}, weight {superAlien.Merged.Weight}, and {superAlien.Merged.HandCount} hands.");

// Expected output:
// We got a super alien with name Super Unknown, age 6, height 8, weight 10, and 12 hands.
}

// A custom type which ML.NET doesn't know yet. Its value will be loaded as a DataView column in this test.
private class AlienBody
{
public int Age { get; set; }
public float Height { get; set; }
public float Weight { get; set; }
public int HandCount { get; set; }

public AlienBody(int age, float height, float weight, int handCount)
{
Age = age;
Height = height;
Weight = weight;
HandCount = handCount;
}
}

// DataViewTypeAttribute applied to class AlienBody members.
private sealed class AlienTypeAttributeAttribute : DataViewTypeAttribute
{
public int RaceId { get; }

// Create an DataViewTypeAttribute> from raceId to a AlienBody.
public AlienTypeAttributeAttribute(int raceId)
{
RaceId = raceId;
}

// A function implicitly invoked by ML.NET when processing a custom type.
// It binds a DataViewType to a custom type plus its attributes.
public override void Register()
{
DataViewTypeManager.Register(new DataViewAlienBodyType(RaceId), typeof(AlienBody), new[] { this });
}

public override bool Equals(DataViewTypeAttribute other)
{
if (other is AlienTypeAttributeAttribute)
return RaceId == ((AlienTypeAttributeAttribute)other).RaceId;
return false;
}

public override int GetHashCode() => RaceId.GetHashCode();
}

// A custom class with a type which ML.NET doesn't know yet. Its value will be loaded as a DataView row in this test.
// It will be the input of AlienFusionProcess.MergeBody(AlienHero, SuperAlienHero).
//
// The members One> and Two" would be mapped to different types inside ML.NET type system because they
// have different AlienTypeAttributeAttribute's. For example, the column type of One would be DataViewAlienBodyType
// with RaceId=100.
// </summary>
private class AlienHero
{
public string Name { get; set; }

[AlienTypeAttribute(100)]
public AlienBody One { get; set; }

[AlienTypeAttribute(200)]
public AlienBody Two { get; set; }

public AlienHero()
{
Name = "Unknown";
One = new AlienBody(0, 0, 0, 0);
Two = new AlienBody(0, 0, 0, 0);
}

public AlienHero(string name,
int age, float height, float weight, int handCount,
int anotherAge, float anotherHeight, float anotherWeight, int anotherHandCount)
{
Name = "Unknown";
One = new AlienBody(age, height, weight, handCount);
Two = new AlienBody(anotherAge, anotherHeight, anotherWeight, anotherHandCount);
}
}

// Type of AlienBody in ML.NET's type system.
// It usually shows up as DataViewSchema.Column.Type among IDataView.Schema.
private class DataViewAlienBodyType : StructuredDataViewType
{
public int RaceId { get; }

public DataViewAlienBodyType(int id) : base(typeof(AlienBody))
{
RaceId = id;
}

public override bool Equals(DataViewType other)
{
if (other is DataViewAlienBodyType otherAlien)
return otherAlien.RaceId == RaceId;
return false;
}

public override int GetHashCode()
{
return RaceId.GetHashCode();
}
}

// The output type of processing AlienHero using AlienFusionProcess.MergeBody(AlienHero, SuperAlienHero).
private class SuperAlienHero
{
public string Name { get; set; }

[AlienTypeAttribute(007)]
public AlienBody Merged { get; set; }

public SuperAlienHero()
{
Name = "Unknown";
Merged = new AlienBody(0, 0, 0, 0);
}
}

// The implementation of custom mapping is MergeBody. It accepts AlienHero and produces SuperAlienHero.
private class AlienFusionProcess
{
public static void MergeBody(AlienHero input, SuperAlienHero output)
{
output.Name = "Super " + input.Name;
output.Merged.Age = input.One.Age + input.Two.Age;
output.Merged.Height = input.One.Height + input.Two.Height;
output.Merged.Weight = input.One.Weight + input.Two.Weight;
output.Merged.HandCount = input.One.HandCount + input.Two.HandCount;
}

public static Action<AlienHero, SuperAlienHero> GetMapping()
{
return MergeBody;
}
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System;
using System.Drawing;
using Microsoft.ML;
using Microsoft.ML.Transforms.Image;

namespace Samples.Dynamic
{
class ConvertToGrayScaleInMemory
{
static public void Example()
{
var mlContext = new MLContext();
// Create an image list.
var images = new[] { new ImageDataPoint(2, 3, Color.Blue), new ImageDataPoint(2, 3, Color.Red) };

// Convert the list of data points to an IDataView object, which is consumable by ML.NET API.
var data = mlContext.Data.LoadFromEnumerable(images);

// Convert image to gray scale.
var pipeline = mlContext.Transforms.ConvertToGrayscale("GrayImage", "Image");

// Fit the model.
var model = pipeline.Fit(data);

// Test path: image files -> IDataView -> Enumerable of Bitmaps.
var transformedData = model.Transform(data);

// Load images in DataView back to Enumerable.
var transformedDataPoints = mlContext.Data.CreateEnumerable<ImageDataPoint>(transformedData, false);

// Print out input and output pixels.
foreach (var dataPoint in transformedDataPoints)
{
var image = dataPoint.Image;
var grayImage = dataPoint.GrayImage;
for (int x = 0; x < grayImage.Width; ++x)
{
for (int y = 0; y < grayImage.Height; ++y)
{
var pixel = image.GetPixel(x, y);
var grayPixel = grayImage.GetPixel(x, y);
Console.WriteLine($"The original pixel is {pixel} and its pixel in gray is {grayPixel}");
}
}
}

// Expected output:
// The original pixel is Color[A = 255, R = 0, G = 0, B = 255] and its pixel in gray is Color[A = 255, R = 28, G = 28, B = 28]
// The original pixel is Color[A = 255, R = 0, G = 0, B = 255] and its pixel in gray is Color[A = 255, R = 28, G = 28, B = 28]
// The original pixel is Color[A = 255, R = 0, G = 0, B = 255] and its pixel in gray is Color[A = 255, R = 28, G = 28, B = 28]
// The original pixel is Color[A = 255, R = 0, G = 0, B = 255] and its pixel in gray is Color[A = 255, R = 28, G = 28, B = 28]
// The original pixel is Color[A = 255, R = 0, G = 0, B = 255] and its pixel in gray is Color[A = 255, R = 28, G = 28, B = 28]
// The original pixel is Color[A = 255, R = 0, G = 0, B = 255] and its pixel in gray is Color[A = 255, R = 28, G = 28, B = 28]
// The original pixel is Color[A = 255, R = 255, G = 0, B = 0] and its pixel in gray is Color[A = 255, R = 77, G = 77, B = 77]
// The original pixel is Color[A = 255, R = 255, G = 0, B = 0] and its pixel in gray is Color[A = 255, R = 77, G = 77, B = 77]
// The original pixel is Color[A = 255, R = 255, G = 0, B = 0] and its pixel in gray is Color[A = 255, R = 77, G = 77, B = 77]
// The original pixel is Color[A = 255, R = 255, G = 0, B = 0] and its pixel in gray is Color[A = 255, R = 77, G = 77, B = 77]
// The original pixel is Color[A = 255, R = 255, G = 0, B = 0] and its pixel in gray is Color[A = 255, R = 77, G = 77, B = 77]
// The original pixel is Color[A = 255, R = 255, G = 0, B = 0] and its pixel in gray is Color[A = 255, R = 77, G = 77, B = 77]
}

private class ImageDataPoint
{
[ImageType(3, 4)]
public Bitmap Image { get; set; }

[ImageType(3, 4)]
public Bitmap GrayImage { get; set; }

public ImageDataPoint()
{
Image = null;
GrayImage = null;
}

public ImageDataPoint(int width, int height, Color color)
{
Image = new Bitmap(width, height);
for (int i = 0; i < width; ++i)
for (int j = 0; j < height; ++j)
Image.SetPixel(i, j, color);
}
}
}
}
Loading

0 comments on commit 02a857a

Please sign in to comment.