/// <summary>
        /// Puts a sample into statistics.
        /// </summary>
        /// <param name="linXform">Linear transform.</param>
        public void Put(LinXForm linXform)
        {
            Helper.ThrowIfNull(linXform);
            for (int i = 0; linXform.Bias != null && i < linXform.Bias.Length; i++)
            {
                Bias[i].Put(linXform.Bias[i]);
            }

            for (int i = 0; i < linXform.Blocks.Count; i++)
            {
                UniStatistic[,] staticBlock = Blocks[i];
                float[,] block = linXform.Blocks[i];
                for (int j = 0; j < linXform.Blocks[i].GetLength(0); j++)
                {
                    for (int k = 0; k < linXform.Blocks[i].GetLength(1); k++)
                    {
                        staticBlock[j, k].Put(block[j, k]);
                    }
                }
            }
        }
        /// <summary>
        /// Loads one linear transform from the stream.
        /// </summary>
        /// <param name="sr">Stream reader that contains the transform matrix.</param>
        /// <returns>The loaded linear transform.</returns>
        private LinXForm LoadLinearTransform(StreamReader sr)
        {
            LinXForm xform = new LinXForm();

            Regex idPattern = new Regex(@"<LINXFORM>\s+(\d+)");
            Regex vecSizePattern = new Regex(@"<VECSIZE>\s+(\d+)");
            Regex biasPattern = new Regex(@"<BIAS>\s+(\d+)");
            string offsetTag = "<OFFSET>", blockInfoTag = "<BLOCKINFO>";

            // Process "<LINXFORM> "
            string line = sr.ReadLine();
            Match match = idPattern.Match(line);
            Debug.Assert(match.Success);
            xform.ID = int.Parse(match.Groups[1].Value);

            // Process "<VECSIZE> "
            line = sr.ReadLine();
            match = vecSizePattern.Match(line);
            Debug.Assert(match.Success);
            xform.VecSize = int.Parse(match.Groups[1].Value);

            // Process "<OFFSET>" in mean form or "<LOGDET>" in var form
            line = sr.ReadLine();
            if (line == offsetTag)
            {
                // Process "<BIAS>"
                line = sr.ReadLine();
                match = biasPattern.Match(line);
                Debug.Assert(match.Success);
                xform.Bias = new float[int.Parse(match.Groups[1].Value)];

                line = sr.ReadLine();
                string[] sArr = line.Trim().Split(new char[] { ' ' });
                Debug.Assert(xform.Bias.Length == sArr.Length);
                for (int i = 0; i < xform.Bias.Length; ++i)
                {
                    xform.Bias[i] = float.Parse(sArr[i]);
                }
            }

            while (!line.StartsWith(blockInfoTag))
            {
                line = sr.ReadLine();
            }

            // Process "<BLOCKINFO> "
            string[] strArr = line.Split(new char[] { ' ' });
            Debug.Assert(strArr[0] == blockInfoTag);
            int numBlock = int.Parse(strArr[1]);
            for (int i = 0; i < numBlock; ++i)
            {
                int blockSize = int.Parse(strArr[i + 2]);
                float[,] matrix = new float[blockSize, blockSize];
                xform.Blocks.Add(matrix);

                sr.ReadLine();  // Skip "<BLOCK>"
                sr.ReadLine();  // Skip "<XFORM>"

                // Loads transform matrix value.
                for (int j = 0; j < blockSize; ++j)
                {
                    line = sr.ReadLine();
                    string[] sArr = line.Trim().Split(new char[] { ' ' });
                    Debug.Assert(blockSize == sArr.Length);
                    for (int k = 0; k < blockSize; ++k)
                    {
                        matrix[j, k] = float.Parse(sArr[k]);
                    }
                }
            }

            return xform;
        }
            /// <summary>
            /// Puts a sample into this list.
            /// </summary>
            /// <param name="linXform">Linear transform.</param>
            /// <param name="streamOrder">Dynamic stream order.</param>
            public void Put(LinXForm linXform, DynamicOrder streamOrder)
            {
                Helper.ThrowIfNull(linXform);
                if (!_linXformStatistics.ContainsKey(streamOrder))
                {
                    _linXformStatistics.Add(streamOrder, new LinXformStatistic(linXform));
                }

                _linXformStatistics[streamOrder].Put(linXform);
            }
        /// <summary>
        /// Initializes a new instance of the LinXformStatistic class.
        /// </summary>
        /// <param name="linXform">Linear transform.</param>
        public LinXformStatistic(LinXForm linXform)
        {
            if (linXform.Bias != null)
            {
                _bias = new UniStatistic[linXform.Bias.Length];
                for (int i = 0; i < _bias.Length; i++)
                {
                    _bias[i] = new UniStatistic();
                }
            }

            _blocks = new List<UniStatistic[,]>();
            for (int i = 0; i < linXform.Blocks.Count; i++)
            {
                float[,] block = linXform.Blocks[i];
                UniStatistic[,] matrix = new UniStatistic[block.GetLength(0), block.GetLength(1)];
                for (int j = 0; j < block.GetLength(0); j++)
                {
                    for (int k = 0; k < block.GetLength(1); k++)
                    {
                        matrix[j, k] = new UniStatistic();
                    }
                }

                _blocks.Add(matrix);
            }
        }
        /// <summary>
        /// Write out one linear transform to be 4-byte aligned.
        /// </summary>
        /// <param name="writer">Binary writer.</param>
        /// <param name="linXform">Linear transform to write out.</param>
        /// <param name="streamOrder">Dynamic order of the stream of current linear transform.</param>
        /// <param name="hasBias">Indicate transform whether has bias info.</param>
        /// <param name="bandWidth">Band width of linear transform matrix.</param>
        /// <returns>Number of bytes written out.</returns>
        protected uint WriteFourBytesAlignedLinXform(DataWriter writer, LinXForm linXform, DynamicOrder streamOrder, bool hasBias, uint bandWidth = 0)
        {
            Helper.ThrowIfNull(writer);
            Helper.ThrowIfNull(linXform);
            uint size = Write(writer, linXform, streamOrder, hasBias, bandWidth);

            if (size == 0)
            {
                throw new InvalidDataException(Helper.NeutralFormat("Zero length of linXform is not allowed."));
            }

            // Pad zero bytes if needed to align with 4-bytes
            if (size % sizeof(uint) > 0)
            {
                size += writer.Write(new byte[sizeof(uint) - (size % sizeof(uint))]);
            }

            Debug.Assert(size % sizeof(uint) == 0, "Data should be 4-byte aligned.");
            return size;
        }
        /// <summary>
        /// Read linear transform.
        /// </summary>
        /// <param name="reader">Binary reader to read linear transform.</param>
        /// <param name="dimension">Dimension of the linear transform to read.</param>
        /// <param name="blockSizes">Size of each linear transform sub matrix to read.</param>
        /// <param name="streamOrder">The dynamic order of current linear transform to read.</param>
        /// <param name="hasBias">Indicate transform whether has bias info.</param>
        /// <param name="bandWidth">Band width of linear transform matrix.</param>
        /// <returns>Linear transform.</returns>
        protected virtual LinXForm ReadOneLinXform(BinaryReader reader, int dimension, List<int> blockSizes, DynamicOrder streamOrder, bool hasBias, uint bandWidth)
        {
            Helper.ThrowIfNull(reader);
            LinXForm linXform = new LinXForm();
            linXform.VecSize = dimension;

            if (hasBias)
            {
                Debug.Assert(Config.BiasBits == sizeof(float) * 8, "Only 32-bit float value is supported here");
                linXform.Bias = new float[dimension];
                for (int i = 0; i < linXform.VecSize; i++)
                {
                    linXform.Bias[i] = reader.ReadSingle();
                }
            }

            Debug.Assert(Config.MatrixBits == sizeof(float) * 8, "Only 32-bit float value is supported here");
            for (int i = 0; i < blockSizes.Count; i++)
            {
                float[,] block = new float[blockSizes[i], blockSizes[i]];
                for (int j = 0; j < blockSizes[i]; j++)
                {
                    for (int k = 0; k < blockSizes[i]; k++)
                    {
                        if (k < j - bandWidth || k > j + bandWidth)
                        {
                            block[j, k] = 0;
                        }
                        else
                        {
                            block[j, k] = reader.ReadSingle();
                        }
                    }
                }

                linXform.Blocks.Add(block);
            }

            return linXform;
        }
        /// <summary>
        /// Write out linear transform.
        /// </summary>
        /// <param name="writer">Binary writer.</param>
        /// <param name="linXform">Linear transform.</param>
        /// <param name="streamOrder">The dynamic order of stream, to which the linear transform belongs.</param>
        /// <param name="hasBias">Indicate transform whether has bias info.</param>
        /// <param name="bandWidth">Band width of linear transform matrix.</param>
        /// <returns>Size of bytes written.</returns>
        protected virtual uint Write(DataWriter writer, LinXForm linXform, DynamicOrder streamOrder, bool hasBias, uint bandWidth)
        {
            Helper.ThrowIfNull(writer);
            Helper.ThrowIfNull(linXform);

            uint size = 0;

            Debug.Assert(Config.BiasBits == sizeof(float) * 8, "Only 32-bit float value is supported here");
            Debug.Assert(Config.MatrixBits == sizeof(float) * 8, "Only 32-bit float value is supported here");

            for (int i = 0; hasBias && i < linXform.VecSize; i++)
            {
                size += writer.Write(linXform.Bias[i]);
            }

            for (int i = 0; i < linXform.Blocks.Count; i++)
            {
                for (int j = 0; j < linXform.Blocks[i].GetLength(0); j++)
                {
                    for (int k = 0; k < linXform.Blocks[i].GetLength(1); k++)
                    {
                        if (k >= j - bandWidth && k <= j + bandWidth)
                        {
                            size += writer.Write(linXform.Blocks[i][j, k]);
                        }
                    }
                }
            }

            return size;
        }
        /// <summary>
        /// Read linear transform.
        /// </summary>
        /// <param name="reader">Binary reader to read linear transforms.</param>
        /// <param name="dimension">Dimension of the linear transform to read.</param>
        /// <param name="blockSizes">Size of each linear transform sub matrix to read.</param>
        /// <param name="streamOrder">The dynamic order of current linear transform to read.</param>
        /// <param name="meanXform">Transform for Gaussian mean.</param>
        /// <param name="varXform">Transform for Gaussian variance.</param>
        public virtual void ReadLinXform(BinaryReader reader, int dimension, List<int> blockSizes, DynamicOrder streamOrder, out LinXForm meanXform, out LinXForm varXform)
        {
            Helper.ThrowIfNull(reader);

            long orgPos = reader.BaseStream.Position;
            meanXform = null;
            varXform = null;

            if (Config.HasMeanXform)
            {
                meanXform = ReadFourBytesAlignedLinXform(reader, dimension, blockSizes, streamOrder, Config.HasMeanBias, Config.MeanBandWidth);
            }

            if (Config.HasVarXform)
            {
                varXform = ReadFourBytesAlignedLinXform(reader, dimension, blockSizes, streamOrder, Config.HasVarBias);
            }

            if (Encoder != null)
            {
                // ramp up the encoder
                long size = reader.BaseStream.Position - orgPos;
                reader.BaseStream.Position = orgPos;
                Encoder.WarmUpData(reader.ReadBytes((int)size), 1);
            }
        }
        public virtual uint Write(DataWriter writer, LinXForm meanXform, LinXForm varXform, DynamicOrder streamOrder)
        {
            Helper.ThrowIfNull(writer);
            Helper.ThrowIfNull(meanXform);
            Helper.ThrowIfNull(varXform);

            DataWriter orgWriter = writer;
            MemoryStream linXformsBuf = null;
            if (EnableCompress)
            {
                linXformsBuf = new MemoryStream();
                writer = new DataWriter(linXformsBuf);
            }

            uint size = 0;
            if (Config.HasMeanXform)
            {
                MeanStatistic.Put(meanXform, streamOrder);
                size += WriteFourBytesAlignedLinXform(writer, meanXform, streamOrder, Config.HasMeanBias, Config.MeanBandWidth);
            }

            if (Config.HasVarXform)
            {
                VarStatistic.Put(varXform, streamOrder);
                size += WriteFourBytesAlignedLinXform(writer, varXform, streamOrder, Config.HasVarBias);
            }

            if (EnableCompress)
            {
                size = orgWriter.Write(Encoder.Encode(linXformsBuf.ToArray()));
                if (size % sizeof(uint) != 0)
                {
                    size += orgWriter.Write(new byte[sizeof(uint) - (size % sizeof(uint))]);
                }

                writer = orgWriter;
            }

            return size;
        }