Click here to Skip to main content
15,887,596 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
I am using the following code to convert a Bitmap to Complex and vice versa.

Even though those were directly copied from Accord.NET framework, while testing these static methods, I have discovered that, repeated use of these static methods cause 'data-loss'.



As a result, the end output/result becomes distorted.[^]

C#
public partial class ImageDataConverter
{
    #region private static Complex[,] FromBitmapData(BitmapData bmpData)
    private static Complex[,] ToComplex(BitmapData bmpData)
    {
        Complex[,] comp = null;

        if (bmpData.PixelFormat == PixelFormat.Format8bppIndexed)
        {
            int width = bmpData.Width;
            int height = bmpData.Height;
            int offset = bmpData.Stride - (width * 1);//1 === 1 byte per pixel.

            if ((!Tools.IsPowerOf2(width)) || (!Tools.IsPowerOf2(height)))
            {
                throw new Exception("Imager width and height should be n of 2.");
            }

            comp = new Complex[width, height];

            unsafe
            {
                byte* src = (byte*)bmpData.Scan0.ToPointer();

                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++, src++)
                    {
                        comp[y, x] = new Complex((float)*src / 255,
                                                    comp[y, x].Imaginary);
                    }
                    src += offset;
                }
            }
        }
        else
        {
            throw new Exception("EightBppIndexedImageRequired");
        }

        return comp;
    }
    #endregion

    public static Complex[,] ToComplex(Bitmap bmp)
    {
        Complex[,] comp = null;

        if (bmp.PixelFormat == PixelFormat.Format8bppIndexed)
        {
            BitmapData bmpData = bmp.LockBits(  new Rectangle(0, 0, bmp.Width, bmp.Height),
                                                ImageLockMode.ReadOnly,
                                                PixelFormat.Format8bppIndexed);
            try
            {
                comp = ToComplex(bmpData);
            }
            finally
            {
                bmp.UnlockBits(bmpData);
            }
        }
        else
        {
            throw new Exception("EightBppIndexedImageRequired");
        }

        return comp;
    }

    public static Bitmap ToBitmap(Complex[,] image, bool fourierTransformed)
    {
        int width = image.GetLength(0);
        int height = image.GetLength(1);

        Bitmap bmp = Imager.CreateGrayscaleImage(width, height);

        BitmapData bmpData = bmp.LockBits(
            new Rectangle(0, 0, width, height),
            ImageLockMode.ReadWrite,
            PixelFormat.Format8bppIndexed);

        int offset = bmpData.Stride - width;
        double scale = (fourierTransformed) ? Math.Sqrt(width * height) : 1;

        unsafe
        {
            byte* address = (byte*)bmpData.Scan0.ToPointer();

            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++, address++)
                {
                    double min = System.Math.Min(255, image[y, x].Magnitude * scale * 255);

                    *address = (byte)System.Math.Max(0, min);
                }
                address += offset;
            }
        }

        bmp.UnlockBits(bmpData);

        return bmp;
    }
}


As you can see, FFT is working correctly, but, I-FFT isn't.

That is because bitmap to complex and vice versa isn't working as expected.

What could be done to correct the ToComplex() and ToBitmap() functions so that they don't loss data?

What I have tried:

C#
public partial class ImageDataConverter
{
    #region private static Complex[,] FromBitmapData(BitmapData bmpData)
    private static Complex[,] ToComplex(BitmapData bmpData)
    {
        Complex[,] comp = null;

        if (bmpData.PixelFormat == PixelFormat.Format8bppIndexed)
        {
            int width = bmpData.Width;
            int height = bmpData.Height;
            int offset = bmpData.Stride - (width * 1);//1 === 1 byte per pixel.

            if ((!Tools.IsPowerOf2(width)) || (!Tools.IsPowerOf2(height)))
            {
                throw new Exception("Imager width and height should be n of 2.");
            }

            comp = new Complex[width, height];

            unsafe
            {
                byte* src = (byte*)bmpData.Scan0.ToPointer();

                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++, src++)
                    {
                        comp[y, x] = new Complex((float)*src / 255,
                                                    comp[y, x].Imaginary);
                    }
                    src += offset;
                }
            }
        }
        else
        {
            throw new Exception("EightBppIndexedImageRequired");
        }

        return comp;
    }
    #endregion

    public static Complex[,] ToComplex(Bitmap bmp)
    {
        Complex[,] comp = null;

        if (bmp.PixelFormat == PixelFormat.Format8bppIndexed)
        {
            BitmapData bmpData = bmp.LockBits(  new Rectangle(0, 0, bmp.Width, bmp.Height),
                                                ImageLockMode.ReadOnly,
                                                PixelFormat.Format8bppIndexed);
            try
            {
                comp = ToComplex(bmpData);
            }
            finally
            {
                bmp.UnlockBits(bmpData);
            }
        }
        else
        {
            throw new Exception("EightBppIndexedImageRequired");
        }

        return comp;
    }

    public static Bitmap ToBitmap(Complex[,] image, bool fourierTransformed)
    {
        int width = image.GetLength(0);
        int height = image.GetLength(1);

        Bitmap bmp = Imager.CreateGrayscaleImage(width, height);

        BitmapData bmpData = bmp.LockBits(
            new Rectangle(0, 0, width, height),
            ImageLockMode.ReadWrite,
            PixelFormat.Format8bppIndexed);

        int offset = bmpData.Stride - width;
        double scale = (fourierTransformed) ? Math.Sqrt(width * height) : 1;

        unsafe
        {
            byte* address = (byte*)bmpData.Scan0.ToPointer();

            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++, address++)
                {
                    double min = System.Math.Min(255, image[y, x].Magnitude * scale * 255);

                    *address = (byte)System.Math.Max(0, min);
                }
                address += offset;
            }
        }

        bmp.UnlockBits(bmpData);

        return bmp;
    }
}
Posted
Updated 23-Jul-16 23:12pm
v3
Comments
Richard MacCutchan 24-Jul-16 3:07am    
Without more information it is anyone's guess. You need to explain where the data is being lost.
[no name] 24-Jul-16 4:51am    

There are some problems when assigning and reading the complex values.

Here you convert a byte (the 8bpp pixel value) to float, scale it, and assign it to the real part of the complex number:
C#
comp[y, x] = new Complex((float)*src / 255, comp[y, x].Imaginary);

But the imaginary part is initialised with the actual value which is not defined at this point! Not critical but worth noting: Why do you cast to float instead of double and scale by 255 instead of 256?

Here you get the scaled magnitude back and write it to the image.
C#
double min = System.Math.Min(255, image[y, x].Magnitude * scale * 255);
*address = (byte)System.Math.Max(0, min);

Using max() here is not necessary because all values from the above expression are always positive.

You did not tell us about the scale of the loss of precision. But I assume it is mainly sourced by setting the imaginary part to an undefined value.

Another source is probably the casting of floating point values to byte. This is done without rounding so that digits after the decimal point are cut off. To use nearest rounding add 0.5 before casting (adding works here because the casted value is always positive). So you might try:
C#
*address = (byte)System.Math.Min(255, 0.5 + image[y, x].Magnitude * scale * 255));


Finally there is always a small loss of precision when performing extensive floating point operations.

In your case you are introducing the first small error when scaling the input value by dividing by 255. When using 256 there would be no error. The reason is that dividing by 255 might produce a result that can not be exactly represented by floating point numbers. Dividing by 256 will produce no error because floating point
numbers use internally a 2-based exponent and dividing by 256 just shifts this exponent.
 
Share this answer
 
Comments
[no name] 24-Jul-16 5:48am    
I can follow your argumentation at the end of the answer. But the mathematical correct value is 255 or not? And does scaling to 256 instead of 255 not finally results in a bigger error?
Jochen Arndt 24-Jul-16 6:07am    
It is just a scaling factor. Using 255 would get a max. value of 1. Using 256 scales to a max. of 0.99609375. It depends on how the data are used. Only when there are requirements that the value must be scaled to max. 1, 255 must be used.
[no name] 24-Jul-16 6:11am    
Ah ok, thanks. I did not dive in very deep, I just assumed the "255" has something to do with the final RGB. But yes for IFFT of course it does make a lot of sense to work with 256 :) +5
Jochen Arndt 24-Jul-16 6:23am    
Thank you.
You should learn to use the debugger as soon as possible. Rather than guessing what your code is doing, It is time to see your code executing and ensuring that it does what you expect.

The debugger allow you to follow the execution line by line, inspect variables and you will see that there is a point where it stop doing what you expect.
Debugger - Wikipedia, the free encyclopedia[^]
Mastering Debugging in Visual Studio 2010 - A Beginner's Guide[^]

With the debugger, you may discover that some variable are not cleaned and that some data of previous uses are still here on next use.
Quote:

Bro, I would like to humbly tell you that, this is not about debugging.
This is about algorithm.

Code is a translation of algorithm, so a problem in code imply a problem in algorithm or in translation, your choice. And a problem in algorithm imply a problem in code.
Quote:

I have copied the code from an already existing and well-tested framework.

This sentence imply there is no error in algorithm or in code.
 
Share this answer
 
v4
Comments
[no name] 24-Jul-16 0:40am    
Bro, I would like to humbly tell you that, this is not about debugging.

This is about algorithm.

I have copied the code from an already existing and well-tested framework.
[no name] 24-Jul-16 4:57am    
> This sentence imply there is no error in algorithm or in code.

Dear Bro,

I don't have any interest to argue with you.

That sentence imply that,

(1) that code should have been working perfectly but isn't working.

(2) That code needs to be modified, but, I don't have understanding of the inner logic of the code. So, I am unable to do that.

(3) The problem is much bigger that is looks. The obtained code needs to be thoroughly overhauled.



Patrice T 24-Jul-16 5:11am    
Why not ask the author of the framework ? or user's forum ?
At least they are already familiar with it.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900