New Sigmoid Scene to Display mapping

Aaand I just found the Python prototype…


%matplotlib notebook
import numpy as np
import matplotlib
matplotlib.rcParams['pdf.fonttype']=42
matplotlib.rcParams['ps.fonttype']=42
import matplotlib.pyplot as plt
from ipywidgets import interact
plt.style.use('bmh')

# define the curve via these control vertices here:
x=np.array([0.3,0.45,0.80,1.00])
y=np.array([0.0,0.62,0.14,0.97])

m=np.array([0.0,0.0,0.0,0.0,0.0])
d=np.array([0.0,0.0,0.0,0.0,0.0])

def fma(a, b, c):
    # fake FMA just for the logic of it
    return a * b + c


def filmic_spline_draw(x, coeffs, toe, shoulder):
    # coeffs = [a, b, c, d, e, f, g, h, i, j, k, l]
    # s.t. :
    # P0 = ax⁴ + bx³ + cx² + dx + e
    # P1 = fx + g
    # P2 = hx⁴ + ix³ + jx² + kx + l
    # latitude = [toe ; shoulder] < dynamic range
    # See https://bit.ly/2IXfLnQ
    
    # build the value masking row vector
    part0 = x < toe
    part2 = x > shoulder
    part1 = part0 == part2 # where part0 == part2 == False, toe < x < shoulder
    mask = np.array([part0, part1, part2])
    
    # make x a 2D row vector
    x = x[np.newaxis, :]
    
    # unpack coeffs
    a = coeffs[0]
    b = coeffs[1]
    c = coeffs[2]
    d = coeffs[3]
    e = coeffs[4]
    f = coeffs[5]
    g = coeffs[6]
    h = coeffs[7]
    i = coeffs[8]
    j = coeffs[9]
    k = coeffs[10]
    l = coeffs[11]
    
    # repack coeffs as column vectors
    M1 = np.array([[e, g, l]]).transpose()  # const
    M2 = np.array([[d, f, k]]).transpose()  # factors of x
    M3 = np.array([[c, 0, j]]).transpose() # factors of x²
    M4 = np.array([[b, 0., i]]).transpose() # factors of x³
    M5 = np.array([[a, 0., h]]).transpose() # factors of x⁴
    
    # evaluate the 3 parts of the curve
    y = fma(x, fma(x, (fma(x, fma(x, M5, M4), M3)), M2), M1)
    
    # apply masks and sum
    return (y * mask).sum(axis=0)


def filmic_desaturation_draw(x, coeffs, toe, shoulder):
    # coeffs = [a, b, c, d, e, f, g, h, i, j, k, l]
    # s.t. :
    # P0 = ax⁴ + bx³ + cx² + dx + e
    # P1 = fx + g
    # P2 = hx⁴ + ix³ + jx² + kx + l
    # latitude = [toe ; shoulder] < dynamic range
    # See https://bit.ly/2IXfLnQ
    
    # build the value masking row vector
    part0 = x < toe
    part2 = x > shoulder
    part1 = part0 == part2 # where part0 == part2 == False, toe < x < shoulder
    mask = np.array([part0, part1, part2])
    
    # make x a 2D row vector
    x = x[np.newaxis, :]
    
    # unpack coeffs
    a = coeffs[0]
    b = coeffs[1]
    c = coeffs[2]
    d = coeffs[3]
    e = coeffs[4]
    f = coeffs[5]
    g = coeffs[6]
    h = coeffs[7]
    i = coeffs[8]
    j = coeffs[9]
    k = coeffs[10]
    l = coeffs[11]
    
    # repack coeffs as column vectors
    M3 = np.array([[c, 0, j]]).transpose() # factors of x²
    M4 = np.array([[b, 0., i]]).transpose() # factors of x³
    M5 = np.array([[a, 0., h]]).transpose() # factors of x⁴
    
    # evaluate the 3 parts of the curve
    y = 2 * fma(x, 3 * fma(x, 2 * M5, M4), M3)
    
    # apply masks and sum
    return np.abs((y * mask).sum(axis=0)) / 8.


def filmic_spline_solve(T_l, T_d, S_l, S_d):
    
    # Contrast/slope of the latitude
    C = (S_d - T_d) / (S_l - T_l)
    
    # Get params of the linear part : P1 = fx + g
    M1 = np.array([[1.  ,  0.], 
                   [T_l ,  1.]])
    y1 = np.array([C, T_d])
    P1 = np.linalg.solve(M1, y1)
    
    f = P1[0]
    g = P1[1]
    
    # Get params of the cubic toe P0 = ax⁴ + bx³ + cx² + dx + e
    M0 = np.array([[0.           , 0.          , 0.       , 0. , 1.],
                   [0.           , 0.          , 0.       , 1. , 0.],
                   [T_l**4       , T_l**3      , T_l**2   , 1. , 0.],
                   [4. * T_l**3  , 3. * T_l**2 , 2. * T_l , 1. , 0.],
                   [12. * T_l**2 , 6. * T_l    , 2.       , 0. , 0.]])
    y0 = np.array([0., 0., T_d, f, 0.])  
    P0 = np.linalg.solve(M0, y0)
    
    a = P0[0]
    b = P0[1]
    c = P0[2]
    d = P0[3]
    e = P0[4]
    
    # Get params of the cubic shoulder P2 = hx⁴ + ix³ + jx² + kx + l
    M2 = np.array([[1.           , 1.          , 1.      , 1. ],
                   [S_l**3      , S_l**2  , S_l , 1.],
                   [3. * S_l**2 , 2. * S_l, 1. , 0.],
                   [6. * S_l    , 2.      , 0. , 0.]])
    y2 = np.array([1., S_d, f, 0.])
    P2 = np.linalg.solve(M2, y2)
    
    h = 0.
    i = P2[0]
    j = P2[1]
    k = P2[2]
    l = P2[3]
    
    # pack the params of the solution
    return [a, b, c, d, e, f, g, h, i, j, k, l]


fig, ax = plt.subplots(figsize=(5,5))
ax.set_ylim([-2.,2.])
ax.set_xlim([-2.,2.])
pos = np.linspace(0., 1., 1000) # 4 Mpix to make benchmarks meaningful
ax.axvline(x=0.0,ymin=0.0,ymax=1.0)
ax.axvline(x=1.0,ymin=0.0,ymax=1.0)
px1 = ax.axvline(x=x[1],ymin=0.0,ymax=1.0)
px2 = ax.axvline(x=x[2],ymin=0.0,ymax=1.0)
ax.axhline(y=0.0,xmin=0.0,xmax=1.0)
ax.axhline(y=1.0,xmin=0.0,xmax=1.0)
plot1 = ax.plot(pos, np.zeros(pos.shape), color='C1')[0]
plot2 = ax.plot(pos, np.zeros(pos.shape), color='C3')[0]
plot3 = ax.plot(pos, np.zeros(pos.shape), color='C5')[0]


@interact(ix1=(0.0,1.0,0.01),ix2=(0.0,1.0,0.01),iy1=(0.0,1.0,0.01),iy2=(0.0,1.0,0.01))
def test_spline(iy1=0.55, iy2=0.92, ix1=0.67, ix2=0.92):
    y[0] = 0.
    y[1] = iy1
    y[2] = iy2
    y[3] = 1.
    x[0] = 0.
    x[1] = ix1
    x[2] = ix2
    x[3] = 1.
    
    px1.set_xdata(x[1])
    px2.set_xdata(x[2])
    
    # plt.plot(pos, np.power(pos, 0)*c[0]+ pos*c[1] + pos*pos*c[2] + pos*pos*pos*c[3])
    
    setup_spline()
    plot1.set_ydata(np.array([hermite(t) for t in pos]))
    
    params = filmic_spline_solve(ix1, iy1, ix2, iy2)
    y_filmic = filmic_spline_draw(pos, params, ix1, ix2)
    plot2.set_ydata(y_filmic)
    
    y_desat = filmic_desaturation_draw(pos, params, ix1, ix2)
    plot3.set_ydata(y_desat)
    
    ax.legend((plot1, plot2, plot3), ["Hermite", "Filmic", "Desaturation"])
    fig.canvas.draw()

# test_spline(0.0, 0.2, 0.8, 1.0)
4 Likes