Why normalized EXR renderings of surface Normals look different than PNG renderings? I render surface Normals using Cycles and store the results in EXR format. The values in the EXR format are within the range of [-1, 1] meaning that I cannot directly store the values in a PNG file. Here's how I read the .exr files and store store the values in a numpy array:

import OpenEXR, array, Imath
exrFile = OpenEXR.InputFile('normal.exr')
FLOAT = Imath.PixelType(Imath.PixelType.FLOAT)
(RGB) = [array.array('f', exrFile.channel(Chan, FLOAT)).tolist() for Chan in ("R", "G", "B") ]
normalNPArray = np.array(RGB)
normalNPArray = normalNPArray.reshape((3, resolution, resolution))


I normalize the values as follow and store the surface Normal as a PNG image:

normalNPArray += 1
normalNPArray /= 2
normalNPArray *= 255
normalNPArray = normalNPArray.astype(np.uint8)
im = Image.fromarray(normalNPArray.transpose(1, 2, 0), mode='RGB')
im.save('temp.png')


Here's what I get after opening the stored image:


If I simply ignore the values below zero (remove/comment the first two lines) I get the following:


However, if I directly store the surface Normal as a PNG image instead of EXR I get the following:



I wonder, how can I get a visualization that looks like the PNG rendering? How does Blender normalize the values?
It turns out Numpy does not automatically set the negative values to 0 when casting the array type to uint8. For instance, suppose I have a numpy array as follow:

array([  46.73017823, -250.31618571, -247.44416527,   97.52841554,
       -204.92988386,  191.09452493,  103.15708521,  -86.10470495,
         46.211924  , -195.30653599,  240.44499889, -169.42729244,
        210.44996545, -182.04892973, -166.20581924, -221.11524425,
        164.79367242, -199.80888341,   94.1786936 ,   43.45477102])


I was expecting that doing normalNPArray = normalNPArray.astype(np.uint8) will automatically set the negative values to 0 since uint8 does not naturally support negative values. However, casting the array as np.uint8 gives me the following:

array([ 46,   6,   9,  97,  52, 191, 103, 170,  46,  61, 240,  87, 210,
            74,  90,  35, 164,  57,  94,  43], dtype=uint8)


Doing normalNPArray[normalNPArray &lt; 0] = 0 and then casting the array to np.uint8 will resolve the issue.