Spaces:
Sleeping
Sleeping
| import numpy as np | |
| RGB_MAX_VALUE = 255.0 | |
| SRGB_GAMMA = 2.4 | |
| SRGB_LINEAR_THRESHOLD = 0.04045 | |
| SRGB_OFFSET = 0.055 | |
| SRGB_SCALE = 1.055 | |
| SRGB_LINEAR_SCALE = 12.92 | |
| D65_ILLUMINANT_X = 0.95047 | |
| D65_ILLUMINANT_Z = 1.08883 | |
| LAB_EPSILON = 216 / 24389 | |
| LAB_KAPPA = 24389 / 27 | |
| LAB_DELTA = 16 / 116 | |
| def rgb_to_lab(rgb): | |
| normalized_rgb = rgb.astype(np.float32) / RGB_MAX_VALUE | |
| linear_rgb = apply_inverse_srgb_gamma(normalized_rgb) | |
| xyz_values = convert_linear_rgb_to_xyz(linear_rgb) | |
| normalized_xyz = normalize_xyz_by_d65_illuminant(xyz_values) | |
| return convert_xyz_to_lab(normalized_xyz) | |
| def apply_inverse_srgb_gamma(normalized_rgb): | |
| above_threshold = normalized_rgb > SRGB_LINEAR_THRESHOLD | |
| linearized_high_values = ((normalized_rgb + SRGB_OFFSET) / SRGB_SCALE) ** SRGB_GAMMA | |
| linearized_low_values = normalized_rgb / SRGB_LINEAR_SCALE | |
| return np.where(above_threshold, linearized_high_values, linearized_low_values) | |
| def convert_linear_rgb_to_xyz(linear_rgb): | |
| srgb_to_xyz_matrix = np.array( | |
| [ | |
| [0.4124564, 0.3575761, 0.1804375], | |
| [0.2126729, 0.7151522, 0.0721750], | |
| [0.0193339, 0.1191920, 0.9503041], | |
| ], | |
| dtype=np.float32, | |
| ) | |
| return linear_rgb @ srgb_to_xyz_matrix.T | |
| def normalize_xyz_by_d65_illuminant(xyz): | |
| normalized = xyz.copy() | |
| normalized[:, 0] /= D65_ILLUMINANT_X | |
| normalized[:, 2] /= D65_ILLUMINANT_Z | |
| return normalized | |
| def convert_xyz_to_lab(normalized_xyz): | |
| above_epsilon = normalized_xyz > LAB_EPSILON | |
| cubic_root_values = normalized_xyz ** (1 / 3) | |
| linear_scaled_values = (LAB_KAPPA * normalized_xyz + 16) / 116 | |
| f_transformed = np.where(above_epsilon, cubic_root_values, linear_scaled_values) | |
| lab_values = np.zeros_like(normalized_xyz) | |
| lab_values[:, 0] = 116 * f_transformed[:, 1] - 16 | |
| lab_values[:, 1] = 500 * (f_transformed[:, 0] - f_transformed[:, 1]) | |
| lab_values[:, 2] = 200 * (f_transformed[:, 1] - f_transformed[:, 2]) | |
| return lab_values | |
| def lab_to_rgb(lab): | |
| xyz_values = convert_lab_to_xyz(lab) | |
| linear_rgb = convert_xyz_to_linear_rgb(xyz_values) | |
| normalized_rgb = apply_srgb_gamma(linear_rgb) | |
| return convert_normalized_to_8bit_rgb(normalized_rgb) | |
| def convert_lab_to_xyz(lab): | |
| f_y = (lab[:, 0] + 16) / 116 | |
| f_x = lab[:, 1] / 500 + f_y | |
| f_z = f_y - lab[:, 2] / 200 | |
| x_above_epsilon = f_x**3 > LAB_EPSILON | |
| y_above_epsilon = lab[:, 0] > LAB_KAPPA * LAB_EPSILON | |
| z_above_epsilon = f_z**3 > LAB_EPSILON | |
| xyz_values = np.zeros((len(lab), 3), dtype=np.float32) | |
| x_cubic = f_x**3 | |
| x_linear = (116 * f_x - 16) / LAB_KAPPA | |
| xyz_values[:, 0] = np.where(x_above_epsilon, x_cubic, x_linear) * D65_ILLUMINANT_X | |
| y_cubic = f_y**3 | |
| y_linear = lab[:, 0] / LAB_KAPPA | |
| xyz_values[:, 1] = np.where(y_above_epsilon, y_cubic, y_linear) | |
| z_cubic = f_z**3 | |
| z_linear = (116 * f_z - 16) / LAB_KAPPA | |
| xyz_values[:, 2] = np.where(z_above_epsilon, z_cubic, z_linear) * D65_ILLUMINANT_Z | |
| return xyz_values | |
| def convert_xyz_to_linear_rgb(xyz): | |
| xyz_to_srgb_matrix = np.array( | |
| [ | |
| [3.2404542, -1.5371385, -0.4985314], | |
| [-0.9692660, 1.8760108, 0.0415560], | |
| [0.0556434, -0.2040259, 1.0572252], | |
| ], | |
| dtype=np.float32, | |
| ) | |
| return xyz @ xyz_to_srgb_matrix.T | |
| def apply_srgb_gamma(linear_rgb): | |
| SRGB_LINEAR_CUTOFF = 0.0031308 | |
| above_cutoff = linear_rgb > SRGB_LINEAR_CUTOFF | |
| gamma_corrected_high = SRGB_SCALE * (linear_rgb ** (1 / SRGB_GAMMA)) - SRGB_OFFSET | |
| gamma_corrected_low = SRGB_LINEAR_SCALE * linear_rgb | |
| return np.where(above_cutoff, gamma_corrected_high, gamma_corrected_low) | |
| def convert_normalized_to_8bit_rgb(normalized_rgb): | |
| scaled_values = normalized_rgb * RGB_MAX_VALUE | |
| clipped_values = np.clip(scaled_values, 0, RGB_MAX_VALUE) | |
| return clipped_values.astype(np.uint8) | |