File: VectorToImageTransform.cs
Web Access
Project: src\src\Microsoft.ML.ImageAnalytics\Microsoft.ML.ImageAnalytics.csproj (Microsoft.ML.ImageAnalytics)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.ML;
using Microsoft.ML.CommandLine;
using Microsoft.ML.Data;
using Microsoft.ML.Internal.Utilities;
using Microsoft.ML.Runtime;
using Microsoft.ML.Transforms.Image;
 
[assembly: LoadableClass(VectorToImageConvertingTransformer.Summary, typeof(IDataTransform), typeof(VectorToImageConvertingTransformer), typeof(VectorToImageConvertingTransformer.Options), typeof(SignatureDataTransform),
    VectorToImageConvertingTransformer.UserName, "VectorToImageTransform", "VectorToImage")]
 
[assembly: LoadableClass(VectorToImageConvertingTransformer.Summary, typeof(IDataTransform), typeof(VectorToImageConvertingTransformer), null, typeof(SignatureLoadDataTransform),
    VectorToImageConvertingTransformer.UserName, VectorToImageConvertingTransformer.LoaderSignature)]
 
[assembly: LoadableClass(typeof(VectorToImageConvertingTransformer), null, typeof(SignatureLoadModel),
    VectorToImageConvertingTransformer.UserName, VectorToImageConvertingTransformer.LoaderSignature)]
 
[assembly: LoadableClass(typeof(IRowMapper), typeof(VectorToImageConvertingTransformer), null, typeof(SignatureLoadRowMapper),
    VectorToImageConvertingTransformer.UserName, VectorToImageConvertingTransformer.LoaderSignature)]
 
namespace Microsoft.ML.Transforms.Image
{
    /// <summary>
    /// <see cref="ITransformer"/> resulting from fitting a <see cref="VectorToImageConvertingEstimator"/>.
    /// </summary>
    public sealed class VectorToImageConvertingTransformer : OneToOneTransformerBase
    {
        internal class Column : OneToOneColumn
        {
            [Argument(ArgumentType.AtMostOnce, HelpText = "Whether to use alpha channel", ShortName = "alpha")]
            public bool? ContainsAlpha;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Whether to use red channel", ShortName = "red")]
            public bool? ContainsRed;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Whether to use green channel", ShortName = "green")]
            public bool? ContainsGreen;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Whether to use blue channel", ShortName = "blue")]
            public bool? ContainsBlue;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Order of channels")]
            public ImagePixelExtractingEstimator.ColorsOrder? Order;
 
            // REVIEW: Consider turning this into an enum that allows for pixel, line, or planar interleaving.
            [Argument(ArgumentType.AtMostOnce, HelpText = "Whether to separate each channel or interleave in specified order")]
            public bool? Interleave;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Width of the image", ShortName = "width")]
            public int? ImageWidth;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Height of the image", ShortName = "height")]
            public int? ImageHeight;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Offset (pre-scale)")]
            public Single? Offset;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Scale factor")]
            public Single? Scale;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Default value for alpha channel. Will be used if ContainsAlpha set to false")]
            public int? DefaultAlpha;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Default value for red channel. Will be used if ContainsRed set to false")]
            public int? DefaultRed;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Default value for green channel. Will be used if ContainsGreen set to false")]
            public int? DefaultGreen;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Default value for blue channel. Will be used if ContainsGreen set to false")]
            public int? DefaultBlue;
 
            internal static Column Parse(string str)
            {
                Contracts.AssertNonEmpty(str);
 
                var res = new Column();
                if (res.TryParse(str))
                    return res;
                return null;
            }
 
            internal bool TryUnparse(StringBuilder sb)
            {
                Contracts.AssertValue(sb);
                if (ContainsAlpha != null || ContainsRed != null || ContainsGreen != null || ContainsBlue != null || ImageWidth != null ||
                    ImageHeight != null || Offset != null || Scale != null || Interleave != null || Order != null || DefaultAlpha != null ||
                    DefaultBlue != null || DefaultGreen != null || DefaultRed != null)
                {
                    return false;
                }
                return TryUnparseCore(sb);
            }
        }
 
        internal class Options : TransformInputBase
        {
            [Argument(ArgumentType.Multiple | ArgumentType.Required, HelpText = "New column definition(s) (optional form: name:src)", Name = "Column", ShortName = "col", SortOrder = 1)]
            public Column[] Columns;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Whether to use alpha channel", ShortName = "alpha")]
            public bool ContainsAlpha = (ImagePixelExtractingEstimator.Defaults.Colors & ImagePixelExtractingEstimator.ColorBits.Alpha) > 0;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Whether to use red channel", ShortName = "red")]
            public bool ContainsRed = (ImagePixelExtractingEstimator.Defaults.Colors & ImagePixelExtractingEstimator.ColorBits.Red) > 0;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Whether to use green channel", ShortName = "green")]
            public bool ContainsGreen = (ImagePixelExtractingEstimator.Defaults.Colors & ImagePixelExtractingEstimator.ColorBits.Green) > 0;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Whether to use blue channel", ShortName = "blue")]
            public bool ContainsBlue = (ImagePixelExtractingEstimator.Defaults.Colors & ImagePixelExtractingEstimator.ColorBits.Blue) > 0;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Order of colors.")]
            public ImagePixelExtractingEstimator.ColorsOrder Order = ImagePixelExtractingEstimator.Defaults.Order;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Whether to separate each channel or interleave in specified order")]
            public bool Interleave = ImagePixelExtractingEstimator.Defaults.Interleave;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Width of the image", ShortName = "width")]
            public int ImageWidth;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Height of the image", ShortName = "height")]
            public int ImageHeight;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Offset (pre-scale)")]
            public Single Offset = VectorToImageConvertingEstimator.Defaults.Offset;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Scale factor")]
            public Single Scale = VectorToImageConvertingEstimator.Defaults.Scale;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Default value for alpha channel. Will be used if ContainsAlpha set to false")]
            public int DefaultAlpha = VectorToImageConvertingEstimator.Defaults.DefaultAlpha;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Default value for red channel. Will be used if ContainsRed set to false")]
            public int DefaultRed = VectorToImageConvertingEstimator.Defaults.DefaultRed;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Default value for green channel. Will be used if ContainsGreen set to false")]
            public int DefaultGreen = VectorToImageConvertingEstimator.Defaults.DefaultGreen;
 
            [Argument(ArgumentType.AtMostOnce, HelpText = "Default value for blue channel. Will be used if ContainsBlue set to false")]
            public int DefaultBlue = VectorToImageConvertingEstimator.Defaults.DefaultBlue;
        }
 
        internal const string Summary = "Converts vector array into image type.";
        internal const string UserName = "Vector To Image Transform";
        internal const string LoaderSignature = "VectorToImageConverter";
        internal const uint BeforeOrderVersion = 0x00010002;
        private static VersionInfo GetVersionInfo()
        {
            return new VersionInfo(
                modelSignature: "VECTOIMG",
                //verWrittenCur: 0x00010001, // Initial
                //verWrittenCur: 0x00010002, // Swith from OpenCV to Bitmap
                verWrittenCur: 0x00010003, // order for pixel colors, default colors, no size(float)
                verReadableCur: 0x00010002,
                verWeCanReadBack: 0x00010003,
                loaderSignature: LoaderSignature,
                loaderAssemblyName: typeof(VectorToImageConvertingTransformer).Assembly.FullName);
        }
 
        private const string RegistrationName = "VectorToImageConverter";
 
        private readonly VectorToImageConvertingEstimator.ColumnOptions[] _columns;
 
        /// <summary>
        /// The columns passed to this <see cref="ITransformer"/>.
        /// </summary>
        internal IReadOnlyCollection<VectorToImageConvertingEstimator.ColumnOptions> Columns => _columns.AsReadOnly();
 
        internal VectorToImageConvertingTransformer(IHostEnvironment env, params VectorToImageConvertingEstimator.ColumnOptions[] columns)
            : base(Contracts.CheckRef(env, nameof(env)).Register(RegistrationName), GetColumnPairs(columns))
        {
            Host.AssertNonEmpty(columns);
 
            _columns = columns.ToArray();
        }
 
        /// <param name="env">The host environment.</param>
        /// <param name="outputColumnName">Name of the column resulting from the transformation of <paramref name="inputColumnName"/>.</param>
        /// <param name="imageHeight">The height of the output images.</param>
        /// <param name="imageWidth">The width of the output images.</param>
        /// <param name="inputColumnName">Name of column to transform. If set to <see langword="null"/>, the value of the <paramref name="outputColumnName"/> will be used as source.</param>
        /// <param name="colorsPresent">Specifies which <see cref="ImagePixelExtractingEstimator.ColorBits"/> are in present the input pixel vectors. The order of colors is specified in <paramref name="orderOfColors"/>.</param>
        /// <param name="orderOfColors">The order in which colors are presented in the input vector.</param>
        /// <param name="interleavedColors">Whether the pixels are interleaved, meaning whether they are in <paramref name="orderOfColors"/> order, or separated in the planar form, where the colors are specified one by one
        /// for all the pixels of the image. </param>
        /// <param name="scaleImage">Scale each pixel's color value by this amount.</param>
        /// <param name="offsetImage">Offset each pixel's color value by this amount.</param>
        /// <param name="defaultAlpha">Default value for alpha color, would be overridden if <paramref name="colorsPresent"/> contains <see cref="ImagePixelExtractingEstimator.ColorBits.Alpha"/>.</param>
        /// <param name="defaultRed">Default value for red color, would be overridden if <paramref name="colorsPresent"/> contains <see cref="ImagePixelExtractingEstimator.ColorBits.Red"/>.</param>
        /// <param name="defaultGreen">Default value for green color, would be overridden if <paramref name="colorsPresent"/> contains <see cref="ImagePixelExtractingEstimator.ColorBits.Green"/>.</param>
        /// <param name="defaultBlue">Default value for blue color, would be overridden if <paramref name="colorsPresent"/> contains <see cref="ImagePixelExtractingEstimator.ColorBits.Blue"/>.</param>
        internal VectorToImageConvertingTransformer(IHostEnvironment env, string outputColumnName,
            int imageHeight, int imageWidth,
            string inputColumnName = null,
            ImagePixelExtractingEstimator.ColorBits colorsPresent = ImagePixelExtractingEstimator.Defaults.Colors,
            ImagePixelExtractingEstimator.ColorsOrder orderOfColors = ImagePixelExtractingEstimator.Defaults.Order,
            bool interleavedColors = ImagePixelExtractingEstimator.Defaults.Interleave,
            float scaleImage = VectorToImageConvertingEstimator.Defaults.Scale,
            float offsetImage = VectorToImageConvertingEstimator.Defaults.Offset,
            int defaultAlpha = VectorToImageConvertingEstimator.Defaults.DefaultAlpha,
            int defaultRed = VectorToImageConvertingEstimator.Defaults.DefaultRed,
            int defaultGreen = VectorToImageConvertingEstimator.Defaults.DefaultGreen,
            int defaultBlue = VectorToImageConvertingEstimator.Defaults.DefaultBlue)
            : this(env, new VectorToImageConvertingEstimator.ColumnOptions(outputColumnName, imageHeight, imageWidth, inputColumnName, colorsPresent, orderOfColors, interleavedColors, scaleImage, offsetImage, defaultAlpha, defaultRed, defaultGreen, defaultBlue))
        {
        }
 
        // Constructor corresponding to SignatureDataTransform.
        internal static IDataTransform Create(IHostEnvironment env, Options args, IDataView input)
        {
            Contracts.CheckValue(env, nameof(env));
            env.CheckValue(args, nameof(args));
            env.CheckValue(input, nameof(input));
 
            env.CheckValue(args.Columns, nameof(args.Columns));
 
            var columns = new VectorToImageConvertingEstimator.ColumnOptions[args.Columns.Length];
            for (int i = 0; i < columns.Length; i++)
            {
                var item = args.Columns[i];
                columns[i] = new VectorToImageConvertingEstimator.ColumnOptions(item, args);
            }
 
            var transformer = new VectorToImageConvertingTransformer(env, columns);
            return new RowToRowMapperTransform(env, input, transformer.MakeRowMapper(input.Schema), transformer.MakeRowMapper);
        }
 
        private VectorToImageConvertingTransformer(IHost host, ModelLoadContext ctx)
            : base(host, ctx)
        {
            Host.AssertValue(ctx);
 
            // *** Binary format ***
            // <base>
            // foreach added column
            //   ColumnOptions
 
            _columns = new VectorToImageConvertingEstimator.ColumnOptions[ColumnPairs.Length];
            for (int i = 0; i < _columns.Length; i++)
                _columns[i] = new VectorToImageConvertingEstimator.ColumnOptions(ColumnPairs[i].outputColumnName, ColumnPairs[i].inputColumnName, ctx);
        }
 
        private static VectorToImageConvertingTransformer Create(IHostEnvironment env, ModelLoadContext ctx)
        {
            Contracts.CheckValue(env, nameof(env));
            var h = env.Register(RegistrationName);
            h.CheckValue(ctx, nameof(ctx));
            ctx.CheckAtModel(GetVersionInfo());
            if (ctx.Header.ModelVerWritten <= VectorToImageConvertingTransformer.BeforeOrderVersion)
                ctx.Reader.ReadFloat();
            return h.Apply("Loading Model",
            ch =>
            {
                return new VectorToImageConvertingTransformer(h, ctx);
            });
        }
 
        // Factory method for SignatureLoadDataTransform.
        private static IDataTransform Create(IHostEnvironment env, ModelLoadContext ctx, IDataView input)
            => Create(env, ctx).MakeDataTransform(input);
 
        // Factory method for SignatureLoadRowMapper.
        private static IRowMapper Create(IHostEnvironment env, ModelLoadContext ctx, DataViewSchema inputSchema)
            => Create(env, ctx).MakeRowMapper(inputSchema);
 
        private protected override void SaveModel(ModelSaveContext ctx)
        {
            Host.CheckValue(ctx, nameof(ctx));
            ctx.CheckAtModel();
            ctx.SetVersionInfo(GetVersionInfo());
 
            // *** Binary format ***
            // <base>
            // foreach added column
            //   ColInfoEx
            base.SaveColumns(ctx);
 
            for (int i = 0; i < _columns.Length; i++)
                _columns[i].Save(ctx);
        }
 
        private static (string outputColumnName, string inputColumnName)[] GetColumnPairs(VectorToImageConvertingEstimator.ColumnOptions[] columns)
        {
            Contracts.CheckValue(columns, nameof(columns));
            return columns.Select(x => (x.Name, x.InputColumnName)).ToArray();
        }
 
        private protected override IRowMapper MakeRowMapper(DataViewSchema schema) => new Mapper(this, schema);
 
        private protected override void CheckInputColumn(DataViewSchema inputSchema, int col, int srcCol)
        {
            var inputColName = _columns[col].InputColumnName;
            var vectorType = inputSchema[srcCol].Type as VectorDataViewType;
            if (vectorType == null)
                throw Host.ExceptSchemaMismatch(nameof(inputSchema), "input", inputColName, "image", inputSchema[srcCol].Type.ToString());
 
            if (vectorType.GetValueCount() != _columns[col].ImageHeight * _columns[col].ImageWidth * _columns[col].Planes)
                throw Host.ExceptSchemaMismatch(nameof(inputSchema), "input", inputColName, new VectorDataViewType(vectorType.ItemType, _columns[col].ImageHeight, _columns[col].ImageWidth, _columns[col].Planes).ToString(), vectorType.ToString());
        }
 
        private sealed class Mapper : OneToOneMapperBase
        {
            private readonly VectorToImageConvertingTransformer _parent;
            private readonly ImageDataViewType[] _types;
 
            public Mapper(VectorToImageConvertingTransformer parent, DataViewSchema inputSchema)
                : base(parent.Host.Register(nameof(Mapper)), parent, inputSchema)
            {
                _parent = parent;
                _types = ConstructTypes(_parent._columns);
            }
 
            protected override DataViewSchema.DetachedColumn[] GetOutputColumnsCore()
                => _parent._columns.Select((x, idx) => new DataViewSchema.DetachedColumn(x.Name, _types[idx], null)).ToArray();
 
            protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, bool> activeOutput, out Action disposer)
            {
                Host.AssertValue(input);
                Host.Assert(0 <= iinfo && iinfo < _parent._columns.Length);
 
                var type = _types[iinfo];
                var ex = _parent._columns[iinfo];
                bool needScale = ex.OffsetImage != 0 || ex.ScaleImage != 1;
                disposer = null;
                var sourceType = InputSchema[ColMapNewToOld[iinfo]].Type;
                var sourceItemType = sourceType.GetItemType();
                if (sourceItemType == NumberDataViewType.Single || sourceItemType == NumberDataViewType.Double)
                    return GetterFromType<float>(NumberDataViewType.Single, input, iinfo, ex, needScale);
                else if (sourceItemType == NumberDataViewType.Byte)
                    return GetterFromType<byte>(NumberDataViewType.Byte, input, iinfo, ex, false);
                else
                    throw Contracts.Except("We only support float, double or byte arrays");
            }
 
            private ValueGetter<MLImage> GetterFromType<TValue>(PrimitiveDataViewType srcType, DataViewRow input, int iinfo,
                VectorToImageConvertingEstimator.ColumnOptions ex, bool needScale) where TValue : IConvertible
            {
                Contracts.Assert(typeof(TValue) == srcType.RawType);
                var getSrc = RowCursorUtils.GetVecGetterAs<TValue>(srcType, input, ColMapNewToOld[iinfo]);
                var src = default(VBuffer<TValue>);
                int width = ex.ImageWidth;
                int height = ex.ImageHeight;
                float offset = ex.OffsetImage;
                float scale = ex.ScaleImage;
 
                return
                    (ref MLImage dst) =>
                    {
                        getSrc(ref src);
                        if (src.GetValues().Length == 0)
                        {
                            dst = null;
                            return;
                        }
                        VBuffer<TValue> dense = default;
                        src.CopyToDense(ref dense);
                        var values = dense.GetValues();
                        int cpix = height * width;
                        int position = 0;
                        ImagePixelExtractingEstimator.GetOrder(ex.Order, ex.Colors, out int a, out int r, out int b, out int g);
 
                        byte[] imageData = new byte[width * height * 4]; // 4 for bgra data blue, green, red, and alpha.
                        int ix = 0;
                        for (int y = 0; y < height; ++y)
                        {
                            for (int x = 0; x < width; x++)
                            {
                                float red = ex.DefaultRed;
                                float green = ex.DefaultGreen;
                                float blue = ex.DefaultBlue;
                                float alpha = ex.DefaultAlpha;
                                if (ex.InterleavedColors)
                                {
                                    if (ex.Alpha)
                                        alpha = Convert.ToSingle(values[position + a]);
                                    if (ex.Red)
                                        red = Convert.ToSingle(values[position + r]);
                                    if (ex.Green)
                                        green = Convert.ToSingle(values[position + g]);
                                    if (ex.Blue)
                                        blue = Convert.ToSingle(values[position + b]);
                                    position += ex.Planes;
                                }
                                else
                                {
                                    position = y * width + x;
                                    if (ex.Alpha) alpha = Convert.ToSingle(values[position + cpix * a]);
                                    if (ex.Red) red = Convert.ToSingle(values[position + cpix * r]);
                                    if (ex.Green) green = Convert.ToSingle(values[position + cpix * g]);
                                    if (ex.Blue) blue = Convert.ToSingle(values[position + cpix * b]);
                                }
                                if (!needScale)
                                {
                                    imageData[ix++] = (byte)blue;
                                    imageData[ix++] = (byte)green;
                                    imageData[ix++] = (byte)red;
                                    imageData[ix++] = (byte)alpha;
                                }
                                else
                                {
                                    imageData[ix++] = (byte)Math.Round(blue * scale - offset);
                                    imageData[ix++] = (byte)Math.Round(green * scale - offset);
                                    imageData[ix++] = (byte)Math.Round(red * scale - offset);
                                    imageData[ix++] = (byte)(ex.Alpha ? Math.Round(alpha * scale - offset) : 0);
                                }
                            }
                        }
 
                        dst = MLImage.CreateFromPixels(width, height, MLPixelFormat.Bgra32, imageData);
                        dst.Tag = nameof(VectorToImageConvertingTransformer);
                    };
            }
 
            private static ImageDataViewType[] ConstructTypes(VectorToImageConvertingEstimator.ColumnOptions[] columns)
            {
                return columns.Select(c => new ImageDataViewType(c.ImageHeight, c.ImageWidth)).ToArray();
            }
        }
    }
 
    /// <summary>
    /// <see cref="IEstimator{TTransformer}"/> for the <see cref="VectorToImageConvertingTransformer"/>.
    /// </summary>
    /// <remarks>
    /// <format type="text/markdown"><![CDATA[
    ///
    /// ###  Estimator Characteristics
    /// |  |  |
    /// | -- | -- |
    /// | Does this estimator need to look at the data to train its parameters? | No |
    /// | Input column data type | Known-sized vector of <xref:System.Single>, <xref:System.Double> or <xref:System.Byte>. |
    /// | Output column data type | <xref:Microsoft.ML.Data.MLImage> |
    /// | Required NuGet in addition to Microsoft.ML | Microsoft.ML.ImageAnalytics |
    /// | Exportable to ONNX | No |
    ///
    /// The resulting <xref:Microsoft.ML.Transforms.Image.VectorToImageConvertingTransformer> creates a new column, named as specified in the output column name parameters, and
    /// creates image from the data in the input column to this new column.
    ///
    /// Check the See Also section for links to usage examples.
    /// ]]>
    /// </format>
    /// </remarks>
    /// <seealso cref="ImageEstimatorsCatalog.ConvertToImage(TransformsCatalog, int, int, string, string, ImagePixelExtractingEstimator.ColorBits, ImagePixelExtractingEstimator.ColorsOrder, bool, float, float, int, int, int, int)" />
    public sealed class VectorToImageConvertingEstimator : TrivialEstimator<VectorToImageConvertingTransformer>
    {
        internal static class Defaults
        {
            public const float Scale = 1f;
            public const float Offset = 0f;
            public const int DefaultAlpha = 255;
            public const int DefaultRed = 0;
            public const int DefaultGreen = 0;
            public const int DefaultBlue = 0;
        }
        /// <summary>
        /// Describes how the transformer handles one vector to image conversion column pair.
        /// </summary>
        [BestFriend]
        internal sealed class ColumnOptions
        {
            /// <summary>Name of the column resulting from the transformation of <see cref="InputColumnName"/>.</summary>
            public readonly string Name;
 
            /// <summary> Name of column to transform.</summary>
            public readonly string InputColumnName;
 
            public readonly ImagePixelExtractingEstimator.ColorBits Colors;
            public readonly ImagePixelExtractingEstimator.ColorsOrder Order;
            public readonly bool InterleavedColors;
            public readonly byte Planes;
 
            public readonly int ImageWidth;
            public readonly int ImageHeight;
            public readonly float OffsetImage;
            public readonly float ScaleImage;
 
            public readonly int DefaultAlpha;
            public readonly int DefaultRed;
            public readonly int DefaultGreen;
            public readonly int DefaultBlue;
 
            public bool Alpha => (Colors & ImagePixelExtractingEstimator.ColorBits.Alpha) != 0;
            public bool Red => (Colors & ImagePixelExtractingEstimator.ColorBits.Red) != 0;
            public bool Green => (Colors & ImagePixelExtractingEstimator.ColorBits.Green) != 0;
            public bool Blue => (Colors & ImagePixelExtractingEstimator.ColorBits.Blue) != 0;
 
            internal ColumnOptions(VectorToImageConvertingTransformer.Column item, VectorToImageConvertingTransformer.Options args)
            {
                Contracts.CheckValue(item, nameof(item));
                Contracts.CheckValue(args, nameof(args));
 
                Name = item.Name;
                InputColumnName = item.Source ?? item.Name;
 
                if (item.ContainsAlpha ?? args.ContainsAlpha)
                { Colors |= ImagePixelExtractingEstimator.ColorBits.Alpha; Planes++; }
                if (item.ContainsRed ?? args.ContainsRed)
                { Colors |= ImagePixelExtractingEstimator.ColorBits.Red; Planes++; }
                if (item.ContainsGreen ?? args.ContainsGreen)
                { Colors |= ImagePixelExtractingEstimator.ColorBits.Green; Planes++; }
                if (item.ContainsBlue ?? args.ContainsBlue)
                { Colors |= ImagePixelExtractingEstimator.ColorBits.Blue; Planes++; }
                Contracts.CheckUserArg(Planes > 0, nameof(item.ContainsRed), "Need to use at least one color plane");
 
                Order = item.Order ?? args.Order;
                InterleavedColors = item.Interleave ?? args.Interleave;
 
                ImageWidth = item.ImageWidth ?? args.ImageWidth;
                ImageHeight = item.ImageHeight ?? args.ImageHeight;
                OffsetImage = item.Offset ?? args.Offset;
                ScaleImage = item.Scale ?? args.Scale;
                Contracts.CheckUserArg(FloatUtils.IsFinite(OffsetImage), nameof(item.Offset));
                Contracts.CheckUserArg(FloatUtils.IsFiniteNonZero(ScaleImage), nameof(item.Scale));
            }
 
            internal ColumnOptions(string outputColumnName, string inputColumnName, ModelLoadContext ctx)
            {
                Contracts.AssertNonEmpty(outputColumnName);
                Contracts.AssertNonEmpty(inputColumnName);
                Contracts.AssertValue(ctx);
 
                Name = outputColumnName;
                InputColumnName = inputColumnName;
 
                // *** Binary format ***
                // byte: colors
                // byte: order
                // int: widht
                // int: height
                // Float: offset
                // Float: scale
                // byte: interleave
                // int: defaultAlpha
                // int: defaultRed
                // int: defaultGreen
                // int: defaultBlue
                Colors = (ImagePixelExtractingEstimator.ColorBits)ctx.Reader.ReadByte();
                Contracts.CheckDecode(Colors != 0);
                Contracts.CheckDecode((Colors & ImagePixelExtractingEstimator.ColorBits.All) == Colors);
 
                // Count the planes.
                int planes = (int)Colors;
                planes = (planes & 0x05) + ((planes >> 1) & 0x05);
                planes = (planes & 0x03) + ((planes >> 2) & 0x03);
                Planes = (byte)planes;
                Contracts.Assert(0 < Planes && Planes <= 4);
 
                if (ctx.Header.ModelVerWritten <= VectorToImageConvertingTransformer.BeforeOrderVersion)
                    Order = ImagePixelExtractingEstimator.ColorsOrder.ARGB;
                else
                {
                    Order = (ImagePixelExtractingEstimator.ColorsOrder)ctx.Reader.ReadByte();
                    Contracts.CheckDecode(Order != 0);
                }
 
                ImageWidth = ctx.Reader.ReadInt32();
                Contracts.CheckDecode(ImageWidth > 0);
                ImageHeight = ctx.Reader.ReadInt32();
                Contracts.CheckDecode(ImageHeight > 0);
                OffsetImage = ctx.Reader.ReadFloat();
                Contracts.CheckDecode(FloatUtils.IsFinite(OffsetImage));
                ScaleImage = ctx.Reader.ReadFloat();
                Contracts.CheckDecode(FloatUtils.IsFiniteNonZero(ScaleImage));
                InterleavedColors = ctx.Reader.ReadBoolByte();
 
                if (ctx.Header.ModelVerWritten <= VectorToImageConvertingTransformer.BeforeOrderVersion)
                {
                    DefaultAlpha = 0;
                    DefaultRed = 0;
                    DefaultGreen = 0;
                    DefaultBlue = 0;
                }
                else
                {
                    DefaultAlpha = ctx.Reader.ReadInt32();
                    DefaultRed = ctx.Reader.ReadInt32();
                    DefaultGreen = ctx.Reader.ReadInt32();
                    DefaultBlue = ctx.Reader.ReadInt32();
                }
            }
 
            /// <param name="name">Name of the column resulting from the transformation of <paramref name="inputColumnName"/>.</param>
            /// <param name="imageHeight">The height of the output images.</param>
            /// <param name="imageWidth">The width of the output images.</param>
            /// <param name="inputColumnName">Name of column to transform. If set to <see langword="null"/>, the value of the <paramref name="name"/> will be used as source.</param>
            /// <param name="colorsPresent">Specifies which <see cref="ImagePixelExtractingEstimator.ColorBits"/> are present in the input pixel vectors. The order of colors is specified in <paramref name="orderOfColors"/>.</param>
            /// <param name="orderOfColors">The order in which colors are presented in the input vector.</param>
            /// <param name="interleavedColors">Whether the pixels are interleaved, meaning whether they are in <paramref name="orderOfColors"/> order, or separated in the planar form, where the colors are specified one by one
            /// alpha, red, green, blue for all the pixels of the image. </param>
            /// <param name="scaleImage">The values are scaled by this value before being converted to pixels. Applied to vector value before <paramref name="offsetImage"/></param>
            /// <param name="offsetImage">The offset is subtracted before converting the values to pixels. Applied to vector value after <paramref name="scaleImage"/>.</param>
            /// <param name="defaultAlpha">Default value for alpha color, would be overridden if <paramref name="colorsPresent"/> contains <see cref="ImagePixelExtractingEstimator.ColorBits.Alpha"/>.</param>
            /// <param name="defaultRed">Default value for red color, would be overridden if <paramref name="colorsPresent"/> contains <see cref="ImagePixelExtractingEstimator.ColorBits.Red"/>.</param>
            /// <param name="defaultGreen">Default value for green color, would be overridden if <paramref name="colorsPresent"/> contains <see cref="ImagePixelExtractingEstimator.ColorBits.Green"/>.</param>
            /// <param name="defaultBlue">Default value for blue color, would be overridden if <paramref name="colorsPresent"/> contains <see cref="ImagePixelExtractingEstimator.ColorBits.Blue"/>.</param>
            public ColumnOptions(string name,
                int imageHeight, int imageWidth,
                string inputColumnName = null,
                ImagePixelExtractingEstimator.ColorBits colorsPresent = ImagePixelExtractingEstimator.Defaults.Colors,
                ImagePixelExtractingEstimator.ColorsOrder orderOfColors = ImagePixelExtractingEstimator.Defaults.Order,
                bool interleavedColors = ImagePixelExtractingEstimator.Defaults.Interleave,
                float scaleImage = VectorToImageConvertingEstimator.Defaults.Scale,
                float offsetImage = VectorToImageConvertingEstimator.Defaults.Offset,
                int defaultAlpha = VectorToImageConvertingEstimator.Defaults.DefaultAlpha,
                int defaultRed = VectorToImageConvertingEstimator.Defaults.DefaultRed,
                int defaultGreen = VectorToImageConvertingEstimator.Defaults.DefaultGreen,
                int defaultBlue = VectorToImageConvertingEstimator.Defaults.DefaultBlue)
            {
                Contracts.CheckNonWhiteSpace(name, nameof(name));
 
                Name = name;
                InputColumnName = inputColumnName ?? name;
                Colors = colorsPresent;
                if ((byte)(Colors & ImagePixelExtractingEstimator.ColorBits.Alpha) > 0)
                    Planes++;
                if ((byte)(Colors & ImagePixelExtractingEstimator.ColorBits.Red) > 0)
                    Planes++;
                if ((byte)(Colors & ImagePixelExtractingEstimator.ColorBits.Green) > 0)
                    Planes++;
                if ((byte)(Colors & ImagePixelExtractingEstimator.ColorBits.Blue) > 0)
                    Planes++;
                Contracts.CheckParam(Planes > 0, nameof(colorsPresent), "Need to use at least one color plane");
 
                Order = orderOfColors;
                InterleavedColors = interleavedColors;
 
                Contracts.CheckParam(imageWidth > 0, nameof(imageWidth), "Image width must be greater than zero");
                Contracts.CheckParam(imageHeight > 0, nameof(imageHeight), "Image height must be greater than zero");
                Contracts.CheckParam(FloatUtils.IsFinite(offsetImage), nameof(offsetImage));
                Contracts.CheckParam(FloatUtils.IsFiniteNonZero(scaleImage), nameof(scaleImage));
                ImageWidth = imageWidth;
                ImageHeight = imageHeight;
                OffsetImage = offsetImage;
                ScaleImage = scaleImage;
                DefaultAlpha = defaultAlpha;
                DefaultRed = defaultRed;
                DefaultGreen = defaultGreen;
                DefaultBlue = defaultBlue;
            }
 
            internal void Save(ModelSaveContext ctx)
            {
                Contracts.AssertValue(ctx);
 
#if DEBUG
                // This code is used in deserialization - assert that it matches what we computed above.
                int planes = (int)Colors;
                planes = (planes & 0x05) + ((planes >> 1) & 0x05);
                planes = (planes & 0x03) + ((planes >> 2) & 0x03);
                Contracts.Assert(planes == Planes);
#endif
 
                // *** Binary format ***
                // byte: colors
                // byte: order
                // byte: convert
                // Float: offset
                // Float: scale
                // byte: interleave
                // int: defaultAlpha
                // int: defaultRed
                // int: defaultGreen
                // int: defaultBlue
                Contracts.Assert(Colors != 0);
                Contracts.Assert((Colors & ImagePixelExtractingEstimator.ColorBits.All) == Colors);
                ctx.Writer.Write((byte)Colors);
                ctx.Writer.Write((byte)Order);
                ctx.Writer.Write(ImageWidth);
                ctx.Writer.Write(ImageHeight);
                Contracts.Assert(FloatUtils.IsFinite(OffsetImage));
                ctx.Writer.Write(OffsetImage);
                Contracts.Assert(FloatUtils.IsFiniteNonZero(ScaleImage));
                ctx.Writer.Write(ScaleImage);
                ctx.Writer.WriteBoolByte(InterleavedColors);
                ctx.Writer.Write(DefaultAlpha);
                ctx.Writer.Write(DefaultRed);
                ctx.Writer.Write(DefaultGreen);
                ctx.Writer.Write(DefaultBlue);
            }
        }
 
        ///<summary>
        /// Convert pixels values into an image.
        ///</summary>
        /// <param name="env">The host environment.</param>
        /// <param name="imageHeight">The height of the output images.</param>
        /// <param name="imageWidth">The width of the output images.</param>
        /// <param name="outputColumnName">Name of the column resulting from the transformation of <paramref name="inputColumnName"/>. Null means <paramref name="inputColumnName"/> is replaced.</param>
        /// <param name="inputColumnName">Name of the input column.</param>
        /// <param name="colorsPresent">Specifies which <see cref="ImagePixelExtractingEstimator.ColorBits"/> are in present the input pixel vectors. The order of colors is specified in <paramref name="orderOfColors"/>.</param>
        /// <param name="orderOfColors">The order in which colors are presented in the input vector.</param>
        /// <param name="interleavedColors">Whether the pixels are interleaved, meaning whether they are in <paramref name="orderOfColors"/> order, or separated in the planar form, where the colors are specified one by one
        /// alpha, red, green, blue for all the pixels of the image. </param>
        /// <param name="scaleImage">The values are scaled by this value before being converted to pixels. Applied to vector value before <paramref name="offsetImage"/>.</param>
        /// <param name="offsetImage">The offset is subtracted before converting the values to pixels. Applied to vector value after <paramref name="scaleImage"/>.</param>
        /// <param name="defaultAlpha">Default value for alpha color, would be overridden if <paramref name="colorsPresent"/> contains <see cref="ImagePixelExtractingEstimator.ColorBits.Alpha"/>.</param>
        /// <param name="defaultRed">Default value for red color, would be overridden if <paramref name="colorsPresent"/> contains <see cref="ImagePixelExtractingEstimator.ColorBits.Red"/>.</param>
        /// <param name="defaultGreen">Default value for green color, would be overridden if <paramref name="colorsPresent"/> contains <see cref="ImagePixelExtractingEstimator.ColorBits.Green"/>.</param>
        /// <param name="defaultBlue">Default value for blue color, would be overridden if <paramref name="colorsPresent"/> contains <see cref="ImagePixelExtractingEstimator.ColorBits.Blue"/>.</param>
        [BestFriend]
        internal VectorToImageConvertingEstimator(IHostEnvironment env,
            int imageHeight,
            int imageWidth,
            string outputColumnName,
            string inputColumnName = null,
            ImagePixelExtractingEstimator.ColorBits colorsPresent = ImagePixelExtractingEstimator.Defaults.Colors,
            ImagePixelExtractingEstimator.ColorsOrder orderOfColors = ImagePixelExtractingEstimator.Defaults.Order,
            bool interleavedColors = ImagePixelExtractingEstimator.Defaults.Interleave,
            float scaleImage = VectorToImageConvertingEstimator.Defaults.Scale,
            float offsetImage = VectorToImageConvertingEstimator.Defaults.Offset,
            int defaultAlpha = VectorToImageConvertingEstimator.Defaults.DefaultAlpha,
            int defaultRed = VectorToImageConvertingEstimator.Defaults.DefaultRed,
            int defaultGreen = VectorToImageConvertingEstimator.Defaults.DefaultGreen,
            int defaultBlue = VectorToImageConvertingEstimator.Defaults.DefaultBlue)
            : base(Contracts.CheckRef(env, nameof(env)).Register(nameof(VectorToImageConvertingEstimator)),
                  new VectorToImageConvertingTransformer(env, outputColumnName, imageHeight, imageWidth, inputColumnName, colorsPresent, orderOfColors, interleavedColors, scaleImage, offsetImage, defaultAlpha, defaultRed, defaultGreen, defaultBlue))
        {
        }
 
        ///<summary>
        /// Extract pixels values from image and produce array of values.
        ///</summary>
        /// <param name="env">The host environment.</param>
        /// <param name="columnOptions">The <see cref="ColumnOptions"/> describing how the transform handles each vector to image conversion column pair.</param>
        internal VectorToImageConvertingEstimator(IHostEnvironment env, params ColumnOptions[] columnOptions)
            : base(Contracts.CheckRef(env, nameof(env)).Register(nameof(VectorToImageConvertingEstimator)), new VectorToImageConvertingTransformer(env, columnOptions))
        {
        }
 
        /// <summary>
        /// Returns the <see cref="SchemaShape"/> of the schema which will be produced by the transformer.
        /// Used for schema propagation and verification in a pipeline.
        /// </summary>
        public override SchemaShape GetOutputSchema(SchemaShape inputSchema)
        {
            Host.CheckValue(inputSchema, nameof(inputSchema));
            var result = inputSchema.ToDictionary(x => x.Name);
            foreach (var colInfo in Transformer.Columns)
            {
                if (!inputSchema.TryFindColumn(colInfo.InputColumnName, out var col))
                    throw Host.ExceptSchemaMismatch(nameof(inputSchema), "input", colInfo.InputColumnName);
                if (col.Kind != SchemaShape.Column.VectorKind.Vector || (col.ItemType != NumberDataViewType.Single && col.ItemType != NumberDataViewType.Double && col.ItemType != NumberDataViewType.Byte))
                    throw Host.ExceptSchemaMismatch(nameof(inputSchema), "input", colInfo.InputColumnName, "known-size vector of type Single, Double or Byte", col.GetTypeString());
 
                var itemType = new ImageDataViewType(colInfo.ImageHeight, colInfo.ImageWidth);
                result[colInfo.Name] = new SchemaShape.Column(colInfo.Name, SchemaShape.Column.VectorKind.Scalar, itemType, false);
            }
 
            return new SchemaShape(result.Values);
        }
    }
}