Skip to content

Commit 02a857a

Browse files
authored
Add DataView Type Register (supporting loading in-memory image and other in-memory custom types) (#3263)
1 parent a1b5eaa commit 02a857a

File tree

14 files changed

+1013
-44
lines changed

14 files changed

+1013
-44
lines changed
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Microsoft.ML;
5+
using Microsoft.ML.Data;
6+
7+
namespace Samples.Dynamic
8+
{
9+
class CustomMappingWithInMemoryCustomType
10+
{
11+
static public void Example()
12+
{
13+
var mlContext = new MLContext();
14+
// Build in-memory data.
15+
var tribe = new List<AlienHero>() { new AlienHero("ML.NET", 2, 1000, 2000, 3000, 4000, 5000, 6000, 7000) };
16+
17+
// Build a ML.NET pipeline and make prediction.
18+
var tribeDataView = mlContext.Data.LoadFromEnumerable(tribe);
19+
var pipeline = mlContext.Transforms.CustomMapping(AlienFusionProcess.GetMapping(), contractName: null);
20+
var model = pipeline.Fit(tribeDataView);
21+
var tribeTransformed = model.Transform(tribeDataView);
22+
23+
// Print out prediction produced by the model.
24+
var firstAlien = mlContext.Data.CreateEnumerable<SuperAlienHero>(tribeTransformed, false).First();
25+
Console.WriteLine($"We got a super alien with name {firstAlien.Name}, age {firstAlien.Merged.Age}, " +
26+
$"height {firstAlien.Merged.Height}, weight {firstAlien.Merged.Weight}, and {firstAlien.Merged.HandCount} hands.");
27+
28+
// Expected output:
29+
// We got a super alien with name Super Unknown, age 4002, height 6000, weight 8000, and 10000 hands.
30+
31+
// Create a prediction engine and print out its prediction.
32+
var engine = mlContext.Model.CreatePredictionEngine<AlienHero, SuperAlienHero>(model);
33+
var alien = new AlienHero("TEN.LM", 1, 2, 3, 4, 5, 6, 7, 8);
34+
var superAlien = engine.Predict(alien);
35+
Console.Write($"We got a super alien with name {superAlien.Name}, age {superAlien.Merged.Age}, " +
36+
$"height {superAlien.Merged.Height}, weight {superAlien.Merged.Weight}, and {superAlien.Merged.HandCount} hands.");
37+
38+
// Expected output:
39+
// We got a super alien with name Super Unknown, age 6, height 8, weight 10, and 12 hands.
40+
}
41+
42+
// A custom type which ML.NET doesn't know yet. Its value will be loaded as a DataView column in this test.
43+
private class AlienBody
44+
{
45+
public int Age { get; set; }
46+
public float Height { get; set; }
47+
public float Weight { get; set; }
48+
public int HandCount { get; set; }
49+
50+
public AlienBody(int age, float height, float weight, int handCount)
51+
{
52+
Age = age;
53+
Height = height;
54+
Weight = weight;
55+
HandCount = handCount;
56+
}
57+
}
58+
59+
// DataViewTypeAttribute applied to class AlienBody members.
60+
private sealed class AlienTypeAttributeAttribute : DataViewTypeAttribute
61+
{
62+
public int RaceId { get; }
63+
64+
// Create an DataViewTypeAttribute> from raceId to a AlienBody.
65+
public AlienTypeAttributeAttribute(int raceId)
66+
{
67+
RaceId = raceId;
68+
}
69+
70+
// A function implicitly invoked by ML.NET when processing a custom type.
71+
// It binds a DataViewType to a custom type plus its attributes.
72+
public override void Register()
73+
{
74+
DataViewTypeManager.Register(new DataViewAlienBodyType(RaceId), typeof(AlienBody), new[] { this });
75+
}
76+
77+
public override bool Equals(DataViewTypeAttribute other)
78+
{
79+
if (other is AlienTypeAttributeAttribute)
80+
return RaceId == ((AlienTypeAttributeAttribute)other).RaceId;
81+
return false;
82+
}
83+
84+
public override int GetHashCode() => RaceId.GetHashCode();
85+
}
86+
87+
// 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.
88+
// It will be the input of AlienFusionProcess.MergeBody(AlienHero, SuperAlienHero).
89+
//
90+
// The members One> and Two" would be mapped to different types inside ML.NET type system because they
91+
// have different AlienTypeAttributeAttribute's. For example, the column type of One would be DataViewAlienBodyType
92+
// with RaceId=100.
93+
// </summary>
94+
private class AlienHero
95+
{
96+
public string Name { get; set; }
97+
98+
[AlienTypeAttribute(100)]
99+
public AlienBody One { get; set; }
100+
101+
[AlienTypeAttribute(200)]
102+
public AlienBody Two { get; set; }
103+
104+
public AlienHero()
105+
{
106+
Name = "Unknown";
107+
One = new AlienBody(0, 0, 0, 0);
108+
Two = new AlienBody(0, 0, 0, 0);
109+
}
110+
111+
public AlienHero(string name,
112+
int age, float height, float weight, int handCount,
113+
int anotherAge, float anotherHeight, float anotherWeight, int anotherHandCount)
114+
{
115+
Name = "Unknown";
116+
One = new AlienBody(age, height, weight, handCount);
117+
Two = new AlienBody(anotherAge, anotherHeight, anotherWeight, anotherHandCount);
118+
}
119+
}
120+
121+
// Type of AlienBody in ML.NET's type system.
122+
// It usually shows up as DataViewSchema.Column.Type among IDataView.Schema.
123+
private class DataViewAlienBodyType : StructuredDataViewType
124+
{
125+
public int RaceId { get; }
126+
127+
public DataViewAlienBodyType(int id) : base(typeof(AlienBody))
128+
{
129+
RaceId = id;
130+
}
131+
132+
public override bool Equals(DataViewType other)
133+
{
134+
if (other is DataViewAlienBodyType otherAlien)
135+
return otherAlien.RaceId == RaceId;
136+
return false;
137+
}
138+
139+
public override int GetHashCode()
140+
{
141+
return RaceId.GetHashCode();
142+
}
143+
}
144+
145+
// The output type of processing AlienHero using AlienFusionProcess.MergeBody(AlienHero, SuperAlienHero).
146+
private class SuperAlienHero
147+
{
148+
public string Name { get; set; }
149+
150+
[AlienTypeAttribute(007)]
151+
public AlienBody Merged { get; set; }
152+
153+
public SuperAlienHero()
154+
{
155+
Name = "Unknown";
156+
Merged = new AlienBody(0, 0, 0, 0);
157+
}
158+
}
159+
160+
// The implementation of custom mapping is MergeBody. It accepts AlienHero and produces SuperAlienHero.
161+
private class AlienFusionProcess
162+
{
163+
public static void MergeBody(AlienHero input, SuperAlienHero output)
164+
{
165+
output.Name = "Super " + input.Name;
166+
output.Merged.Age = input.One.Age + input.Two.Age;
167+
output.Merged.Height = input.One.Height + input.Two.Height;
168+
output.Merged.Weight = input.One.Weight + input.Two.Weight;
169+
output.Merged.HandCount = input.One.HandCount + input.Two.HandCount;
170+
}
171+
172+
public static Action<AlienHero, SuperAlienHero> GetMapping()
173+
{
174+
return MergeBody;
175+
}
176+
}
177+
178+
}
179+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using System;
2+
using System.Drawing;
3+
using Microsoft.ML;
4+
using Microsoft.ML.Transforms.Image;
5+
6+
namespace Samples.Dynamic
7+
{
8+
class ConvertToGrayScaleInMemory
9+
{
10+
static public void Example()
11+
{
12+
var mlContext = new MLContext();
13+
// Create an image list.
14+
var images = new[] { new ImageDataPoint(2, 3, Color.Blue), new ImageDataPoint(2, 3, Color.Red) };
15+
16+
// Convert the list of data points to an IDataView object, which is consumable by ML.NET API.
17+
var data = mlContext.Data.LoadFromEnumerable(images);
18+
19+
// Convert image to gray scale.
20+
var pipeline = mlContext.Transforms.ConvertToGrayscale("GrayImage", "Image");
21+
22+
// Fit the model.
23+
var model = pipeline.Fit(data);
24+
25+
// Test path: image files -> IDataView -> Enumerable of Bitmaps.
26+
var transformedData = model.Transform(data);
27+
28+
// Load images in DataView back to Enumerable.
29+
var transformedDataPoints = mlContext.Data.CreateEnumerable<ImageDataPoint>(transformedData, false);
30+
31+
// Print out input and output pixels.
32+
foreach (var dataPoint in transformedDataPoints)
33+
{
34+
var image = dataPoint.Image;
35+
var grayImage = dataPoint.GrayImage;
36+
for (int x = 0; x < grayImage.Width; ++x)
37+
{
38+
for (int y = 0; y < grayImage.Height; ++y)
39+
{
40+
var pixel = image.GetPixel(x, y);
41+
var grayPixel = grayImage.GetPixel(x, y);
42+
Console.WriteLine($"The original pixel is {pixel} and its pixel in gray is {grayPixel}");
43+
}
44+
}
45+
}
46+
47+
// Expected output:
48+
// 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]
49+
// 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]
50+
// 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]
51+
// 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]
52+
// 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]
53+
// 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]
54+
// 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]
55+
// 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]
56+
// 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]
57+
// 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]
58+
// 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]
59+
// 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]
60+
}
61+
62+
private class ImageDataPoint
63+
{
64+
[ImageType(3, 4)]
65+
public Bitmap Image { get; set; }
66+
67+
[ImageType(3, 4)]
68+
public Bitmap GrayImage { get; set; }
69+
70+
public ImageDataPoint()
71+
{
72+
Image = null;
73+
GrayImage = null;
74+
}
75+
76+
public ImageDataPoint(int width, int height, Color color)
77+
{
78+
Image = new Bitmap(width, height);
79+
for (int i = 0; i < width; ++i)
80+
for (int j = 0; j < height; ++j)
81+
Image.SetPixel(i, j, color);
82+
}
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)