Example #1
0
        private void    InsertMinMax(ImageUtility.float3 v, ImageUtility.float3[] _Min, ImageUtility.float3[] _Max, int _Length)
        {
            if (v.y < _Min[_Length - 1].y)
            {                   // We're sure we're good enough for that array!
                for (int i = 0; i < _Length; i++)
                {
                    if (v.y < _Min[i].y)
                    {                           // Insert here
                        if (i != _Length - 1)
                        {
                            Array.Copy(_Min, i, _Min, i + 1, _Length - i - 1);
                        }
                        _Min[i] = v;
                        break;
                    }
                }
            }

            if (v.y > _Max[_Length - 1].y)
            {                   // We're sure we're good enough for that array!
                for (int i = 0; i < _Length; i++)
                {
                    if (v.y > _Max[i].y)
                    {                           // Insert here
                        if (i != _Length - 1)
                        {
                            Array.Copy(_Max, i, _Max, i + 1, _Length - i - 1);
                        }
                        _Max[i] = v;
                        break;
                    }
                }
            }
        }
Example #2
0
        /// <summary>
        /// Calibrates a raw luminance value with spatial luminance correction
        /// </summary>
        /// <param name="_U">The U coordinate in the image (U=X/Width)</param>
        /// <param name="_V">The V coordinate in the image (V=Y/Height)</param>
        /// <param name="_Luminance">The uncalibrated value</param>
        /// <returns>The calibrated reflectance value</returns>
        /// <remarks>Typically, you start from a RAW XYZ value that you convert to xyY, pass it to this method
        /// and replace it into your orignal xyY, convert back to XYZ and voilà!</remarks>
        public ImageUtility.float3      CalibrateWithSpatialCorrection(float _U, float _V, ImageUtility.float3 _xyY)
        {
            if (m_RootNode == null)
            {
                throw new Exception("Calibration grid hasn't been built: did you provide a valid database path? Does the path contain camera calibration data?");
            }
            if (m_InterpolatedCalibration == null)
            {
                throw new Exception("Calibration grid hasn't been prepared for calibration: did you call the PrepareCalibrationFor() method?");
            }

            float CorrectionFactor = m_WhiteReflectanceCorrectionFactor;

            CorrectionFactor *= GetSpatialLuminanceCorrectionFactor(_U, _V);                                    // Apply spatial correction

            float Reflectance = m_InterpolatedCalibration.Calibrate(CorrectionFactor * _xyY.z);

            _xyY.z = Reflectance;
            if (m_DoWhiteBalance)
            {
                ImageUtility.float3 XYZ = ImageUtility.ColorProfile.xyY2XYZ(_xyY);
                XYZ *= m_WhiteBalanceXYZ;
                _xyY = ImageUtility.ColorProfile.XYZ2xyY(XYZ);
            }
            return(_xyY);
        }
Example #3
0
 /// <summary>
 /// Computes the statistical mean of the values
 /// </summary>
 /// <param name="_Values"></param>
 /// <returns></returns>
 private ImageUtility.float3     ComputeMean(ImageUtility.float3[] _Values)
 {
     ImageUtility.float3 AvgXYZ = new ImageUtility.float3(0, 0, 0);
     for (int i = 0; i < _Values.Length; i++)
     {
         AvgXYZ += _Values[i];
     }
     AvgXYZ = (1.0f / _Values.Length) * AvgXYZ;
     return(AvgXYZ);
 }
Example #4
0
        /// <summary>
        /// Builds a swatch bitmap
        /// </summary>
        /// <param name="_Width"></param>
        /// <param name="_Height"></param>
        /// <param name="_xyY"></param>
        /// <returns></returns>
        private ImageUtility.Bitmap     BuildSwatch(int _Width, int _Height, ImageUtility.float3 _xyY)
        {
            ImageUtility.Bitmap Result = new ImageUtility.Bitmap(_Width, _Height, new ImageUtility.ColorProfile(ImageUtility.ColorProfile.STANDARD_PROFILE.sRGB));
            ImageUtility.float4 XYZ    = new ImageUtility.float4(ImageUtility.ColorProfile.xyY2XYZ(_xyY), 1.0f);
            for (int Y = 0; Y < _Height; Y++)
            {
                for (int X = 0; X < _Width; X++)
                {
                    Result.ContentXYZ[X, Y] = XYZ;
                }
            }

            return(Result);
        }
Example #5
0
        /// <summary>
        /// Computes the statistical mode of the values
        /// </summary>
        /// <param name="_Values"></param>
        /// <returns></returns>
        private ImageUtility.float3     ComputeMode(ImageUtility.float3[] _Values)
        {
            int COUNT = _Values.Length;

            // The idea is simply to discretize the set of values and count the ones that are the most represented
            float Start = _Values[0].y;
            float End = _Values[_Values.Length - 1].y;
            float IntervalLength = 0.0f, Normalizer = 0.0f;

            if (Math.Abs(End - Start) > 1e-6f)
            {
                IntervalLength = (End - Start) / COUNT;
                Normalizer     = 1.0f / IntervalLength;
            }

            // Fill bins
            int[] Bins                    = new int[COUNT];
            ImageUtility.float3[] BinsSum = new ImageUtility.float3[COUNT];
            for (int i = 0; i < _Values.Length; i++)
            {
                int BinIndex = Math.Min(COUNT - 1, (int)Math.Floor((_Values[i].y - Start) * Normalizer));
                Bins[BinIndex]++;
                BinsSum[BinIndex] += _Values[i];
            }

            // Find the one that contains most values (yep, that's the mode!)
            int MaxValuesCount    = 0;
            int MaxValuesBinIndex = -1;

            for (int BinIndex = 0; BinIndex < COUNT; BinIndex++)
            {
                if (Bins[BinIndex] > MaxValuesCount)
                {                       // More filled up bin discovered!
                    MaxValuesCount    = Bins[BinIndex];
                    MaxValuesBinIndex = BinIndex;
                }
            }

            ImageUtility.float3 ModeXYZ = (1.0f / MaxValuesCount) * BinsSum[MaxValuesBinIndex];
            return(ModeXYZ);
        }
Example #6
0
        /// <summary>
        /// Computes the average color within a rectangle in UV space
        /// </summary>
        /// <param name="_TopLeft">The top left corner (in UV space) of the rectangle to sample</param>
        /// <param name="_BottomRight">The bottom right corner (in UV space) of the rectangle to sample</param>
        /// <returns>The average xyY color</returns>
        public ImageUtility.float3      ComputeAverageSwatchColor(ImageUtility.float2 _TopLeft, ImageUtility.float2 _BottomRight)
        {
            // Average xyY values in the specified rectangle
            int X0 = Math.Max(0, Math.Min(m_Texture.Width - 1, (int)Math.Floor(_TopLeft.x * m_Texture.Width)));
            int Y0 = Math.Max(0, Math.Min(m_Texture.Height - 1, (int)Math.Floor(_TopLeft.y * m_Texture.Height)));
            int X1 = Math.Min(m_Texture.Width, Math.Max(X0 + 1, (int)Math.Floor(_BottomRight.x * m_Texture.Width)));
            int Y1 = Math.Min(m_Texture.Height, Math.Max(Y0 + 1, (int)Math.Floor(_BottomRight.y * m_Texture.Height)));
            int W  = X1 - X0;
            int H  = Y1 - Y0;

            ImageUtility.float4 AverageXYZ = new ImageUtility.float4(0, 0, 0, 0);
            for (int Y = Y0; Y < Y1; Y++)
            {
                for (int X = X0; X < X1; X++)
                {
                    ImageUtility.float4 XYZ = m_Texture.ContentXYZ[X, Y];
                    AverageXYZ += XYZ;
                }
            }
            AverageXYZ = (1.0f / (W * H)) * AverageXYZ;

            ImageUtility.float3 xyY = ImageUtility.ColorProfile.XYZ2xyY((ImageUtility.float3)AverageXYZ);
            return(xyY);
        }
Example #7
0
        /// <summary>
        /// Integrates the luminance of the pixels in a given circle selected by the user
        /// </summary>
        /// <param name="_X"></param>
        /// <param name="_Y"></param>
        /// <param name="_Radius"></param>
        /// <param name="_PercentOfBlackValues"></param>
        /// <param name="_HasSaturatedValues">Returns the percentage of encountered saturated values. 0 is okay, more means the probe shouldn't be used</param>
        /// <returns></returns>
        private ImageUtility.float3 IntegrateLuminance( float _X, float _Y, float _Radius, out ImageUtility.float3 _MinXYZ, out ImageUtility.float3 _MaxXYZ, out float _PercentOfBlackValues, out float _PercentOfSaturatedValues )
        {
            float	Radius = Math.Max( 1, _Radius * m_BitmapXYZ.Width );
            float	CenterX = _X * m_BitmapXYZ.Width;
            float	CenterY = _Y * m_BitmapXYZ.Height;
            float	SqRadius = Radius*Radius;

            int		X0 = Math.Max( 0, Math.Min( m_BitmapXYZ.Width-1, (int) Math.Floor( CenterX - Radius ) ) );
            int		X1 = Math.Max( 0, Math.Min( m_BitmapXYZ.Width-1, (int) Math.Ceiling( CenterX + Radius ) ) );
            int		Y0 = Math.Max( 0, Math.Min( m_BitmapXYZ.Height-1, (int) Math.Floor( CenterY - Radius ) ) );
            int		Y1 = Math.Max( 0, Math.Min( m_BitmapXYZ.Height-1, (int) Math.Ceiling( CenterY + Radius ) ) );

            const float	SMOOTHSTEP_MAX_RADIUS = 0.2f;	// We reach max weight 1 at 20% of the border of the circle

            bool	ApplySpatialCorrection = checkBoxSpatialLuminanceCorrection.Checked;

            int		TotalBlackValuesCount = 0;
            int		TotalSaturatedValuesCount = 0;
            int		TotalValuesCount = 0;
            ImageUtility.float3	SumXYZ = new ImageUtility.float3( 0, 0, 0 );
            _MinXYZ = new ImageUtility.float3( 0, +float.MaxValue, 0 );
            _MaxXYZ = new ImageUtility.float3( 0, 0, 0 );
            float	SumWeights = 0.0f;
            for ( int Y=Y0; Y < Y1; Y++ )
                for ( int X=X0; X < X1; X++ )
                {
                    float	SqR = (X-CenterX)*(X-CenterX) + (Y-CenterY)*(Y-CenterY);
                    if ( SqR > SqRadius )
                        continue;

                    float	r = (float) Math.Sqrt( SqR ) / Radius;	// Nomalized radius
            //					float	Weight = Math.Min( 1.0f, floatTrackbarControl1.Value * (float) Math.Exp( -floatTrackbarControl2.Value * r ) );

                    float	x = Math.Max( 0.0f, Math.Min( 1.0f, (1.0f - r) / SMOOTHSTEP_MAX_RADIUS ) );
                    float	Weight = x*x*(3.0f - 2.0f*x);

            //DEBUG					m_BitmapXYZ.ContentXYZ[X,Y].y = Weight;
                    ImageUtility.float3	XYZ = (ImageUtility.float3) m_BitmapXYZ.ContentXYZ[X,Y];
                    if ( ApplySpatialCorrection )
                    {
                        ImageUtility.float3	xyY = ImageUtility.ColorProfile.XYZ2xyY( XYZ );
                        xyY.z *= m_CalibrationDatabase.GetSpatialLuminanceCorrectionFactor( (float) X / m_BitmapXYZ.Width, (float) Y / m_BitmapXYZ.Height );
                        XYZ = ImageUtility.ColorProfile.xyY2XYZ( xyY );
                    }

                    if ( XYZ.y < 0.001f )
                        TotalBlackValuesCount++;		// Warning!
                    if ( XYZ.y > 0.999f )
                        TotalSaturatedValuesCount++;	// Warning!
                    TotalValuesCount++;

                    if ( XYZ.y < _MinXYZ.y )
                        _MinXYZ = XYZ;
                    if ( XYZ.y > _MaxXYZ.y )
                        _MaxXYZ = XYZ;
                    SumXYZ += Weight * XYZ;
                    SumWeights += Weight;
                }

            //DEBUG			RebuildImage();

            _PercentOfBlackValues = (float) TotalBlackValuesCount / TotalValuesCount;
            _PercentOfSaturatedValues = (float) TotalSaturatedValuesCount / TotalValuesCount;

            SumXYZ = (1.0f/SumWeights) * SumXYZ;
            return SumXYZ;
        }
Example #8
0
        private void buttonWhiteRefTest3_Click( object sender, EventArgs e )
        {
            int	W = DEFAULT_WHITE_REFERENCE_IMAGE_SIZE;
            int	H = DEFAULT_WHITE_REFERENCE_IMAGE_SIZE;
            ImageUtility.Bitmap	WhiteRef = new ImageUtility.Bitmap( W, H, m_sRGBProfile );
            ImageUtility.float3	xyY = new ImageUtility.float3( m_sRGBProfile.Chromas.W.x, m_sRGBProfile.Chromas.W.y, 0.0f );
            for ( int Y=0; Y < H; Y++ )
            {
                float	V = (float) Y / H;
                for ( int X=0; X < W; X++ )
                {
                    float	U = (float) X / W;
                    xyY.z = Math.Min( 1.0f, 1.0f - 0.5f * V );

                    ImageUtility.float4	XYZ = new ImageUtility.float4( ImageUtility.ColorProfile.xyY2XYZ( xyY ), 1.0f );
                    WhiteRef.ContentXYZ[X,Y] = XYZ;
                }
            }

            // Assign to the database
            m_CalibrationDatabase.WhiteReferenceImage = WhiteRef;

            UpdateWhiteReferenceImageUI();
        }
Example #9
0
        private void buttonResetWhiteReflectance_Click( object sender, EventArgs e )
        {
            m_CalibrationDatabase.WhiteReflectanceReference = new ImageUtility.float3( 0, 0, -1 );	// Will reset the factor

            // Clear cached white reference data
            m_WhiteReflectanceReference = m_CalibrationDatabase.WhiteReflectanceReference;
            m_WhiteReflectanceISOSpeed = -1;
            m_WhiteReflectanceShutterSpeed = -1;
            m_WhiteReflectanceAperture = -1;

            SetRegKey( "ReloadWhiteReflectanceOnStartup", "false" );	// Prevent auto load on startup

            UpdateWhiteReflectanceUI();
        }
Example #10
0
        //////////////////////////////////////////////////////////////////////////
        // White Reflectance Reference
        //
        private void buttonPickWhiteReflectance_Click( object sender, EventArgs e )
        {
            if ( m_BitmapXYZ == null )
            {	// No image loaded you moron!
                MessageBox( "Can't pick white reflectance as no image is currently loaded!", MessageBoxButtons.OK, MessageBoxIcon.Exclamation );
                return;
            }

            outputPanel.StartCalibrationTargetPicking( ( ImageUtility.float2 _Center, float _Radius ) => {

                ImageUtility.float3	MinXYZ, MaxXYZ;
                float	BlackValues, SaturatedValues;
                ImageUtility.float3	AverageXYZ = IntegrateLuminance( _Center.x, _Center.y, _Radius, out MinXYZ, out MaxXYZ, out BlackValues, out SaturatedValues );

                if ( BlackValues > BLACK_VALUES_TOLERANCE )
                {
                    MessageBox( "This probe has more than 5% luminance values that are too dark, it cannot be used as a white reference!", MessageBoxButtons.OK, MessageBoxIcon.Warning );
                    return;
                }
                if ( SaturatedValues > SATURATED_VALUES_TOLERANCE )
                {
                    if ( MessageBox( "This probe has more than 5% luminance values that are saturated, it SHOULD NOT be used as a white reference!\r\n\r\nDo you want to use it anyway?", MessageBoxButtons.YesNo, MessageBoxIcon.Warning ) != DialogResult.Yes )
                        return;
                }

                // Scale the luminance based on the user-supplied white target
            //				ImageUtility.float3	WhiteRefxyY = ImageUtility.ColorProfile.XYZ2xyY( MaxXYZ );		// Use Max otherwise we can get luminances higher than 99%!
                ImageUtility.float3	WhiteRefxyY = ImageUtility.ColorProfile.XYZ2xyY( AverageXYZ );	// NO! Use Average because we want a white probe to return exactly the same result as when we sampled it at calibration time!!!!
                WhiteRefxyY.z *= 99.0f / floatTrackbarControlTargetWhiteReflectance.Value;

                m_CalibrationDatabase.WhiteReflectanceReference = WhiteRefxyY;

                // Cache white reference data
                m_WhiteReflectanceReference = m_CalibrationDatabase.WhiteReflectanceReference;
                m_WhiteReflectanceISOSpeed = m_CalibrationDatabase.PreparedForISOSpeed;
                m_WhiteReflectanceShutterSpeed = m_CalibrationDatabase.PreparedForShutterSpeed;
                m_WhiteReflectanceAperture = m_CalibrationDatabase.PreparedForAperture;

                UpdateWhiteReflectanceUI();
            } );
        }
Example #11
0
 /// <summary>
 /// Computes the statistical mean of the values
 /// </summary>
 /// <param name="_Values"></param>
 /// <returns></returns>
 private ImageUtility.float3 ComputeMean( ImageUtility.float3[] _Values )
 {
     ImageUtility.float3	AvgXYZ = new ImageUtility.float3( 0, 0, 0 );
     for ( int i=0; i < _Values.Length; i++ )
         AvgXYZ += _Values[i];
     AvgXYZ = (1.0f / _Values.Length) * AvgXYZ;
     return AvgXYZ;
 }
Example #12
0
            public ImageUtility.float3 m_xyY; // Last picked xyY color

            #endregion Fields

            #region Methods

            public virtual void UpdateSwatchColor()
            {
                m_RGB = (ImageUtility.float3) m_Owner.m_sRGBProfile.XYZ2RGB( new ImageUtility.float4( ImageUtility.ColorProfile.xyY2XYZ( m_xyY ), 1.0f ) );

                Color	C = Color.FromArgb( Math.Max( 0, Math.Min( 255, (int) (m_RGB.x * 255.0f) ) ),
                                            Math.Max( 0, Math.Min( 255, (int) (m_RGB.y * 255.0f) ) ),
                                            Math.Max( 0, Math.Min( 255, (int) (m_RGB.z * 255.0f) ) ) );
                m_Panel.BackColor = C;
                m_Panel.ForeColor = m_xyY.z > 1.0f ? Color.Red : Color.Black;
            }
Example #13
0
            public ImageUtility.Bitmap Texture;                                                                 // The bitmap generated from the swatch color

            public virtual void     Save(CalibratedTexture _Owner, XmlElement _SwatchElement)
            {
                ImageUtility.float4 XYZ = new ImageUtility.float4(ImageUtility.ColorProfile.xyY2XYZ(xyY), 1.0f);
                ImageUtility.float3 RGB = (ImageUtility.float3)Texture.Profile.XYZ2RGB(XYZ);
                _Owner.SetAttribute(_SwatchElement, "xyY", xyY.ToString()).SetAttribute("RGB", RGB.ToString());
            }
Example #14
0
        /// <summary>
        /// Captures the calibrated texture
        /// </summary>
        /// <param name="_Source">The source image to capture</param>
        /// <param name="_Database">Database to perform proper calibration</param>
        /// <param name="_Parms">Parameters for the capture</param>
        public void             Capture(ImageUtility.Bitmap _Source, CameraCalibrationDatabase _Database, CaptureParms _Parms)
        {
            if (_Source == null)
            {
                throw new Exception("Invalid source bitmap to build texture from!");
            }
            if (_Database == null)
            {
                throw new Exception("Invalid calibration database found in parameters!");
            }
            if (_Parms == null)
            {
                throw new Exception("Invalid calibration parameters!");
            }
            if (m_SwatchWidth <= 0 || m_SwatchHeight <= 0)
            {
                throw new Exception("Invalid swatch size! Must be > 0!");
            }

            // Save parameters as they're associated to this texture
            m_CaptureParameters                = _Parms;
            m_WhiteReflectanceReference        = _Database.WhiteReflectanceReference;
            m_WhiteReflectanceCorrectionFactor = _Database.WhiteReflectanceCorrectionFactor;
            m_SpatialCorrectionEnabled         = _Database.WhiteReferenceImage != null;

            //////////////////////////////////////////////////////////////////////////
            // Setup the database to find the most appropriate calibration data for our image infos
            _Database.PrepareCalibrationFor(_Parms.ISOSpeed, _Parms.ShutterSpeed, _Parms.Aperture);


            //////////////////////////////////////////////////////////////////////////
            // Build target texture
            ImageUtility.float4 AvgXYZ = new ImageUtility.float4(0, 0, 0, 0);
//DEBUG
// float	MinLuminance_Raw = float.MaxValue;
// float	MaxLuminance_Raw = -float.MaxValue;

            const int EXTREME_VALUES_COUNT = 100;

            ImageUtility.float3[] ArrayMin = new ImageUtility.float3[EXTREME_VALUES_COUNT];
            ImageUtility.float3[] ArrayMax = new ImageUtility.float3[EXTREME_VALUES_COUNT];
            for (int i = 0; i < EXTREME_VALUES_COUNT; i++)
            {
                ArrayMin[i] = new ImageUtility.float3(0, 1, 0);
                ArrayMax[i] = new ImageUtility.float3(0, 0, 0);
            }

            if (_Parms.CropSource)
            {
                float fImageWidth  = 2.0f * _Parms.CropRectangleHalfSize.x * _Source.Height;
                float fImageHeight = 2.0f * _Parms.CropRectangleHalfSize.y * _Source.Height;
                int   W            = (int)Math.Floor(fImageWidth);
                int   H            = (int)Math.Floor(fImageHeight);

                ImageUtility.float2 AxisX = new ImageUtility.float2((float)Math.Cos(_Parms.CropRectangleRotation), -(float)Math.Sin(_Parms.CropRectangleRotation));
                ImageUtility.float2 AxisY = new ImageUtility.float2((float)Math.Sin(_Parms.CropRectangleRotation), (float)Math.Cos(_Parms.CropRectangleRotation));

                ImageUtility.float2 TopLeftCorner = new ImageUtility.float2(0.5f * (_Source.Width - _Source.Height) + _Parms.CropRectangleCenter.x * _Source.Height, _Source.Height * _Parms.CropRectangleCenter.y)
                                                    + _Source.Height * (-_Parms.CropRectangleHalfSize.x * AxisX - _Parms.CropRectangleHalfSize.y * AxisY);

                m_Texture = new ImageUtility.Bitmap(W, H, new ImageUtility.ColorProfile(ImageUtility.ColorProfile.STANDARD_PROFILE.sRGB));
                ImageUtility.float4 XYZ;
                ImageUtility.float3 ShortXYZ;
                ImageUtility.float3 xyY;

                ImageUtility.float2 CurrentScanlinePixel = TopLeftCorner + 0.5f * (fImageWidth - W) * AxisX + 0.5f * (fImageHeight - H) * AxisY;
                if (Math.Abs(_Parms.CropRectangleRotation) < 1e-6f)
                {                       // Use integer pixels to avoid attenuated values due to bilinear filtering
                    CurrentScanlinePixel.x = (float)Math.Floor(CurrentScanlinePixel.x);
                    CurrentScanlinePixel.y = (float)Math.Floor(CurrentScanlinePixel.y);
                }
                for (int Y = 0; Y < H; Y++)
                {
                    ImageUtility.float2 CurrentPixel = CurrentScanlinePixel;
                    for (int X = 0; X < W; X++)
                    {
                        float U = CurrentPixel.x / _Source.Width;
                        float V = CurrentPixel.y / _Source.Height;

                        XYZ = _Source.BilinearSample(CurrentPixel.x, CurrentPixel.y);

//DEBUG
// float	L = XYZ.y * _Database.GetSpatialLuminanceCorrectionFactor( U, V );
// if ( L < MinLuminance_Raw )
//  MinLuminance_Raw = L;
// if ( L > MaxLuminance_Raw )
//  MaxLuminance_Raw = L;
//DEBUG

                        xyY      = ImageUtility.ColorProfile.XYZ2xyY((ImageUtility.float3)XYZ);
                        xyY      = _Database.CalibrateWithSpatialCorrection(U, V, xyY);                         // Apply luminance calibration
                        ShortXYZ = ImageUtility.ColorProfile.xyY2XYZ(xyY);
                        XYZ      = new ImageUtility.float4(ShortXYZ, XYZ.w);
                        m_Texture.ContentXYZ[X, Y] = XYZ;

                        // Update min/max/avg values
                        InsertMinMax(ShortXYZ, ArrayMin, ArrayMax, EXTREME_VALUES_COUNT);
                        AvgXYZ += XYZ;

                        CurrentPixel += AxisX;
                    }
                    CurrentScanlinePixel += AxisY;
                }
            }
            else
            {                   // Simple texture copy, with luminance calibration
                m_Texture = new ImageUtility.Bitmap(_Source.Width, _Source.Height, new ImageUtility.ColorProfile(ImageUtility.ColorProfile.STANDARD_PROFILE.sRGB));
                ImageUtility.float4 XYZ;
                ImageUtility.float3 ShortXYZ;
                ImageUtility.float3 xyY;

                int W = m_Texture.Width;
                int H = m_Texture.Height;

                int X0 = 0;
                int X1 = W;
                int Y0 = 0;
                int Y1 = H;

//DEBUG
// X0 = 1088; Y0 = 764;
// X1 = X0 + 1100; Y1 = Y0 + 632;

                for (int Y = Y0; Y < Y1; Y++)
                {
                    float V = (float)Y / H;
                    for (int X = X0; X < X1; X++)
                    {
                        float U = (float)X / W;

                        XYZ = _Source.ContentXYZ[X, Y];

//DEBUG
// float	L = XYZ.y * _Database.GetSpatialLuminanceCorrectionFactor( U, V );
// if ( L < MinLuminance_Raw )
//  MinLuminance_Raw = L;
// if ( L > MaxLuminance_Raw )
//  MaxLuminance_Raw = L;
//DEBUG

                        xyY      = ImageUtility.ColorProfile.XYZ2xyY((ImageUtility.float3)XYZ);
                        xyY      = _Database.CalibrateWithSpatialCorrection(U, V, xyY);                         // Apply luminance calibration
                        ShortXYZ = ImageUtility.ColorProfile.xyY2XYZ(xyY);
                        XYZ      = new ImageUtility.float4(ShortXYZ, XYZ.w);
                        m_Texture.ContentXYZ[X, Y] = XYZ;

                        // Update min/max/avg values
                        InsertMinMax(ShortXYZ, ArrayMin, ArrayMax, EXTREME_VALUES_COUNT);
                        AvgXYZ += XYZ;
                    }
                }
            }

            // Normalize average swatch color
            float Normalizer = 1.0f / (m_Texture.Width * m_Texture.Height);

            ImageUtility.float3 avgxyY = ImageUtility.ColorProfile.XYZ2xyY(Normalizer * ((ImageUtility.float3)AvgXYZ));
            m_SwatchAvg.xyY = avgxyY;

            // Compute min & max using statistical norm
            ImageUtility.float3 BestXYZ_Min;
            ImageUtility.float3 BestXYZ_Max;

            if (_Parms.UseModeInsteadOfMean)
            {                   // Use mode
                BestXYZ_Min = ComputeMode(ArrayMin);
                BestXYZ_Max = ComputeMode(ArrayMax);
            }
            else
            {                   // Use mean
                BestXYZ_Min = ComputeMean(ArrayMin);
                BestXYZ_Max = ComputeMean(ArrayMax);
            }
            m_SwatchMin.xyY = ImageUtility.ColorProfile.XYZ2xyY(BestXYZ_Min);
            m_SwatchMax.xyY = ImageUtility.ColorProfile.XYZ2xyY(BestXYZ_Max);

            m_SwatchMin.Texture = BuildSwatch(m_SwatchWidth, m_SwatchHeight, m_SwatchMin.xyY);
            m_SwatchMax.Texture = BuildSwatch(m_SwatchWidth, m_SwatchHeight, m_SwatchMax.xyY);
            m_SwatchAvg.Texture = BuildSwatch(m_SwatchWidth, m_SwatchHeight, m_SwatchAvg.xyY);

            // Rebuild custom swatches
            foreach (CustomSwatch CS in m_CustomSwatches)
            {
                CS.Texture = BuildSwatch(m_SwatchWidth, m_SwatchHeight, CS.xyY);
            }

            //////////////////////////////////////////////////////////////////////////
            // Feed some purely informational shot infos to the main texture, probably won't be saved anyway...
            m_Texture.HasValidShotInfo = true;
            m_Texture.ISOSpeed         = _Parms.ISOSpeed;
            m_Texture.ShutterSpeed     = _Parms.ShutterSpeed;
            m_Texture.Aperture         = _Parms.Aperture;
        }
Example #15
0
        /// <summary>
        /// Loads the white reflectance reference from a file
        /// </summary>
        /// <param name="_WhiteReflectanceFileName"></param>
        /// <param name="_AlertIfFailed"></param>
        private void LoadWhiteReflectanceFile( System.IO.FileInfo _FileName, bool _AlertIfFailed )
        {
            try
            {
                // Load
                System.Xml.XmlDocument	Doc = new System.Xml.XmlDocument();
                Doc.Load( _FileName.FullName );

                System.Xml.XmlElement	Root = Doc["WhiteReflectance"];
                if ( Root == null )
                    throw new Exception( "Couldn't find expected root element \"WhiteReflectance\"! Is this a white ref file?" );

                m_WhiteReflectanceReference = new ImageUtility.float3( 0, 0, -1 );	// Invalid
                m_WhiteReflectanceReference = ImageUtility.float3.Parse( Root.GetAttribute( "Value" ) );

                m_WhiteReflectanceISOSpeed = floatTrackbarControlISOSpeed.Value;
                float.TryParse( Root.GetAttribute( "ISOSpeed" ), out m_WhiteReflectanceISOSpeed );
                m_WhiteReflectanceShutterSpeed = floatTrackbarControlShutterSpeed.Value;
                float.TryParse( Root.GetAttribute( "ShutterSpeed" ), out m_WhiteReflectanceShutterSpeed );
                m_WhiteReflectanceAperture = floatTrackbarControlAperture.Value;
                float.TryParse( Root.GetAttribute( "Aperture" ), out m_WhiteReflectanceAperture );

                m_CalibrationDatabase.WhiteReflectanceReference = m_WhiteReflectanceReference;

                UpdateWhiteReflectanceUI();
            }
            catch ( Exception _e )
            {
                if ( _AlertIfFailed )
                    MessageBox( "An error occurred while loading white reflectance file \"" + _FileName.FullName + "\":\r\n\r\n", _e );
            }
        }
Example #16
0
            public ImageUtility.float2 m_LocationTopLeft = new ImageUtility.float2( -1, -1 ); // Last picked location

            #endregion Fields

            #region Methods

            public override void UpdateSwatchColor()
            {
                if ( !m_CheckBox.Checked || m_Owner.m_Texture == null )
                {
                    m_CheckBox.Checked = false;
                    m_xyY = m_RGB = new ImageUtility.float3( 0, 0, 0 );
                    m_Panel.BackColor = Color.DimGray;
                    return;
                }

                // Re-capture
                m_xyY = m_Owner.m_Texture.ComputeAverageSwatchColor( m_LocationTopLeft, m_LocationBottomRight );
                base.UpdateSwatchColor();
            }
Example #17
0
        void TestChromaRanges()
        {
            ImageUtility.ColorProfile	profile = new ImageUtility.ColorProfile(ImageUtility.ColorProfile.STANDARD_PROFILE.sRGB );

            ImageUtility.float3	tempFloat3 = new ImageUtility.float3( 0, 0, 0 );
            ImageUtility.float4	tempFloat4 = new ImageUtility.float4( 0, 0, 0, 1 );

            float	Ygo, Cg, Co;

            ranges_t[]	ranges = new ranges_t[4];
            for ( int lumaIndex=0; lumaIndex < ranges.Length; lumaIndex++ ) {

                ranges_t	range = new ranges_t();
                ranges[lumaIndex] = range;

                float	L = (1+lumaIndex) / 255.0f;

                for ( int R=0; R < 256; R++ ) {
                    for ( int G=0; G < 256; G++ ) {
                        for ( int B=0; B < 256; B++ ) {

                            tempFloat4.x = L * R;
                            tempFloat4.y = L * G;
                            tempFloat4.z = L * B;

                            // Convert to YCoCg
            // 							Ygo = 0.25f * tempFloat4.x + 0.5f * tempFloat4.y + 0.25f * tempFloat4.z;
            // 							Cg = -0.25f * tempFloat4.x + 0.5f * tempFloat4.y - 0.25f * tempFloat4.z;
            // 							Co =  0.50f * tempFloat4.x + 0.0f * tempFloat4.y - 0.50f * tempFloat4.z;

                            RGB2YCoCg( tempFloat4.x, tempFloat4.y, tempFloat4.z, out Ygo, out Co, out Cg );
                            YCoCg2RGB( Ygo, Co, Cg, out tempFloat3.x, out tempFloat3.y, out tempFloat3.z );
                            if ( Math.Abs( tempFloat3.x - tempFloat4.x ) > 1e-6 ) throw new Exception( "RHA!" );
                            if ( Math.Abs( tempFloat3.y - tempFloat4.y ) > 1e-6 ) throw new Exception( "RHA!" );
                            if ( Math.Abs( tempFloat3.z - tempFloat4.z ) > 1e-6 ) throw new Exception( "RHA!" );

                            // Convert to xyY
                            ImageUtility.float4	XYZ = profile.RGB2XYZ( tempFloat4 );
                            tempFloat3.x = XYZ.x;
                            tempFloat3.y = XYZ.y;
                            tempFloat3.z = XYZ.z;
                            ImageUtility.float3	xyY = ImageUtility.ColorProfile.XYZ2xyY( tempFloat3 );

                            // Update ranges
                            range.Ygo_min = Math.Min( range.Ygo_min, Ygo );
                            range.Ygo_max = Math.Max( range.Ygo_max, Ygo );
                            range.Cg_min = Math.Min( range.Cg_min, Cg );
                            range.Cg_max = Math.Max( range.Cg_max, Cg );
                            range.Co_min = Math.Min( range.Co_min, Co );
                            range.Co_max = Math.Max( range.Co_max, Co );

                            range.Y_min = Math.Min( range.Y_min, xyY.z );
                            range.Y_max = Math.Max( range.Y_max, xyY.z );
                            range.x_min = Math.Min( range.x_min, xyY.x );
                            range.x_max = Math.Max( range.x_max, xyY.x );
                            range.y_min = Math.Min( range.y_min, xyY.y );
                            range.y_max = Math.Max( range.y_max, xyY.y );
                        }
                    }
                }

            }
        }
Example #18
0
        /// <summary>
        /// Captures the calibrated texture
        /// </summary>
        /// <param name="_Source">The source image to capture</param>
        /// <param name="_Database">Database to perform proper calibration</param>
        /// <param name="_Parms">Parameters for the capture</param>
        public void Capture( ImageUtility.Bitmap _Source, CameraCalibrationDatabase _Database, CaptureParms _Parms )
        {
            if ( _Source == null )
                throw new Exception( "Invalid source bitmap to build texture from!" );
            if ( _Database == null )
                throw new Exception( "Invalid calibration database found in parameters!" );
            if ( _Parms == null )
                throw new Exception( "Invalid calibration parameters!" );
            if ( m_SwatchWidth <= 0 || m_SwatchHeight <= 0 )
                throw new Exception( "Invalid swatch size! Must be > 0!" );

            // Save parameters as they're associated to this texture
            m_CaptureParameters = _Parms;
            m_WhiteReflectanceReference = _Database.WhiteReflectanceReference;
            m_WhiteReflectanceCorrectionFactor = _Database.WhiteReflectanceCorrectionFactor;
            m_SpatialCorrectionEnabled = _Database.WhiteReferenceImage != null;

            //////////////////////////////////////////////////////////////////////////
            // Setup the database to find the most appropriate calibration data for our image infos
            _Database.PrepareCalibrationFor( _Parms.ISOSpeed, _Parms.ShutterSpeed, _Parms.Aperture );

            //////////////////////////////////////////////////////////////////////////
            // Build target texture
            ImageUtility.float4	AvgXYZ = new ImageUtility.float4( 0, 0, 0, 0 );
            //DEBUG
            // float	MinLuminance_Raw = float.MaxValue;
            // float	MaxLuminance_Raw = -float.MaxValue;

            const int	EXTREME_VALUES_COUNT = 100;
            ImageUtility.float3[]	ArrayMin = new ImageUtility.float3[EXTREME_VALUES_COUNT];
            ImageUtility.float3[]	ArrayMax = new ImageUtility.float3[EXTREME_VALUES_COUNT];
            for ( int i=0; i < EXTREME_VALUES_COUNT; i++ )
            {
                ArrayMin[i] = new ImageUtility.float3( 0, 1, 0 );
                ArrayMax[i] = new ImageUtility.float3( 0, 0, 0 );
            }

            if ( _Parms.CropSource )
            {
                float	fImageWidth = 2.0f * _Parms.CropRectangleHalfSize.x * _Source.Height;
                float	fImageHeight = 2.0f * _Parms.CropRectangleHalfSize.y * _Source.Height;
                int		W = (int) Math.Floor( fImageWidth );
                int		H = (int) Math.Floor( fImageHeight );

                ImageUtility.float2	AxisX = new ImageUtility.float2( (float) Math.Cos( _Parms.CropRectangleRotation ), -(float) Math.Sin( _Parms.CropRectangleRotation ) );
                ImageUtility.float2	AxisY = new ImageUtility.float2( (float) Math.Sin( _Parms.CropRectangleRotation ), (float) Math.Cos( _Parms.CropRectangleRotation ) );

                ImageUtility.float2	TopLeftCorner = new ImageUtility.float2( 0.5f * (_Source.Width - _Source.Height) + _Parms.CropRectangleCenter.x * _Source.Height, _Source.Height * _Parms.CropRectangleCenter.y )
                                                  + _Source.Height * (-_Parms.CropRectangleHalfSize.x * AxisX - _Parms.CropRectangleHalfSize.y * AxisY);

                m_Texture = new ImageUtility.Bitmap( W, H, new ImageUtility.ColorProfile( ImageUtility.ColorProfile.STANDARD_PROFILE.sRGB ) );
                ImageUtility.float4	XYZ;
                ImageUtility.float3	ShortXYZ;
                ImageUtility.float3	xyY;

                ImageUtility.float2	CurrentScanlinePixel = TopLeftCorner + 0.5f * (fImageWidth - W) * AxisX + 0.5f * (fImageHeight - H) * AxisY;
                if ( Math.Abs( _Parms.CropRectangleRotation ) < 1e-6f )
                {	// Use integer pixels to avoid attenuated values due to bilinear filtering
                    CurrentScanlinePixel.x = (float) Math.Floor( CurrentScanlinePixel.x );
                    CurrentScanlinePixel.y = (float) Math.Floor( CurrentScanlinePixel.y );
                }
                for ( int Y=0; Y < H; Y++ )
                {
                    ImageUtility.float2	CurrentPixel = CurrentScanlinePixel;
                    for ( int X=0; X < W; X++ )
                    {
                        float	U = CurrentPixel.x / _Source.Width;
                        float	V = CurrentPixel.y / _Source.Height;

                        XYZ = _Source.BilinearSample( CurrentPixel.x, CurrentPixel.y );

            //DEBUG
            // float	L = XYZ.y * _Database.GetSpatialLuminanceCorrectionFactor( U, V );
            // if ( L < MinLuminance_Raw )
            // 	MinLuminance_Raw = L;
            // if ( L > MaxLuminance_Raw )
            // 	MaxLuminance_Raw = L;
            //DEBUG

                        xyY = ImageUtility.ColorProfile.XYZ2xyY( (ImageUtility.float3) XYZ );
                        xyY = _Database.CalibrateWithSpatialCorrection( U, V, xyY );	// Apply luminance calibration
                        ShortXYZ = ImageUtility.ColorProfile.xyY2XYZ( xyY );
                        XYZ = new ImageUtility.float4( ShortXYZ, XYZ.w );
                        m_Texture.ContentXYZ[X,Y] = XYZ;

                        // Update min/max/avg values
                        InsertMinMax( ShortXYZ, ArrayMin, ArrayMax, EXTREME_VALUES_COUNT );
                        AvgXYZ += XYZ;

                        CurrentPixel += AxisX;
                    }
                    CurrentScanlinePixel += AxisY;
                }
            }
            else
            {	// Simple texture copy, with luminance calibration
                m_Texture = new ImageUtility.Bitmap( _Source.Width, _Source.Height, new ImageUtility.ColorProfile( ImageUtility.ColorProfile.STANDARD_PROFILE.sRGB ) );
                ImageUtility.float4	XYZ;
                ImageUtility.float3	ShortXYZ;
                ImageUtility.float3	xyY;

                int	W = m_Texture.Width;
                int	H = m_Texture.Height;

                int	X0 = 0;
                int	X1 = W;
                int	Y0 = 0;
                int	Y1 = H;

            //DEBUG
            // X0 = 1088; Y0 = 764;
            // X1 = X0 + 1100; Y1 = Y0 + 632;

                for ( int Y=Y0; Y < Y1; Y++ )
                {
                    float	V = (float) Y / H;
                    for ( int X=X0; X < X1; X++ )
                    {
                        float	U = (float) X / W;

                        XYZ = _Source.ContentXYZ[X,Y];

            //DEBUG
            // float	L = XYZ.y * _Database.GetSpatialLuminanceCorrectionFactor( U, V );
            // if ( L < MinLuminance_Raw )
            // 	MinLuminance_Raw = L;
            // if ( L > MaxLuminance_Raw )
            // 	MaxLuminance_Raw = L;
            //DEBUG

                        xyY = ImageUtility.ColorProfile.XYZ2xyY( (ImageUtility.float3) XYZ );
                        xyY = _Database.CalibrateWithSpatialCorrection( U, V, xyY );	// Apply luminance calibration
                        ShortXYZ = ImageUtility.ColorProfile.xyY2XYZ( xyY );
                        XYZ = new ImageUtility.float4( ShortXYZ, XYZ.w );
                        m_Texture.ContentXYZ[X,Y] = XYZ;

                        // Update min/max/avg values
                        InsertMinMax( ShortXYZ, ArrayMin, ArrayMax, EXTREME_VALUES_COUNT );
                        AvgXYZ += XYZ;
                    }
                }
            }

            // Normalize average swatch color
            float	Normalizer = 1.0f / (m_Texture.Width*m_Texture.Height);
            ImageUtility.float3	avgxyY = ImageUtility.ColorProfile.XYZ2xyY( Normalizer * ((ImageUtility.float3) AvgXYZ) );
            m_SwatchAvg.xyY = avgxyY;

            // Compute min & max using statistical norm
             			ImageUtility.float3	BestXYZ_Min;
             			ImageUtility.float3	BestXYZ_Max;

            if ( _Parms.UseModeInsteadOfMean )
            {	// Use mode
                BestXYZ_Min = ComputeMode( ArrayMin );
                BestXYZ_Max = ComputeMode( ArrayMax );
            }
            else
            {	// Use mean
             				BestXYZ_Min = ComputeMean( ArrayMin );
             				BestXYZ_Max = ComputeMean( ArrayMax );
            }
            m_SwatchMin.xyY = ImageUtility.ColorProfile.XYZ2xyY( BestXYZ_Min );
            m_SwatchMax.xyY = ImageUtility.ColorProfile.XYZ2xyY( BestXYZ_Max );

            m_SwatchMin.Texture = BuildSwatch( m_SwatchWidth, m_SwatchHeight, m_SwatchMin.xyY );
            m_SwatchMax.Texture = BuildSwatch( m_SwatchWidth, m_SwatchHeight, m_SwatchMax.xyY );
            m_SwatchAvg.Texture = BuildSwatch( m_SwatchWidth, m_SwatchHeight, m_SwatchAvg.xyY );

            // Rebuild custom swatches
            foreach ( CustomSwatch CS in m_CustomSwatches )
                CS.Texture = BuildSwatch( m_SwatchWidth, m_SwatchHeight, CS.xyY );

            //////////////////////////////////////////////////////////////////////////
            // Feed some purely informational shot infos to the main texture, probably won't be saved anyway...
            m_Texture.HasValidShotInfo = true;
            m_Texture.ISOSpeed = _Parms.ISOSpeed;
            m_Texture.ShutterSpeed = _Parms.ShutterSpeed;
            m_Texture.Aperture = _Parms.Aperture;
        }
        /// <summary>
        /// Prepares the interpolated calibration table to process the pixels in an image shot with the specified shot infos
        /// </summary>
        /// <param name="_ISOSpeed"></param>
        /// <param name="_ShutterSpeed"></param>
        /// <param name="_Aperture"></param>
        public void PrepareCalibrationFor( float _ISOSpeed, float _ShutterSpeed, float _Aperture )
        {
            if ( m_RootNode == null )
                throw new Exception( "Calibration grid hasn't been built: did you provide a valid database path? Does the path contain camera calibration data?" );

            if ( IsPreparedFor( _ISOSpeed, _ShutterSpeed, _Aperture ) )
                return;	// Already prepared!

            //////////////////////////////////////////////////////////////////////////
            // Find the 8 nodes encompassing our values
            // I'm making the delicate assumption that, although the starting node is chosen on the
            //	condition its EV values are strictly inferior to the target we're looking for, all
            //	neighbor nodes should satisfy the condition they're properly placed.
            //
            // This is true for the direct neighbors +X, +Y, +Z that are immediately above target values
            //	but for example, neighbor (+X +Y) may have a very bad aperture value (Z) that may be
            //	above the target aperture...
            //
            // Let's hope the user won't provide too fancy calibrations...
            // (anyway, interpolants are clamped in [0,1] so there's no risk of overshooting)
            //
            ImageUtility.float3	EV;
            GridNode.Convert2EV( _ISOSpeed, _ShutterSpeed, _Aperture, out EV.x, out EV.y, out EV.z );

            // Find the start node
            GridNode		StartNode = FindStartNode( EV.x, EV.y, EV.z );
            m_InterpolationStartNode = StartNode;

            // Build the 8 grid nodes from it
            GridNode[,,]	Grid = new GridNode[2,2,2];
            Grid[0,0,0] = StartNode;
            Grid[1,0,0] = StartNode.m_Neighbors[0][1] != null ? StartNode.m_Neighbors[0][1] : StartNode;		// +X
            Grid[0,1,0] = StartNode.m_Neighbors[1][1] != null ? StartNode.m_Neighbors[1][1] : StartNode;		// +Y
            Grid[0,0,1] = StartNode.m_Neighbors[2][1] != null ? StartNode.m_Neighbors[2][1] : StartNode;		// +Z
            Grid[1,1,0] = Grid[1,0,0].m_Neighbors[1][1] != null ? Grid[1,0,0].m_Neighbors[1][1] : Grid[1,0,0];	// +X +Y
            Grid[0,1,1] = Grid[0,1,0].m_Neighbors[2][1] != null ? Grid[0,1,0].m_Neighbors[2][1] : Grid[0,1,0];	// +Y +Z
            Grid[1,0,1] = Grid[0,0,1].m_Neighbors[0][1] != null ? Grid[0,0,1].m_Neighbors[0][1] : Grid[0,0,1];	// +X +Z
            Grid[1,1,1] = Grid[1,1,0].m_Neighbors[2][1] != null ? Grid[1,1,0].m_Neighbors[2][1] : Grid[1,1,0];	// +X +Y +Z

            //////////////////////////////////////////////////////////////////////////
            // Create the successive interpolants for trilinear interpolation
            //
            // Assume we interpolate on X first (ISO speed), so we need 4 distinct values
            ImageUtility.float4	tX = new ImageUtility.float4(
                    Math.Max( 0.0f, Math.Min( 1.0f, (EV.x - Grid[0,0,0].m_EV_ISOSpeed) / Math.Max( 1e-6f, Grid[1,0,0].m_EV_ISOSpeed - Grid[0,0,0].m_EV_ISOSpeed) ) ),	// Y=0 Z=0
                    Math.Max( 0.0f, Math.Min( 1.0f, (EV.x - Grid[0,1,0].m_EV_ISOSpeed) / Math.Max( 1e-6f, Grid[1,1,0].m_EV_ISOSpeed - Grid[0,1,0].m_EV_ISOSpeed) ) ),	// Y=1 Z=0
                    Math.Max( 0.0f, Math.Min( 1.0f, (EV.x - Grid[0,0,1].m_EV_ISOSpeed) / Math.Max( 1e-6f, Grid[1,0,1].m_EV_ISOSpeed - Grid[0,0,1].m_EV_ISOSpeed) ) ),	// Y=0 Z=1
                    Math.Max( 0.0f, Math.Min( 1.0f, (EV.x - Grid[0,1,1].m_EV_ISOSpeed) / Math.Max( 1e-6f, Grid[1,1,1].m_EV_ISOSpeed - Grid[0,1,1].m_EV_ISOSpeed) ) )	// Y=1 Z=1
                );
            ImageUtility.float4	rX = new ImageUtility.float4( 1.0f - tX.x, 1.0f - tX.y, 1.0f - tX.z, 1.0f - tX.w );

                // Compute the 4 interpolated shutter speeds & apertures
            ImageUtility.float4	ShutterSpeedsX = new ImageUtility.float4(
                    rX.x * Grid[0,0,0].m_EV_ShutterSpeed + tX.x * Grid[1,0,0].m_EV_ShutterSpeed,	// Y=0 Z=0
                    rX.y * Grid[0,1,0].m_EV_ShutterSpeed + tX.y * Grid[1,1,0].m_EV_ShutterSpeed,	// Y=1 Z=0
                    rX.z * Grid[0,0,1].m_EV_ShutterSpeed + tX.z * Grid[1,0,1].m_EV_ShutterSpeed,	// Y=0 Z=1
                    rX.w * Grid[0,1,1].m_EV_ShutterSpeed + tX.w * Grid[1,1,1].m_EV_ShutterSpeed		// Y=1 Z=1
                );
            ImageUtility.float4	AperturesX = new ImageUtility.float4(
                    rX.x * Grid[0,0,0].m_EV_Aperture + tX.x * Grid[1,0,0].m_EV_Aperture,			// Y=0 Z=0
                    rX.y * Grid[0,1,0].m_EV_Aperture + tX.y * Grid[1,1,0].m_EV_Aperture,			// Y=1 Z=0
                    rX.z * Grid[0,0,1].m_EV_Aperture + tX.z * Grid[1,0,1].m_EV_Aperture,			// Y=0 Z=1
                    rX.w * Grid[0,1,1].m_EV_Aperture + tX.w * Grid[1,1,1].m_EV_Aperture				// Y=1 Z=1
                );

            // Next we interpolate on Y (Shutter speed), so we need 2 distinct values
            ImageUtility.float2	tY = new ImageUtility.float2(
                    Math.Max( 0.0f, Math.Min( 1.0f, (EV.y - ShutterSpeedsX.x) / Math.Max( 1e-6f, ShutterSpeedsX.y - ShutterSpeedsX.x) ) ),	// Z=0
                    Math.Max( 0.0f, Math.Min( 1.0f, (EV.y - ShutterSpeedsX.z) / Math.Max( 1e-6f, ShutterSpeedsX.w - ShutterSpeedsX.z) ) )	// Z=1
                );
            ImageUtility.float2	rY = new ImageUtility.float2( 1.0f - tY.x, 1.0f - tY.y );

                // Compute the 2 apertures
            ImageUtility.float2	AperturesY = new ImageUtility.float2(
                    rY.x * AperturesX.x + tY.x * AperturesX.y,
                    rY.y * AperturesX.z + tY.y * AperturesX.w
                );

            // Finally, we interpolate on Z (Aperture), we need only 1 single value
            float	tZ = Math.Max( 0.0f, Math.Min( 1.0f, (EV.z - AperturesY.x) / Math.Max( 1e-6f, AperturesY.y - AperturesY.x) ) );
            float	rZ = 1.0f - tZ;

            //////////////////////////////////////////////////////////////////////////
            // Create the special camera calibration that is the result of the interpolation of the 8 nearest ones in the grid
            m_InterpolatedCalibration = new CameraCalibration();
            m_InterpolatedCalibration.m_CameraShotInfos.m_ISOSpeed = _ISOSpeed;
            m_InterpolatedCalibration.m_CameraShotInfos.m_ShutterSpeed = _ShutterSpeed;
            m_InterpolatedCalibration.m_CameraShotInfos.m_Aperture = _Aperture;

            for ( int ProbeIndex=0; ProbeIndex < REQUIRED_PROBES_COUNT; ProbeIndex++ )
            {
                CameraCalibration.Probe TargetProbe = m_InterpolatedCalibration.m_Reflectances[ProbeIndex];

                float	L000 = Grid[0,0,0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured;
                float	L100 = Grid[1,0,0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured;
                float	L010 = Grid[0,1,0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured;
                float	L110 = Grid[1,1,0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured;
                float	L001 = Grid[0,0,1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured;
                float	L101 = Grid[1,0,1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured;
                float	L011 = Grid[0,1,1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured;
                float	L111 = Grid[1,1,1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured;

                // Interpolate on X (ISO speed)
                float	L00 = rX.x * L000 + tX.x * L100;
                float	L10 = rX.y * L010 + tX.y * L110;
                float	L01 = rX.z * L001 + tX.z * L101;
                float	L11 = rX.w * L011 + tX.w * L111;

                // Interpolate on Y (shutter speed)
                float	L0 = rY.x * L00 + tY.x * L10;
                float	L1 = rY.y * L01 + tY.y * L11;

                // Interpolate on Z (aperture)
                float	L = rZ * L0 + tZ * L1;

                TargetProbe.m_IsAvailable = true;
                TargetProbe.m_LuminanceMeasured = L;
            }

            // Fill missing values
            m_InterpolatedCalibration.UpdateAllLuminances();

            // Reset white reflectance reference because it was set for another setup
            WhiteReflectanceReference = new ImageUtility.float3( 0, 0, -1 );
        }
Example #20
0
        /// <summary>
        /// Computes the statistical mode of the values
        /// </summary>
        /// <param name="_Values"></param>
        /// <returns></returns>
        private ImageUtility.float3 ComputeMode( ImageUtility.float3[] _Values )
        {
            int		COUNT = _Values.Length;

            // The idea is simply to discretize the set of values and count the ones that are the most represented
            float	Start = _Values[0].y;
            float	End = _Values[_Values.Length-1].y;
            float	IntervalLength = 0.0f, Normalizer = 0.0f;
            if ( Math.Abs( End - Start ) > 1e-6f )
            {
                IntervalLength = (End - Start) / COUNT;
                Normalizer = 1.0f / IntervalLength;
            }

            // Fill bins
            int[]		Bins = new int[COUNT];
            ImageUtility.float3[]	BinsSum = new ImageUtility.float3[COUNT];
            for ( int i=0; i < _Values.Length; i++ )
            {
                int	BinIndex = Math.Min( COUNT-1, (int) Math.Floor( (_Values[i].y - Start) * Normalizer ) );
                Bins[BinIndex]++;
                BinsSum[BinIndex] += _Values[i];
            }

            // Find the one that contains most values (yep, that's the mode!)
            int		MaxValuesCount = 0;
            int		MaxValuesBinIndex = -1;
            for ( int BinIndex=0; BinIndex < COUNT; BinIndex++ )
                if ( Bins[BinIndex] > MaxValuesCount )
                {	// More filled up bin discovered!
                    MaxValuesCount = Bins[BinIndex];
                    MaxValuesBinIndex = BinIndex;
                }

            ImageUtility.float3	ModeXYZ = (1.0f / MaxValuesCount) * BinsSum[MaxValuesBinIndex];
            return ModeXYZ;
        }
Example #21
0
        /// <summary>
        /// Prepares the interpolated calibration table to process the pixels in an image shot with the specified shot infos
        /// </summary>
        /// <param name="_ISOSpeed"></param>
        /// <param name="_ShutterSpeed"></param>
        /// <param name="_Aperture"></param>
        public void     PrepareCalibrationFor(float _ISOSpeed, float _ShutterSpeed, float _Aperture)
        {
            if (m_RootNode == null)
            {
                throw new Exception("Calibration grid hasn't been built: did you provide a valid database path? Does the path contain camera calibration data?");
            }

            if (IsPreparedFor(_ISOSpeed, _ShutterSpeed, _Aperture))
            {
                return;                 // Already prepared!
            }
            //////////////////////////////////////////////////////////////////////////
            // Find the 8 nodes encompassing our values
            // I'm making the delicate assumption that, although the starting node is chosen on the
            //	condition its EV values are strictly inferior to the target we're looking for, all
            //	neighbor nodes should satisfy the condition they're properly placed.
            //
            // This is true for the direct neighbors +X, +Y, +Z that are immediately above target values
            //	but for example, neighbor (+X +Y) may have a very bad aperture value (Z) that may be
            //	above the target aperture...
            //
            // Let's hope the user won't provide too fancy calibrations...
            // (anyway, interpolants are clamped in [0,1] so there's no risk of overshooting)
            //
            ImageUtility.float3 EV;
            GridNode.Convert2EV(_ISOSpeed, _ShutterSpeed, _Aperture, out EV.x, out EV.y, out EV.z);

            // Find the start node
            GridNode StartNode = FindStartNode(EV.x, EV.y, EV.z);

            m_InterpolationStartNode = StartNode;

            // Build the 8 grid nodes from it
            GridNode[,,]    Grid = new GridNode[2, 2, 2];
            Grid[0, 0, 0]        = StartNode;
            Grid[1, 0, 0]        = StartNode.m_Neighbors[0][1] != null ? StartNode.m_Neighbors[0][1] : StartNode;               // +X
            Grid[0, 1, 0]        = StartNode.m_Neighbors[1][1] != null ? StartNode.m_Neighbors[1][1] : StartNode;               // +Y
            Grid[0, 0, 1]        = StartNode.m_Neighbors[2][1] != null ? StartNode.m_Neighbors[2][1] : StartNode;               // +Z
            Grid[1, 1, 0]        = Grid[1, 0, 0].m_Neighbors[1][1] != null ? Grid[1, 0, 0].m_Neighbors[1][1] : Grid[1, 0, 0];   // +X +Y
            Grid[0, 1, 1]        = Grid[0, 1, 0].m_Neighbors[2][1] != null ? Grid[0, 1, 0].m_Neighbors[2][1] : Grid[0, 1, 0];   // +Y +Z
            Grid[1, 0, 1]        = Grid[0, 0, 1].m_Neighbors[0][1] != null ? Grid[0, 0, 1].m_Neighbors[0][1] : Grid[0, 0, 1];   // +X +Z
            Grid[1, 1, 1]        = Grid[1, 1, 0].m_Neighbors[2][1] != null ? Grid[1, 1, 0].m_Neighbors[2][1] : Grid[1, 1, 0];   // +X +Y +Z

            //////////////////////////////////////////////////////////////////////////
            // Create the successive interpolants for trilinear interpolation
            //
            // Assume we interpolate on X first (ISO speed), so we need 4 distinct values
            ImageUtility.float4 tX = new ImageUtility.float4(
                Math.Max(0.0f, Math.Min(1.0f, (EV.x - Grid[0, 0, 0].m_EV_ISOSpeed) / Math.Max(1e-6f, Grid[1, 0, 0].m_EV_ISOSpeed - Grid[0, 0, 0].m_EV_ISOSpeed))),                              // Y=0 Z=0
                Math.Max(0.0f, Math.Min(1.0f, (EV.x - Grid[0, 1, 0].m_EV_ISOSpeed) / Math.Max(1e-6f, Grid[1, 1, 0].m_EV_ISOSpeed - Grid[0, 1, 0].m_EV_ISOSpeed))),                              // Y=1 Z=0
                Math.Max(0.0f, Math.Min(1.0f, (EV.x - Grid[0, 0, 1].m_EV_ISOSpeed) / Math.Max(1e-6f, Grid[1, 0, 1].m_EV_ISOSpeed - Grid[0, 0, 1].m_EV_ISOSpeed))),                              // Y=0 Z=1
                Math.Max(0.0f, Math.Min(1.0f, (EV.x - Grid[0, 1, 1].m_EV_ISOSpeed) / Math.Max(1e-6f, Grid[1, 1, 1].m_EV_ISOSpeed - Grid[0, 1, 1].m_EV_ISOSpeed)))                               // Y=1 Z=1
                );
            ImageUtility.float4 rX = new ImageUtility.float4(1.0f - tX.x, 1.0f - tX.y, 1.0f - tX.z, 1.0f - tX.w);

            // Compute the 4 interpolated shutter speeds & apertures
            ImageUtility.float4 ShutterSpeedsX = new ImageUtility.float4(
                rX.x * Grid[0, 0, 0].m_EV_ShutterSpeed + tX.x * Grid[1, 0, 0].m_EV_ShutterSpeed,                        // Y=0 Z=0
                rX.y * Grid[0, 1, 0].m_EV_ShutterSpeed + tX.y * Grid[1, 1, 0].m_EV_ShutterSpeed,                        // Y=1 Z=0
                rX.z * Grid[0, 0, 1].m_EV_ShutterSpeed + tX.z * Grid[1, 0, 1].m_EV_ShutterSpeed,                        // Y=0 Z=1
                rX.w * Grid[0, 1, 1].m_EV_ShutterSpeed + tX.w * Grid[1, 1, 1].m_EV_ShutterSpeed                         // Y=1 Z=1
                );
            ImageUtility.float4 AperturesX = new ImageUtility.float4(
                rX.x * Grid[0, 0, 0].m_EV_Aperture + tX.x * Grid[1, 0, 0].m_EV_Aperture,                                        // Y=0 Z=0
                rX.y * Grid[0, 1, 0].m_EV_Aperture + tX.y * Grid[1, 1, 0].m_EV_Aperture,                                        // Y=1 Z=0
                rX.z * Grid[0, 0, 1].m_EV_Aperture + tX.z * Grid[1, 0, 1].m_EV_Aperture,                                        // Y=0 Z=1
                rX.w * Grid[0, 1, 1].m_EV_Aperture + tX.w * Grid[1, 1, 1].m_EV_Aperture                                         // Y=1 Z=1
                );

            // Next we interpolate on Y (Shutter speed), so we need 2 distinct values
            ImageUtility.float2 tY = new ImageUtility.float2(
                Math.Max(0.0f, Math.Min(1.0f, (EV.y - ShutterSpeedsX.x) / Math.Max(1e-6f, ShutterSpeedsX.y - ShutterSpeedsX.x))),                               // Z=0
                Math.Max(0.0f, Math.Min(1.0f, (EV.y - ShutterSpeedsX.z) / Math.Max(1e-6f, ShutterSpeedsX.w - ShutterSpeedsX.z)))                                // Z=1
                );
            ImageUtility.float2 rY = new ImageUtility.float2(1.0f - tY.x, 1.0f - tY.y);

            // Compute the 2 apertures
            ImageUtility.float2 AperturesY = new ImageUtility.float2(
                rY.x * AperturesX.x + tY.x * AperturesX.y,
                rY.y * AperturesX.z + tY.y * AperturesX.w
                );

            // Finally, we interpolate on Z (Aperture), we need only 1 single value
            float tZ = Math.Max(0.0f, Math.Min(1.0f, (EV.z - AperturesY.x) / Math.Max(1e-6f, AperturesY.y - AperturesY.x)));
            float rZ = 1.0f - tZ;


            //////////////////////////////////////////////////////////////////////////
            // Create the special camera calibration that is the result of the interpolation of the 8 nearest ones in the grid
            m_InterpolatedCalibration = new CameraCalibration();
            m_InterpolatedCalibration.m_CameraShotInfos.m_ISOSpeed     = _ISOSpeed;
            m_InterpolatedCalibration.m_CameraShotInfos.m_ShutterSpeed = _ShutterSpeed;
            m_InterpolatedCalibration.m_CameraShotInfos.m_Aperture     = _Aperture;

            for (int ProbeIndex = 0; ProbeIndex < REQUIRED_PROBES_COUNT; ProbeIndex++)
            {
                CameraCalibration.Probe TargetProbe = m_InterpolatedCalibration.m_Reflectances[ProbeIndex];

                float L000 = Grid[0, 0, 0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured;
                float L100 = Grid[1, 0, 0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured;
                float L010 = Grid[0, 1, 0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured;
                float L110 = Grid[1, 1, 0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured;
                float L001 = Grid[0, 0, 1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured;
                float L101 = Grid[1, 0, 1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured;
                float L011 = Grid[0, 1, 1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured;
                float L111 = Grid[1, 1, 1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured;

                // Interpolate on X (ISO speed)
                float L00 = rX.x * L000 + tX.x * L100;
                float L10 = rX.y * L010 + tX.y * L110;
                float L01 = rX.z * L001 + tX.z * L101;
                float L11 = rX.w * L011 + tX.w * L111;

                // Interpolate on Y (shutter speed)
                float L0 = rY.x * L00 + tY.x * L10;
                float L1 = rY.y * L01 + tY.y * L11;

                // Interpolate on Z (aperture)
                float L = rZ * L0 + tZ * L1;

                TargetProbe.m_IsAvailable       = true;
                TargetProbe.m_LuminanceMeasured = L;
            }

            // Fill missing values
            m_InterpolatedCalibration.UpdateAllLuminances();

            // Reset white reflectance reference because it was set for another setup
            WhiteReflectanceReference = new ImageUtility.float3(0, 0, -1);
        }