/// <summary>
        /// Initializes a new instance of the <see cref="MicroLensCollection"/> from metadata.
        /// </summary>
        /// <param name="metadata">The metadata with microlens array parameters.</param>
        /// <exception cref="ArgumentNullException"><paramref name="metadata"/> is null.</exception>
        /// <exception cref="ArgumentException"><paramref name="metadata"/> contains invalid or no information about the microlens array.</exception>
        /// <exception cref="NotSupportedException">The microlens tiling specified by <paramref name="metadata"/> is not supported.</exception>
        public MicroLensCollection(Json.FrameMetadata metadata)
        {
            if (metadata == null)
            {
                throw new ArgumentNullException("metadata");
            }

            if (metadata.Image == null || metadata.Devices == null)
            {
                throw new ArgumentException("Critical metadata missing.");
            }

            _metadata = metadata;
            Json.Mla    mla    = metadata.Devices.Mla;
            Json.Sensor sensor = metadata.Devices.Sensor;

            if (mla == null || sensor == null)
            {
                throw new ArgumentException("Critical metadata missing.");
            }

            switch (mla.Tiling)
            {
            case "squareUniform":
                Initialize(metadata, 0.0, 1.0, 1.0);
                _orthogonal = true;
                break;

            case "hexUniformRowMajor":
                Initialize(metadata, HexagonalSkew, 1.0, HexagonalMinorFactor / 2.0);
                break;

            default:
                throw new NotSupportedException("Unsupported MLA tiling.");
            }
        }
        private void Initialize(Json.FrameMetadata metadata, double skewX, double deltaXfactor, double deltaYfactor)
        {
            global::System.Diagnostics.Debug.Assert(deltaXfactor != 0, "X factor cannot be zero.");
            global::System.Diagnostics.Debug.Assert(deltaYfactor != 0, "Y factor cannot be zero.");

            Json.Mla    mla    = metadata.Devices.Mla;
            Json.Sensor sensor = metadata.Devices.Sensor;
            Json.Lens   lens   = metadata.Devices.Lens;

            if (sensor.PixelPitch == 0)
            {
                throw new ArgumentException("Pixel pitch cannot be zero.", "metadata");
            }
            else if (mla.LensPitch == 0)
            {
                throw new ArgumentException("Lens pitch cannot be zero.", "metadata");
            }
            else if (mla.ScaleFactor.X == 0 || mla.ScaleFactor.Y == 0)
            {
                throw new ArgumentException("Lens scale factor cannot be zero.", "metadata");
            }

            _width  = (int)metadata.Image.Width;
            _height = (int)metadata.Image.Height;

            decimal projectedPitch = mla.LensPitch;

            if (lens != null && lens.ExitPupilOffset.Z != 0)
            {
                projectedPitch *= (lens.ExitPupilOffset.Z + mla.SensorOffset.Z) / lens.ExitPupilOffset.Z;
            }

            _rotation = (double)mla.Rotation;
            _startX   = _width / 2 + (double)(mla.SensorOffset.X / sensor.PixelPitch);
            _startY   = _height / 2 + (double)(mla.SensorOffset.Y / sensor.PixelPitch);
            _radius   = (double)(projectedPitch / sensor.PixelPitch);
            _deltaX   = (double)(projectedPitch / sensor.PixelPitch * mla.ScaleFactor.X) * deltaXfactor;
            _deltaY   = (double)(projectedPitch / sensor.PixelPitch * mla.ScaleFactor.Y) * deltaYfactor;
            _skewX    = skewX;

            double pX = _startY * Math.Cos(_rotation) - _startX * Math.Sin(_rotation);
            double pY = _startY * Math.Sin(_rotation) + _startX * Math.Cos(_rotation);

            double xMin, xMax, yMin, yMax;

            if (_rotation >= 0)
            {
                xMin = -pY;
                xMax = _height * Math.Sin(_rotation) + _width * Math.Cos(_rotation) - pY;

                yMin = -_width *Math.Sin(_rotation) - pX;

                yMax = _height * Math.Cos(_rotation) - pX;
            }
            else
            {
                yMin = -pX;
                yMax = -_width *Math.Sin(_rotation) + _height * Math.Cos(_rotation) - pX;

                xMin = _width * Math.Sin(_rotation) - pY;
                xMax = _height * Math.Cos(_rotation) - pY;
            }

            if (_skewX > 0)
            {
                xMin -= (_height - _startY) * _skewX;
                xMax += _startY * _skewX;
            }
            else if (_skewX < 0)
            {
                xMin += _startY * _skewX;
                xMax -= (_height - _startY) * _skewX;
            }

            _xMinStep = (int)Math.Floor(xMin / _deltaX);
            _xMaxStep = (int)Math.Ceiling(xMax / _deltaX);

            _yMinStep = (int)Math.Floor(yMin / _deltaY);
            _yMaxStep = (int)Math.Ceiling(yMax / _deltaY);
        }