Nodes/comfyui-exr-plate/Load EXR Plate (linear)
ComfyUI Node

Load EXR Plate (linear)

Plate sequences, exposure, and why your preview is dark

By Samuelsujan·Created 4 days ago·Updated 4 days ago· 7
Load EXR Plate (linear)
    • image
    • alpha
    • frame_count
    • fps
    • video_info
    path
    first_frame1
    last_frame0
    fps25.00
    custom_width0
    custom_height0
    frame_load_cap0
    skip_first_frames0
    select_every_nth1
    colorspace
    exposure0.0
    clamp_outputtrue

    Every diffusion or video model in ComfyUI wants an sRGB-ish image squeezed into 0–1. Your plate is a scene-linear EXR sequence with highlights well past 1 and a #### sitting in the filename. Core's Load Image won't even open it - it goes through PIL, and PIL doesn't read OpenEXR. So if you're feeding plate into a generative model, you need a loader that understands both halves of that problem at once: the sequence, and the colorspace.

    That's this node. It's the anchor of Samuelsujan's EXR Plate pack (written by a Nuke compositor, Samuelsujan), and the pack's premise is one sentence from its README: generative models want sRGB in the 0–1 range; your plates live in scene-linear EXR. The number that makes it concrete: linear mid-grey 0.18 is sRGB 0.46. Treat gamma-encoded values as linear light and every merge, add and gain in your comp is wrong - which is why the transforms in this pack are exact and invertible rather than a slider.

    How it works

    Reading is OpenCV, not PIL: cv2.imread(..., IMREAD_UNCHANGED | IMREAD_ANYDEPTH) at float32, BGR flipped to RGB, with the alpha channel pulled out as a separate matte. One-channel plates get replicated to three channels; no alpha means a full-opaque matte.

    path takes a single .exr, a sequence with #### or %04d in it, or just a folder - a bare directory gets globbed for every .exr inside, sorted. A relative path resolves against ComfyUI's input directory, which is what makes the in-node "choose exr sequence to upload" button work: it uploads your EXRs into an exrplate_<timestamp> subfolder there and points path at it.

    The order is fixed: resolve the sequence, then skip_first_frames, then select_every_nth, then frame_load_cap. Frames go into one preallocated tensor rather than being stacked at the end, because stacking doubles peak RAM and that's what kills long 2K loads.

    Then per frame: exposure in stops (a 2^stops gain, applied in linear, before any conversion), resize, the colorspace transform, and clamp_output if it's on.

    The inputs that actually matter

    • path - the one you type or get pushed in. ####, %04d or a folder.
    • colorspace - seven options, and the first, keep linear (no conversion), is what a compositor wants. Use linear -> sRGB when you're handing the plate to a diffusion or video model. linear -> Reinhard (HDR-safe) is the interesting one: it squeezes [0, infinity) into [0,1) so a plate with superwhites survives the model and comes back exactly with the inverse.
    • clamp_output - on by default, because most models need it. If you're doing a linear roundtrip with nothing in between, turn it off, or you've silently clipped every highlight above 1.
    • first_frame / last_frame - set the range explicitly and a missing frame is an error (good: you want to know about a gap in the render). Leave last_frame at 0 and it auto-globs whatever exists on disk.
    • exposure - ±10 stops in the linear domain. This is the honest way to lift a dark plate; doing it after the sRGB encode is not the same operation.

    Outputs: image (the batch), alpha as a MASK, frame_count as an INT, fps as a FLOAT, and video_info, a VHS-shaped dict that Video Combine understands if you have VideoHelperSuite installed. Wire fps into your video node - EXRs carry no frame rate, so this just passes your widget value along, at an effective fps / select_every_nth (every second frame of a 25 fps sequence plays back at 12.5, which is right).

    Check one thing before wiring alpha into a mask chain: core Load Image hands you the matte inverted (1 - alpha, white = transparent), while this node hands you the channel straight (white = opaque). If your matte comes out backwards, that's why.

    The preview, and the dark plate

    The node draws its own preview: a server-side endpoint decodes the first frame, applies exposure and colorspace, and serves it as a PNG. Press the ▶ overlay and it caches the first 300 frames at 640px as JPEGs and loops them at your fps; change path, colorspace or exposure and it refreshes instantly. That feedback loop is the nicest part of the pack.

    It's also where the most common question comes from - "why is my plate dark?" Because it is. A correctly-exposed plate in true linear looks dark and flat on an sRGB monitor. Switch the colorspace widget to linear -> sRGB and look again. Nothing in ComfyUI applies a display transform for you, which is why a Preview Image on a linear tensor looks the same way.

    Installing it

    ComfyUI Manager, search EXR Plate (listed as Samuelsujan's EXR Plate), or:

    cd ComfyUI/custom_nodes
    git clone https://github.com/Samuelsujan/comfyui-exr-plate
    python -m pip install -r comfyui-exr-plate/requirements.txt   # opencv-python>=4.6, numpy
    

    Restart ComfyUI and hard-refresh the browser (Ctrl+Shift+R) - the preview, playback and Browse bits are JS served from the pack's web/ folder, and a cached frontend gives you a node with no picture. Nodes appear under Add Node → EXR Plate. Custom nodes don't run on Comfy Cloud, so this is a local-install node.

    Where people get burned

    • Blank/failed read with EXR files. OpenCV only handles EXR if OPENCV_IO_ENABLE_OPENEXR=1 is set before the library initializes. The pack sets it at import, but if another node pack already loaded OpenCV, that's too late. On OpenCV failed to read: ..., set it before ComfyUI starts:

      OPENCV_IO_ENABLE_OPENEXR=1 python main.py
    • Channels and shapes it won't take. The reader handles 1, 3 or 4 channels; a cryptomatte or multi-AOV beauty with eight channels errors out (Unsupported channel count) - go through OpenImageIO (comfy_oiio) for that. And the batch has to be uniform: one wrong-sized frame in the sequence gives you a Frame size mismatch instead of a silently letterboxed render.

    • RAM. A 1920×1080 float32 frame is ~25 MB in the batch, so 200 frames is ~5 GB of system RAM before anything touches the model. Use frame_load_cap and work in chunks.

    • The 8-bit reality check. Loading an EXR doesn't create dynamic range that wasn't in the pixels. This node preserves and converts what's there - it can't rescue a plate that already went through an 8-bit PNG upstream.

    CategoryEXR Plate

    Inputs (12)

    NameTypeDefaultDescription
    pathSTRINGSingle .exr, or sequence with #### / %04d. e.g. /plates/END_130.####.exr
    first_frameINT10–999999
    last_frameINT00–9999990 = auto (glob all frames, or single file)
    fpsFLOAT25.000.1–240Passthrough only (EXRs carry no rate) — wire the fps output into Video Combine etc.
    custom_widthINT00–163840 = source width. Set one of width/height to keep aspect.
    custom_heightINT00–163840 = source height.
    frame_load_capINT00–999999Max frames to load after skip/nth. 0 = all.
    skip_first_framesINT00–999999
    select_every_nthINT11–1000
    colorspaceCOMBO7 options: keep linear (no conversion), linear -> sRGB, sRGB -> linear, linear -> Rec.709, Rec.709 -> linear, linear -> Reinhard (HDR-safe), +1
    exposureFLOAT0.0-10–10Stops, applied in linear before conversion
    clamp_outputBOOLEANtrueClamp 0-1 after conversion (needed by most models). Off = HDR passthrough.

    Outputs (5)

    NameTypeDescription
    imageIMAGE
    alphaMASK
    frame_countINT
    fpsFLOAT
    video_infoVHS_VIDEOINFO