I am exporting a scene to OpenEXR multichannel (with no compression and FULL format) and read it back using Python. Here is the code

exrFile = OpenEXR.InputFile('fallroad_0001.exr')
header = exrFile.header()
dw = header['dataWindow']
pt = Imath.PixelType(Imath.PixelType.FLOAT)
size = (dw.max.x - dw.min.x + 1, dw.max.y - dw.min.y + 1)

cc_r = np.fromstring(exrFile.channel('RenderLayer.Combined.R', pt), dtype=np.float32)
cc_g = np.fromstring(exrFile.channel('RenderLayer.Combined.G', pt), dtype=np.float32)
cc_b = np.fromstring(exrFile.channel('RenderLayer.Combined.B', pt), dtype=np.float32)
cc_r.shape = cc_g.shape = cc_b.shape = (size[1], size[0])
cc = np.dstack((cc_r, cc_g, cc_b))

I understand that an OpenEXR file contain a 'raw' value of the pixel (in high dynamic range), so the pixel values are not in the image standard range [0, 1]. Indeed, here is the range of each channel

[(0.0, 270.6739501953125), (0.0, 221.4493865966797), (0.0, 106.66129302978516)]

Now I am having problems to convert it to the image that Render shows (and export as png), which ranges in [0, 255] or [0, 1]. Scaling [min, max] to [0, 1] or cut off at 1 (values that are greater than 1 become 1) does not work.

I include here the images that I have hand-on. From left to right: image with raw exported float values, image with value cut-off at 1 (all > 1 become 1) and image rendered by Blender

enter image description here


4 Answers 4


The values are scene referred.

You require a view transform to map to the display referred range.

If you choose to use the sub-optimal sRGB EOTF, you are losing a heck of a lot of dynamic range. You can use a much wider dynamic range for the scene referred to display referred transform if you wish. Ultimately it is always a creative decision.

See Render with a wider dynamic range in cycles to produce photorealistic looking images for more information.


After hours of endless Googling around, I have figured out an answer for the question. Any other comments would also be welcomed.

The problem is basically because the way Blender store color value interally (and in OpenEXR format) is using linear space, while PNG and other image format is Display Space, or sRGB (ref http://blender-manual-i18n.readthedocs.io/ja/latest/render/post_process/cm_and_exposure.html).

So in other words, my problem is to convert color values from linear space to sRGB, which is discussed at length here https://stackoverflow.com/questions/12524623/what-are-the-practical-differences-when-working-with-colors-in-a-linear-vs-a-no. TL;DR, the formula is

float linear = do_processing();
float s;
if (linear <= 0.0031308) s = linear * 12.92;
else s = 1.055 * pow(linear, 1.0/2.4) - 0.055; ( Edited: The previous version is -0.55 )

With just one small twist, to cut off at 1 any resulted values that higher than 1, and I got the image that I want.

  • 2
    $\begingroup$ By cutting values higher than one you are throwing away information on the highlights. That sort of defeats the whole purpose of using EXRs on the first place... A different approach would be to remap your (linear scene referred values so that they fit within the confines of the display referred 0-1 range. You can do so with the CDL node. Read: blender.stackexchange.com/questions/55231/… $\endgroup$
    – user1853
    Commented Oct 18, 2016 at 22:32
  • $\begingroup$ Thanks for your comment, the CDL maybe something that is useful, I'll look into it. Just to make clear, my question was to get back the image that Blender shows when render (and exported as png) since I got a mismatch when exported as png and openexr. Yet, I understand that we've got more than just 1 way to map between linear space and display space. $\endgroup$
    – AugLe
    Commented Oct 19, 2016 at 8:11
  • $\begingroup$ Both "linear" and "display" are not spaces. To get a perfect 1:1 with the default view transform (which is the sRGB EOTF) simply apply the transform via OpenColorIO's ociolutimage, or via the Python stubs to transform the scene referred values. $\endgroup$
    – troy_s
    Commented Oct 19, 2016 at 18:39

Bumped by the same question (the need to easily/automatically convert multi-layer EXR to sRGB PNG images), i wrote another py-script for this task. May be someone find it useful too.

This script automatically exports layers from multi-layered EXR into set of PNG images. With proper Linear->sRGB conversion (and keeping alpha where appropriate) and layer naming. Most of all - it does not use OpenEXR python bindings (it`s a hell to setup them in Windows environment), it just utilizes oiiotool.exe from OpenImageIO suite



Python version of AugLe's answer:

def norm(val):
    return val * 12.92 if val <= 0.0031308 else 1.055 * val**(1.0/2.4) - 0.055
norm = numpy.vectorize(norm)

def openEXR_RGB_to_mat(path):
    image = OpenEXR.InputFile(path)
    x, y = get_exr_dim(image)
    im = numpy.zeros((x,y,3))

    im[:,:,0] = numpy.frombuffer(image.channel('R'), dtype=numpy.float32).reshape((x,y))
    im[:,:,1] = numpy.frombuffer(image.channel('G'), dtype=numpy.float32).reshape((x,y))
    im[:,:,2] = numpy.frombuffer(image.channel('B'), dtype=numpy.float32).reshape((x,y))

    im = numpy.clip(im, 0, 1)
    return norm(im)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .