def main(): """A simple test case.""" x = [0, 1 / 3, 1] y = floatX([[51, 51, 127], [51, 127, 51], [255, 102, 51]]) / 255 # x = [0, 0.5, 1] # y = floatX([[20, 80, 120], [40, 60, 160], [255, 255, 200]]) / 255 g = Gradient(x, y, bg=BgColors.LIGHT) x_out, y_out, _ = g.make_gradient(steps=30, bg=BgColors.LIGHT) g.print_stdout(x_out, y_out)
def srgb_to_ucs(RGB, Y_w, L_A, Y_b, F, c, N_c): """Converts sRGB (gamma=2.2) colors to CAM02-UCS (Luo et al. 2006) Jab.""" XYZ_w = T.dot([[1, 1, 1]], M_SRGB_to_XYZ) * Y_w RGB_w = T.dot(XYZ_w, M_CAT02) # D = T.clip(F * (1 - (1/3.6) * T.exp((-L_A - 42) / 92)), 0, 1) D = floatX([1, 1, 1]) # Discount the illuminant fully k = 1 / (5 * L_A + 1) D_rgb = D * Y_w / RGB_w + 1 - D F_L = 0.2 * k**4 * (5 * L_A) + 0.1 * (1 - k**4)**2 * (5 * L_A)**(1/3) n = Y_b / Y_w z = 1.48 + T.sqrt(n) N_bb = 0.725 * (1/n)**0.2 N_cb = N_bb RGB_wc = D_rgb * RGB_w RGB_wp = T.dot(T.dot(RGB_wc, M_CAT02_inv), M_HPE) RGB_aw_i = (F_L * RGB_wp / 100)**0.42 RGB_aw = 400 * RGB_aw_i / (RGB_aw_i + 27.13) + 0.1 A_w = (T.sum(RGB_aw * [2, 1, 1/20], axis=-1) - 0.305) * N_bb RGB_linear = T.maximum(EPS, RGB)**2.2 XYZ = T.dot(RGB_linear, M_SRGB_to_XYZ) * Y_w RGB_ = T.dot(XYZ, M_CAT02) RGB_c = D_rgb * RGB_ RGB_p = T.dot(T.dot(RGB_c, M_CAT02_inv), M_HPE) RGB_ap_p_i = (F_L * RGB_p / 100)**0.42 RGB_ap_n_i = (-F_L * RGB_p / 100)**0.42 RGB_ap_p = 400 * RGB_ap_p_i / (RGB_ap_p_i + 27.13) + 0.1 RGB_ap_n = -400 * RGB_ap_n_i / (RGB_ap_n_i + 27.13) + 0.1 RGB_ap = T.switch(RGB_ap_p >= 0, RGB_ap_p, RGB_ap_n) a = T.sum(RGB_ap * [1, -12/11, 1/11], axis=-1) b = T.sum(RGB_ap * [1/9, 1/9, -2/9], axis=-1) h = T.rad2deg(T.arctan2(b, a)) h_p = T.switch(h < 0, h + 360, h) e_t = (T.cos(h_p * np.pi / 180 + 2) + 3.8) / 4 A = (T.sum(RGB_ap * [2, 1, 1/20], axis=-1) - 0.305) * N_bb J = 100 * T.maximum(0, A / A_w)**(c * z) t_num = 50000/13 * N_c * N_cb * e_t * T.sqrt(a**2 + b**2) t = t_num / T.sum(RGB_ap * [1, 1, 21/20], axis=-1) C = t**0.9 * T.sqrt(J / 100) * (1.64 - 0.29**n)**0.73 M = C * F_L**0.25 K_L, c_1, c_2 = 1, 0.007, 0.0228 J_p = (1 + 100 * c_1) * J / (1 + c_1 * J) M_p = (1 / c_2) * T.log(1 + c_2 * M) a_Mp = M_p * T.cos(T.deg2rad(h_p)) b_Mp = M_p * T.sin(T.deg2rad(h_p)) return T.stack([J_p, a_Mp, b_Mp], axis=-1)
def main(): """A simple test case.""" rgb1, rgb2 = T.matrices('rgb1', 'rgb2') jab1 = srgb_to_ucs(rgb1, 100, 20, 20, **Surrounds.AVERAGE) jab2 = srgb_to_ucs(rgb2, 100, 20, 20, **Surrounds.AVERAGE) loss = delta_e(jab1, jab2)**2 grad_ = T.grad(loss, rgb2) grad = theano.function([rgb1, rgb2], grad_) # Inversion of CAM02-UCS via gradient descent target = floatX([[0.25, 0.25, 1]]) x = np.zeros_like(target) + 0.5 print(x) for i in range(1500): g = grad(target, x) x -= 1e-6 * g if i % 100 == 99: print(x)
def __init__(self, x, y, colorspace='rgb', periodic=False, bg=BgColors.NEUTRAL, compile_only=False): if compile_only: self.x, self.y, self.bg = None, None, None self.make_gradient() return self.y = np.atleast_2d(y) if self.y.ndim != 2 or self.y.shape[-1] != 3: raise ValueError( 'y.ndim must be 2 and y.shape[-1] must be 3 (RGB/JMH).') self.x = np.linspace(0, 1, len(y)) if x is None else floatX(x) self.colorspace = colorspace.lower() self.periodic = periodic self.bg = bg
def grad_request(msg, send): if msg['steps'] > 1024: send({'_': 'error', 'text': 'Limit 1024 steps.'}) return parser = Parser() try: parser.parse(msg['spec']) except ParseBaseException as err: send({'_': 'error', 'text': str(err)}) return if len(parser.grad_points) < 2: send({'_': 'error', 'text': 'At least two colors are required.'}) return else: x = [point[0] for point in parser.grad_points] y = [point[1] for point in parser.grad_points] y = floatX(y) / 255 if parser.colorspace == 'rgb' else y g = Gradient(x, y, colorspace=parser.colorspace, periodic=bool(msg['periodic'])) x_out, y_out, s = g.make_gradient(steps=msg['steps'], interpolator=msg['interpolator'], callback=lambda x: send({ '_': 'progress', 'text': x })) send({'_': 'progress', 'text': s}) css_data_url = 'data:text/css,' + urllib.parse.quote( g.to_css(x_out, y_out)) csv_data_url = 'data:text/csv,' + urllib.parse.quote( g.to_csv(x_out, y_out)) send({ '_': 'result', 'html': g.to_html(x_out, y_out), 'asCSS': css_data_url, 'asCSV': csv_data_url })
def make_gradient(self, steps=30, interpolator='PchipInterpolator', bg=BgColors.NEUTRAL, diff_weight=1e4, callback=None): global opfunc start = time.perf_counter() def _loss(y, ideal_jab, ideal_diff): jab = ucs.symbolic.srgb_to_ucs(y, 80, 16, ucs.srgb_to_xyz(bg)[1] * 80, **Surrounds.AVERAGE) diff = jab[1:, :] - jab[:-1, :] ucs_loss = T.sum(T.sqr(jab - ideal_jab)) diff_loss = T.mean(T.sqr(diff - ideal_diff)) return ucs_loss + diff_loss * diff_weight if opfunc is None: rgb, _ideal_jab, _ideal_diff = T.matrices('rgb', 'ideal_jab', 'ideal_diff') loss_sym = _loss(rgb, _ideal_jab, _ideal_diff) grad_sym = T.grad(loss_sym, rgb) # Ensure this function is compiled ucs.srgb_to_ucs([1, 1, 1]) print('Building opfunc()...', file=sys.stderr) opfunc = theano.function([rgb, _ideal_jab, _ideal_diff], [loss_sym, grad_sym], allow_input_downcast=True, on_unused_input='ignore') print('Done building functions in {:.3g} seconds.'.format( time.perf_counter() - start), file=sys.stderr) # If the method was called only to precompile Theano functions, return early if self.x is None: return if self.colorspace == 'rgb': conds = Conditions(Y_w=100, Y_b=ucs.srgb_to_xyz(self.bg)[1] * 100) jmh = ucs.jab_to_jmh(ucs.srgb_to_ucs(self.y, conds)) elif self.colorspace == 'jmh': jmh = self.y.copy() jmh[:, 2] = ucs.H_to_h(self.y[:, 2]) else: raise ValueError('colorspace must be RGB or JMH') jmh[:, 2] = np.rad2deg(np.unwrap(np.deg2rad(jmh[:, 2]))) if self.periodic: jmh[-1] = jmh[0] interp = interpolate.CubicSpline(self.x, jmh, axis=0, bc_type='periodic') else: if not hasattr(interpolate, interpolator): raise ValueError( 'interpolator must exist in scipy.interpolate') interp = getattr(interpolate, interpolator)(self.x, jmh, axis=0) ideal_jmh = np.zeros((steps, 3)) x = np.linspace(self.x[0], self.x[-1], steps) for i, n in enumerate(x): ideal_jmh[i] = interp(n) ideal_jab = ucs.jmh_to_jab(ideal_jmh) ideal_diff = ideal_jab[1:, :] - ideal_jab[:-1, :] y = floatX(np.random.uniform(-1e-8, 1e-8, size=ideal_jab.shape)) + 0.5 opt = AdamOptimizer(y, opfunc=lambda y: opfunc(y, ideal_jab, ideal_diff), proj=lambda y: np.clip(y, 0, 1)) for i in opt: if i % 100 == 0: loss_ = float(opfunc(y, ideal_jab, ideal_diff)[0]) if callback is not None: callback('Iteration {:d}, loss = {:.3f}'.format(i, loss_)) # i = 0 # y_shape = y.shape # def lbfgs_callback(y_opt): # nonlocal i # i += 1 # if i % 100 == 0: # loss_ = float(opfunc(y_opt.reshape(y_shape), ideal_jab, ideal_diff)[0]) # if callback is not None: # callback('Iteration {:d}, loss = {:.3f}'.format(i, loss_)) # y = lbfgs(y, lambda y: opfunc(y, ideal_jab, ideal_diff), callback=lbfgs_callback) done = time.perf_counter() s = ( 'Loss was {:.3f} after {:d} iterations; make_gradient() took {:.3f} seconds.' ).format( float(opfunc(y, ideal_jab, ideal_diff)[0]), i, done - start, ) return x, y, s
class BgColors: """Specifies three different background colors that work well with CIECAM02.""" DARK = floatX([0.2] * 3) NEUTRAL = floatX([0.5] * 3) LIGHT = floatX([0.8] * 3)