Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dataset import: Track progress of data texture and gradient texture generation #164

Closed
mlavik1 opened this issue Mar 24, 2023 · 5 comments
Closed

Comments

@mlavik1
Copy link
Owner

mlavik1 commented Mar 24, 2023

See suggestion by @SitronX : #162 (comment)

@SitronX
Copy link
Contributor

SitronX commented Apr 4, 2023

I sadly do not have a time for creating the proper PR for this, but here are the updated methods from my project which reports whole percent updates. The correct progressHandler just need to be passed to these methods. Hope it might help someone in the future trying to add it here.

CreateTextureInternalAsync


private async Task<Texture3D> CreateTextureInternalAsync(ProgressHandler progressHandler)               
{
    Debug.Log("Async texture generation. Hold on.");

    Texture3D.allowThreadedTextureCreation = true;
    TextureFormat texformat = SystemInfo.SupportsTextureFormat(TextureFormat.RHalf) ? TextureFormat.RHalf : TextureFormat.RFloat;

    float minValue = 0;
    float maxValue = 0;
    float maxRange = 0;

    await Task.Run(() =>
    {
        minValue = GetMinDataValue(progressHandler);
        maxValue = GetMaxDataValue(progressHandler);
        maxRange = maxValue - minValue;
    });

    bool isHalfFloat = texformat == TextureFormat.RHalf;

    try
    {
        if (isHalfFloat)
        {
            NativeArray<ushort> pixelBytes = default;

            await Task.Run(() => {
                pixelBytes = new NativeArray<ushort>(data.Length, Allocator.TempJob);

                int onePercentVal = data.Length / 100;
                int percentCounter = 0;
                for (int iData = 0; iData < data.Length; iData++)
                {
                    pixelBytes[iData] = Mathf.FloatToHalf((float)(data[iData] - minValue) / maxRange);
                    if(percentCounter>=onePercentVal)
                    {
                        progressHandler.ReportProgress(iData, data.Length, "Creating Data...");
                        percentCounter = 0;
                    }
                    percentCounter++;
                }
            });

            Texture3D texture = new Texture3D(dimX, dimY, dimZ, texformat, false);       
            texture.wrapMode = TextureWrapMode.Clamp;
            texture.SetPixelData(pixelBytes, 0);
            texture.Apply();

            pixelBytes.Dispose();
        }
        else
        {
            NativeArray<float> pixelBytes = default;

            int onePercentVal = data.Length / 100;
            int percentCounter = 0;

            await Task.Run(() => {
                pixelBytes = new NativeArray<float>(data.Length, Allocator.TempJob);
                for (int iData = 0; iData < data.Length; iData++)
                {
                    pixelBytes[iData] = (float)(data[iData] - minValue) / maxRange;

                    if (percentCounter >= onePercentVal)
                    {
                        progressHandler.ReportProgress(iData, data.Length, "Creating Data...");
                        percentCounter = 0;
                    }
                    percentCounter++;
                }
            });

            Texture3D texture = new Texture3D(dimX, dimY, dimZ, texformat, false);              
            texture.wrapMode = TextureWrapMode.Clamp;
            texture.SetPixelData(pixelBytes, 0);
            texture.Apply();

            pixelBytes.Dispose();
        }
    }
    catch (OutOfMemoryException)
    {
        Texture3D texture = new Texture3D(dimX, dimY, dimZ, texformat, false);          
        texture.wrapMode = TextureWrapMode.Clamp;


        Debug.LogWarning("Out of memory when creating texture. Using fallback method.");
        for (int x = 0; x < dimX; x++)
            for (int y = 0; y < dimY; y++)
                for (int z = 0; z < dimZ; z++)
                    texture.SetPixel(x, y, z, new Color((float)(data[x + y * dimX + z * (dimX * dimY)] - minValue) / maxRange, 0.0f, 0.0f, 0.0f));

        texture.Apply();
    }
    Debug.Log("Texture generation done.");
    return texture;
}

CreateGradientTextureInternal

private async Task<Texture3D> CreateGradientTextureInternalAsync(ProgressHandler progressHandler)
{
    Debug.Log("Async gradient generation. Hold on.");

    Texture3D.allowThreadedTextureCreation = true;
    TextureFormat texformat = SystemInfo.SupportsTextureFormat(TextureFormat.RGBAHalf) ? TextureFormat.RGBAHalf : TextureFormat.RGBAFloat;

    float minValue = 0;
    float maxValue = 0;
    float maxRange = 0;
    Color[] cols = null;

    await Task.Run(() => {

        minValue = GetMinDataValue(progressHandler);
        maxValue = GetMaxDataValue(progressHandler);
        maxRange = maxValue - minValue;
    });

    try
    {
        await Task.Run(() => cols = new Color[data.Length]);
    }
    catch (OutOfMemoryException)
    {
        Texture3D textureTmp = new Texture3D(dimX, dimY, dimZ, texformat, false);
        textureTmp.wrapMode = TextureWrapMode.Clamp;

        int totalCount = dimX * dimY * dimZ;
        int onePercentVal = totalCount / 100;
        int percentCounter = 0;
        int overall = 0;

        for (int x = 0; x < dimX; x++)
        {
            for (int y = 0; y < dimY; y++)
            {
                for (int z = 0; z < dimZ; z++)
                {
                    int iData = x + y * dimX + z * (dimX * dimY);
                    Vector3 grad = GetGrad(x, y, z, minValue, maxRange);

                    textureTmp.SetPixel(x, y, z, new Color(grad.x, grad.y, grad.z, (float)(data[iData] - minValue) / maxRange));

                    if (percentCounter >= onePercentVal)
                    {
                        progressHandler.ReportProgress(overall, totalCount, "Creating Gradient...");
                        percentCounter = 0;
                    }
                    percentCounter++;
                    overall++;
                }
            }
        }
        textureTmp.Apply();

        Debug.Log("Gradient gereneration done.");
        return textureTmp;
    }

    await Task.Run(() => {

        int totalCount = dimX * dimY * dimZ;
        int onePercentVal = totalCount / 100;
        int percentCounter = 0;
        int overall = 0;

        for (int x = 0; x < dimX; x++)
        {
            for (int y = 0; y < dimY; y++)
            {
                for (int z = 0; z < dimZ; z++)
                {
                    int iData = x + y * dimX + z * (dimX * dimY);
                    Vector3 grad = GetGrad(x, y, z, minValue, maxRange);

                    cols[iData] = new Color(grad.x, grad.y, grad.z, (float)(data[iData] - minValue) / maxRange);

                    if (percentCounter >= onePercentVal)
                    {
                        progressHandler.ReportProgress(overall, totalCount, "Creating Gradient...");
                        percentCounter = 0;
                    }
                    percentCounter++;
                    overall++;
                }
            }
        }
    });

    Texture3D texture = new Texture3D(dimX, dimY, dimZ, texformat, false);
    texture.wrapMode = TextureWrapMode.Clamp;
    texture.SetPixels(cols);
    texture.Apply();

    Debug.Log("Gradient gereneration done.");
    return texture;
}

GetMinDataValue

public float GetMinDataValue(ProgressHandler progressHandler)
{
    if (minDataValue == float.MaxValue)
        CalculateValueBounds(progressHandler);
    return minDataValue;
}

GetMaxDataValue

public float GetMaxDataValue(ProgressHandler progressHandler)
{
    if (maxDataValue == float.MinValue)
        CalculateValueBounds(progressHandler);
    return maxDataValue;
}

CalculateValueBounds

private void CalculateValueBounds(ProgressHandler progressHandler)
{
    minDataValue = float.MaxValue;
    maxDataValue = float.MinValue;

    if (data != null)
    {
        int totalCount=dimX * dimY * dimZ;
        int onePercent=totalCount/100;
        int percentCounter = 0;
        for (int i = 0; i < totalCount; i++)
        {
            float val = data[i];
            minDataValue = Mathf.Min(minDataValue, val);
            maxDataValue = Mathf.Max(maxDataValue, val);

            if (percentCounter >= onePercent)
            {
                progressHandler.ReportProgress(i, totalCount, "Calculating Boundaries...");
                percentCounter = 0;
            }
            percentCounter++;
        }
    }
}

@mlavik1
Copy link
Owner Author

mlavik1 commented Apr 25, 2023

Oh, I never noticed that you posted this here. Thanks @SitronX for sharing 😁
This is what I was thinking of working on next!
Your code looks good, so I'll probably do it much similar to how you did it - though I think maybe I won't add progress reporting inside the loop in CalculateValueBounds since it should be pretty fast, and progress reporting would potentially make much slower - but I'll profile first to be sure :)

By the way, I think there's a small bug there, in case you didn't notice: You need to call StartStage and EndStage when reporting different stages of the import, such as before CalculateValueBounds. If not, the percentage will go from 0% to 100% before everything is done, and then back to 0% again.
Maybe I should change the ProgressHandler class to allow using a different syntax for this, so you can do:

using (ProgressHandler calcBoundsProgress = progressHandler.Fork(0.1f))
   // use calcBoundsProgress as before

instead of having to manually call StartStage and EndStage, to make this easier to get right.

@SitronX
Copy link
Contributor

SitronX commented Apr 25, 2023

Hello @mlavik1,
oh i think you just dont have large enough dataset to notice the slowness of CalculateValueBounds :D Having 512x512x730 dataset, it takes around 13 seconds, which is a lot.

By the way, I think there's a small bug there, in case you didn't notice: You need to call StartStage and EndStage when reporting

Ehhm, you are probably right, i made some modification in my project and used it kinda differently (also due to AR/VR nature), so the bug was a feature for me :D So ye it is possible there are some mistakes when translated here. In my project i used it with numbering the stages like you see here (this is not the 512x512x730 dataset :D)

Loading.mp4

@mlavik1
Copy link
Owner Author

mlavik1 commented Apr 25, 2023

oh i think you just dont have large enough dataset to notice the slowness of CalculateValueBounds :D Having 512x512x730 dataset, it takes around 13 seconds, which is a lot.

Ok, that's quite big for sure 😁 That gives 190 million iterations, so I guess that makes sense! Though, does it take as much as 19 seconds if you remove the progress reporting? I suspect that might be more expensive than the actual calculations.

Ehhm, you are probably right, i made some modification in my project and used it kinda differently (also due to AR/VR nature), so the bug was a feature for me :D So ye it is possible there are some mistakes when translated here. In my project i used it with numbering the stages like you see here (this is not the 512x512x730 dataset :D)

Oh, right! That's definitely a use case that I need to support. As I've designed it now, you can only get the total progress value.. I'll add a currentStageProgress field to it then :)

By the way, I love seeing this in AR 😁 I'll really need to try out your project sometime soon. I don't have an AR headset, but I'll try with the Oculus headset I'm borrowing from my friend :D

And mentioning VR/AR: If you were having performance issues when enabling lighting and cubic interpolation (render settings) in VR/AR, then you might want to merge in this fix: #178

@SitronX
Copy link
Contributor

SitronX commented Apr 25, 2023

And mentioning VR/AR: If you were having performance issues when enabling lighting and cubic interpolation (render settings) in VR/AR, then you might want to merge in this fix:

Ahh that is cool, thanks for this notification.

Though, does it take as much as 19 seconds if you remove the progress reporting? I suspect that might be more expensive than the actual calculations.

I just tested it and the difference is actually really negligable, it is like 11 seconds when i removed it instead of 13

By the way, I love seeing this in AR 😁

Actually this is a VR version just with added skybox :D But if someone has a hololens 2 device, the view you probably imagined is possible :D

I'll really need to try out your project sometime soon. I don't have an AR headset, but I'll try with the Oculus headset I'm borrowing from my friend :D

Nice, thanks! :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants