Skip to content

Commit 018293c

Browse files
authored
Remove the System.Drawing dependency (#6363)
1 parent da53e55 commit 018293c

File tree

27 files changed

+1441
-686
lines changed

27 files changed

+1441
-686
lines changed

THIRD-PARTY-NOTICES.TXT

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,4 @@ Unless required by applicable law or agreed to in writing, software
8484
distributed under the License is distributed on an "AS IS" BASIS,
8585
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8686
See the License for the specific language governing permissions and
87-
limitations under the License.
87+
limitations under the License.

build/ci/job-template.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,11 @@ jobs:
6868
steps:
6969
# Extra MacOS step required to install OS-specific dependencies
7070
- ${{ if and(contains(parameters.pool.vmImage, 'macOS'), not(contains(parameters.name, 'cross'))) }}:
71-
- script: export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=TRUE && brew update && brew install mono-libgdiplus && brew unlink libomp && brew install $(Build.SourcesDirectory)/build/libomp.rb --build-from-source --formula
71+
- script: export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=TRUE && brew update && brew unlink libomp && brew install $(Build.SourcesDirectory)/build/libomp.rb --build-from-source --formula
7272
displayName: Install MacOS build dependencies
7373
# Extra Apple MacOS step required to install OS-specific dependencies
7474
- ${{ if and(contains(parameters.pool.vmImage, 'macOS'), contains(parameters.name, 'cross')) }}:
75-
- script: brew update && brew install mono-libgdiplus && brew install libomp && brew link libomp --force
75+
- script: brew update && brew install libomp && brew link libomp --force
7676
displayName: Install MacOS ARM build dependencies
7777
- ${{ if and( eq(parameters.nightlyBuild, 'true'), eq(parameters.pool.vmImage, 'ubuntu-18.04')) }}:
7878
- bash: echo "##vso[task.setvariable variable=LD_LIBRARY_PATH]$(nightlyBuildRunPath):$LD_LIBRARY_PATH"

build/vsts-ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ jobs:
108108
pool:
109109
vmImage: macOS-12
110110
steps:
111-
- script: brew update && brew unlink [email protected] && brew install mono-libgdiplus && brew install $(Build.SourcesDirectory)/build/libomp.rb --build-from-source --formula && brew link libomp --force
111+
- script: brew update && brew unlink [email protected] && brew install $(Build.SourcesDirectory)/build/libomp.rb --build-from-source --formula && brew link libomp --force
112112
displayName: Install build dependencies
113113
- script: ./restore.sh
114114
displayName: restore all projects
@@ -138,7 +138,7 @@ jobs:
138138
rm -rf /usr/local/bin/2to3
139139
displayName: MacOS Homebrew bug Workaround
140140
continueOnError: true
141-
- script: brew update && brew unlink [email protected] && brew install mono-libgdiplus && brew install libomp && brew link libomp --force
141+
- script: brew update && brew unlink [email protected] && brew install libomp && brew link libomp --force
142142
displayName: Install build dependencies
143143
- script: ./restore.sh
144144
displayName: restore all projects

docs/building/unix-instructions.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,12 @@ macOS 10.13 (High Sierra) or higher is needed to build dotnet/machinelearning. W
6565
On macOS a few components are needed which are not provided by a default developer setup:
6666
* cmake 3.10.3
6767
* libomp 7
68-
* libgdiplus
6968
* gettext
7069
* All the requirements necessary to run .NET Core 3.1 applications. To view macOS prerequisites click [here](https://docs.microsoft.com/en-us/dotnet/core/install/macos?tabs=netcore31#dependencies).
7170

7271
One way of obtaining CMake and other required libraries is via [Homebrew](https://brew.sh):
7372
```sh
74-
$ brew update && brew install cmake https://github.com/raw/dotnet/machinelearning/main/build/libomp.rb mono-libgdiplus gettext && brew link gettext --force && brew link libomp --force
73+
$ brew update && brew install cmake https://github.com/raw/dotnet/machinelearning/main/build/libomp.rb gettext && brew link gettext --force && brew link libomp --force
7574
```
7675

7776
Please note that newer versions of Homebrew [don't allow installing directly from a URL](https://github.com/Homebrew/brew/issues/8791). If you run into this issue, you may need to download libomp.rb first and install it with the local file instead.

docs/samples/Microsoft.ML.Samples/Dynamic/Trainers/MulticlassClassification/ImageClassification/LearningRateSchedulingCifarResnetTransferLearning.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,10 @@ public static void Example()
7878
{
7979
FeatureColumnName = "Image",
8080
LabelColumnName = "Label",
81-
// Just by changing/selecting InceptionV3/MobilenetV2
82-
// here instead of
81+
// Just by changing/selecting InceptionV3/MobilenetV2
82+
// here instead of
8383
// ResnetV2101 you can try a different architecture/
84-
// pre-trained model.
84+
// pre-trained model.
8585
Arch = ImageClassificationTrainer.Architecture.ResnetV2101,
8686
Epoch = 182,
8787
BatchSize = 128,
@@ -92,8 +92,8 @@ public static void Example()
9292
ReuseTrainSetBottleneckCachedValues = false,
9393
// Use linear scaling rule and Learning rate decay as an option
9494
// This is known to do well for Cifar dataset and Resnet models
95-
// You can also try other types of Learning rate scheduling
96-
// methods available in LearningRateScheduler.cs
95+
// You can also try other types of Learning rate scheduling
96+
// methods available in LearningRateScheduler.cs
9797
LearningRateScheduler = new LsrDecay()
9898
};
9999

@@ -111,7 +111,7 @@ public static void Example()
111111

112112
// Train the model.
113113
// This involves calculating the bottleneck values, and then
114-
// training the final layer. Sample output is:
114+
// training the final layer. Sample output is:
115115
// Phase: Bottleneck Computation, Dataset used: Train, Image Index: 1
116116
// Phase: Bottleneck Computation, Dataset used: Train, Image Index: 2
117117
// ...
@@ -271,8 +271,9 @@ public static string DownloadImageSet(string imagesDownloadFolder)
271271
// get a set of images to teach the network about the new classes
272272
// CIFAR dataset ( 50000 train images and 10000 test images )
273273
string fileName = "cifar10.zip";
274-
string url = $"https://aka.ms/mlnet-resources/" +
275-
"datasets/cifar10.zip";
274+
275+
// https://github.com/YoongiKim/CIFAR-10-images
276+
string url = $"https://github.com/YoongiKim/CIFAR-10-images/archive/refs/heads/master.zip";
276277

277278
Download(url, imagesDownloadFolder, fileName);
278279
UnZip(Path.Combine(imagesDownloadFolder, fileName),

docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ApplyONNXModelWithInMemoryImages.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Drawing;
32
using System.Linq;
43
using Microsoft.ML;
54
using Microsoft.ML.Data;
@@ -26,8 +25,8 @@ public static void Example()
2625
// input /output of the used ONNX model.
2726
var dataPoints = new ImageDataPoint[]
2827
{
29-
new ImageDataPoint(Color.Red),
30-
new ImageDataPoint(Color.Green)
28+
new ImageDataPoint(red: 255, green: 0, blue: 0), // Red color
29+
new ImageDataPoint(red: 0, green: 128, blue: 0) // Green color
3130
};
3231

3332
// Convert training data to IDataView, the general data type used in
@@ -91,7 +90,7 @@ private class ImageDataPoint
9190

9291
// Image will be consumed by ONNX image multiclass classification model.
9392
[ImageType(height, width)]
94-
public Bitmap Image { get; set; }
93+
public MLImage Image { get; set; }
9594

9695
// Expected output of ONNX model. It contains probabilities of all
9796
// classes. Note that the ColumnName below should match the output name
@@ -104,12 +103,19 @@ public ImageDataPoint()
104103
Image = null;
105104
}
106105

107-
public ImageDataPoint(Color color)
106+
public ImageDataPoint(byte red, byte green, byte blue)
108107
{
109-
Image = new Bitmap(width, height);
110-
for (int i = 0; i < width; ++i)
111-
for (int j = 0; j < height; ++j)
112-
Image.SetPixel(i, j, color);
108+
byte[] imageData = new byte[width * height * 4]; // 4 for the red, green, blue and alpha colors
109+
for (int i = 0; i < imageData.Length; i += 4)
110+
{
111+
// Fill the buffer with the Bgra32 format
112+
imageData[i] = blue;
113+
imageData[i + 1] = green;
114+
imageData[i + 2] = red;
115+
imageData[i + 3] = 255;
116+
}
117+
118+
Image = MLImage.CreateFromPixels(width, height, MLPixelFormat.Bgra32, imageData);
113119
}
114120
}
115121
}

docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ApplyOnnxModel.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.IO;
23
using System.Linq;
34
using Microsoft.ML;
45
using Microsoft.ML.Data;
@@ -10,8 +11,9 @@ public static class ApplyOnnxModel
1011
public static void Example()
1112
{
1213
// Download the squeeznet image model from ONNX model zoo, version 1.2
13-
// https://github.com/onnx/models/tree/master/squeezenet or use
14-
// Microsoft.ML.Onnx.TestModels nuget.
14+
// https://github.com/onnx/models/tree/master/squeezenet or
15+
// https://s3.amazonaws.com/download.onnx/models/opset_8/squeezenet.tar.gz
16+
// or use Microsoft.ML.Onnx.TestModels nuget.
1517
var modelPath = @"squeezenet\00000001\model.onnx";
1618

1719
// Create ML pipeline to score the data using OnnxScoringEstimator
@@ -56,7 +58,7 @@ public static void Example()
5658
// inputSize is the overall dimensions of the model input tensor.
5759
private const int inputSize = 224 * 224 * 3;
5860

59-
// A class to hold sample tensor data. Member name should match
61+
// A class to hold sample tensor data. Member name should match
6062
// the inputs that the model expects (in this case, data_0)
6163
public class TensorData
6264
{

docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScale.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Drawing;
32
using System.IO;
43
using Microsoft.ML;
54
using Microsoft.ML.Data;
@@ -9,7 +8,7 @@ namespace Samples.Dynamic
98
public static class ConvertToGrayscale
109
{
1110
// Sample that loads images from the file system, and converts them to
12-
// grayscale.
11+
// grayscale.
1312
public static void Example()
1413
{
1514
// Create a new ML context, for ML.NET operations. It can be used for
@@ -20,7 +19,7 @@ public static void Example()
2019
// list of the files from the dotnet/machinelearning/test/data/images/.
2120
// If you inspect the fileSystem, after running this line, an "images"
2221
// folder will be created, containing 4 images, and a .tsv file
23-
// enumerating the images.
22+
// enumerating the images.
2423
var imagesDataFile = Microsoft.ML.SamplesUtils.DatasetUtils
2524
.GetSampleImages();
2625

@@ -42,7 +41,7 @@ public static void Example()
4241
}).Load(imagesDataFile);
4342

4443
var imagesFolder = Path.GetDirectoryName(imagesDataFile);
45-
// Image loading pipeline.
44+
// Image loading pipeline.
4645
var pipeline = mlContext.Transforms.LoadImages("ImageObject",
4746
imagesFolder, "ImagePath")
4847
.Append(mlContext.Transforms.ConvertToGrayscale("Grayscale",
@@ -67,23 +66,23 @@ private static void PrintColumns(IDataView transformedData)
6766
.Schema))
6867
{
6968
// Note that it is best to get the getters and values *before*
70-
// iteration, so as to faciliate buffer sharing (if applicable), and
69+
// iteration, so as to facilitate buffer sharing (if applicable), and
7170
// column -type validation once, rather than many times.
7271
ReadOnlyMemory<char> imagePath = default;
7372
ReadOnlyMemory<char> name = default;
74-
Bitmap imageObject = null;
75-
Bitmap grayscaleImageObject = null;
73+
MLImage imageObject = null;
74+
MLImage grayscaleImageObject = null;
7675

7776
var imagePathGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
7877
.Schema["ImagePath"]);
7978

8079
var nameGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
8180
.Schema["Name"]);
8281

83-
var imageObjectGetter = cursor.GetGetter<Bitmap>(cursor.Schema[
82+
var imageObjectGetter = cursor.GetGetter<MLImage>(cursor.Schema[
8483
"ImageObject"]);
8584

86-
var grayscaleGetter = cursor.GetGetter<Bitmap>(cursor.Schema[
85+
var grayscaleGetter = cursor.GetGetter<MLImage>(cursor.Schema[
8786
"Grayscale"]);
8887

8988
while (cursor.MoveNext())
@@ -94,8 +93,8 @@ private static void PrintColumns(IDataView transformedData)
9493
grayscaleGetter(ref grayscaleImageObject);
9594

9695
Console.WriteLine("{0, -25} {1, -25} {2, -25} {3, -25}",
97-
imagePath, name, imageObject.PhysicalDimension,
98-
grayscaleImageObject.PhysicalDimension);
96+
imagePath, name, $"Width={imageObject.Width}, Height={imageObject.Height}",
97+
$"Width={grayscaleImageObject.Width}, Height={grayscaleImageObject.Height}");
9998
}
10099

101100
// Dispose the image.

docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScaleInMemory.cs

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System;
2-
using System.Drawing;
32
using Microsoft.ML;
3+
using Microsoft.ML.Data;
44
using Microsoft.ML.Transforms.Image;
55

66
namespace Samples.Dynamic
@@ -11,16 +11,17 @@ public static void Example()
1111
{
1212
var mlContext = new MLContext();
1313
// Create an image list.
14-
var images = new[] { new ImageDataPoint(2, 3, Color.Blue), new
15-
ImageDataPoint(2, 3, Color.Red) };
14+
var images = new[]
15+
{
16+
new ImageDataPoint(2, 3, red: 0, green: 0, blue: 255), // Blue color
17+
new ImageDataPoint(2, 3, red: 255, green: 0, blue: 0) }; // red color
1618

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

2123
// Convert image to gray scale.
22-
var pipeline = mlContext.Transforms.ConvertToGrayscale("GrayImage",
23-
"Image");
24+
var pipeline = mlContext.Transforms.ConvertToGrayscale("GrayImage", "Image");
2425

2526
// Fit the model.
2627
var model = pipeline.Fit(data);
@@ -37,15 +38,31 @@ public static void Example()
3738
{
3839
var image = dataPoint.Image;
3940
var grayImage = dataPoint.GrayImage;
40-
for (int x = 0; x < grayImage.Width; ++x)
41+
42+
ReadOnlySpan<byte> imageData = image.Pixels;
43+
(int alphaIndex, int redIndex, int greenIndex, int blueIndex) = image.PixelFormat switch
44+
{
45+
MLPixelFormat.Bgra32 => (3, 2, 1, 0),
46+
MLPixelFormat.Rgba32 => (3, 0, 1, 2),
47+
_ => throw new InvalidOperationException($"Image pixel format is not supported")
48+
};
49+
50+
ReadOnlySpan<byte> grayImageData = grayImage.Pixels;
51+
(int alphaIndex1, int redIndex1, int greenIndex1, int blueIndex1) = grayImage.PixelFormat switch
4152
{
42-
for (int y = 0; y < grayImage.Height; ++y)
43-
{
44-
var pixel = image.GetPixel(x, y);
45-
var grayPixel = grayImage.GetPixel(x, y);
46-
Console.WriteLine($"The original pixel is {pixel} and its" +
47-
$"pixel in gray is {grayPixel}");
48-
}
53+
MLPixelFormat.Bgra32 => (3, 2, 1, 0),
54+
MLPixelFormat.Rgba32 => (3, 0, 1, 2),
55+
_ => throw new InvalidOperationException($"Image pixel format is not supported")
56+
};
57+
58+
int pixelSize = image.BitsPerPixel / 8;
59+
60+
for (int i = 0; i < imageData.Length; i += pixelSize)
61+
{
62+
string pixelString = $"[A = {imageData[i + alphaIndex]}, R = {imageData[i + redIndex]}, G = {imageData[i + greenIndex]}, B = {imageData[i + blueIndex]}]";
63+
string grayPixelString = $"[A = {grayImageData[i + alphaIndex1]}, R = {grayImageData[i + redIndex1]}, G = {grayImageData[i + greenIndex1]}, B = {grayImageData[i + blueIndex1]}]";
64+
65+
Console.WriteLine($"The original pixel is {pixelString} and its pixel in gray is {grayPixelString}");
4966
}
5067
}
5168

@@ -67,23 +84,30 @@ public static void Example()
6784
private class ImageDataPoint
6885
{
6986
[ImageType(3, 4)]
70-
public Bitmap Image { get; set; }
87+
public MLImage Image { get; set; }
7188

7289
[ImageType(3, 4)]
73-
public Bitmap GrayImage { get; set; }
90+
public MLImage GrayImage { get; set; }
7491

7592
public ImageDataPoint()
7693
{
7794
Image = null;
7895
GrayImage = null;
7996
}
8097

81-
public ImageDataPoint(int width, int height, Color color)
98+
public ImageDataPoint(int width, int height, byte red, byte green, byte blue)
8299
{
83-
Image = new Bitmap(width, height);
84-
for (int i = 0; i < width; ++i)
85-
for (int j = 0; j < height; ++j)
86-
Image.SetPixel(i, j, color);
100+
byte[] imageData = new byte[width * height * 4]; // 4 for the red, green, blue and alpha colors
101+
for (int i = 0; i < imageData.Length; i += 4)
102+
{
103+
// Fill the buffer with the Bgra32 format
104+
imageData[i] = blue;
105+
imageData[i + 1] = green;
106+
imageData[i + 2] = red;
107+
imageData[i + 3] = 255;
108+
}
109+
110+
Image = MLImage.CreateFromPixels(width, height, MLPixelFormat.Bgra32, imageData);
87111
}
88112
}
89113
}

0 commit comments

Comments
 (0)