-
Notifications
You must be signed in to change notification settings - Fork 0
/
Color_operations.py
383 lines (316 loc) · 15.7 KB
/
Color_operations.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Mar 28 20:33:16 2019
@author: armi
"""
from RGB_extractor_Xrite_CC import rgb_extractor_Xrite_CC
from RGB_extractor import plot_aging_data, get_image, image_slicing
import numpy as np
import numpy.matlib as matlib
from colormath.color_objects import LabColor, sRGBColor
from colormath.color_conversions import convert_color
from Video import substring_indexes
from PIL import Image
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.collections import PatchCollection
import os
###############################################################################
# Part 1/2: Functions related to color space conversions and color calibration.
###############################################################################
def convert_color_space(data, from_space, to_space):
# Input:
# - data: a np array with dimensions (n_samples, {optional
# dimension: n_times}, n_color_coordinates=3) (e.g., a direct output of
# 'rgb_extractor()' or 'rgb_extractor_Xrite_CC()')
# - from_space: choose either 'RGB' or 'Lab'
# - to_space: choose either 'RGB' or 'Lab'
# Output:
# - converted: a np array with the same dimensions than in the input
# We need the code to work for inputs containing the optional dimension
# n_times (i.e., many time points) and for inputs containing only one time
# point.
n_d = data.ndim
if n_d == 2:
data = np.expand_dims(data, 1)
elif n_d != 3:
raise Exception('Faulty number of dimensions in the input!')
if (from_space == 'RGB') and (to_space == 'Lab'):
# Values from rgb_extractor() are [0,255] so let's normalize.
data = data/255
# Transform to color objects (either sRGBColor or LabColor).
data_objects = np.vectorize(lambda x,y,z: sRGBColor(x,y,z))(
data[:,:,0], data[:,:,1], data[:,:,2])
# Target color space
color_space = matlib.repmat(LabColor, *data_objects.shape)
# Transform from original space to new space
converted_objects = np.vectorize(lambda x,y: convert_color(x,y))(
data_objects, color_space)
# We got a matrix of color objects. Let's transform to a 3D matrix of floats.
converted = np.transpose(np.vectorize(lambda x: (x.lab_l, x.lab_a, x.lab_b))(
converted_objects), (1,2,0))
elif (from_space == 'Lab') and (to_space == 'RGB'):
data_objects = np.vectorize(lambda x,y,z: LabColor(x,y,z))(
data[:,:,0], data[:,:,1], data[:,:,2])
color_space = matlib.repmat(sRGBColor, *data_objects.shape)
converted_objects = np.vectorize(lambda x,y: convert_color(x,y))(
data_objects, color_space)
converted = np.transpose(np.vectorize(lambda x: (x.rgb_r, x.rgb_g, x.rgb_b))(
converted_objects), (1,2,0))
# Colormath library interprets rgb in [0,1] and we want [0,255] so let's
# normalize to [0,255].
converted = converted*255
else:
raise Exception('The given input space conversions have not been implemented.')
if n_d == 2:
converted = np.squeeze(converted)
return (converted)
def color_calibration(sample_rgb, pic_folder_Xrite, pic_name_Xrite,
crop_box_Xrite, offset_array_Xrite, plot_images):
# Reference data for Xrite color chart in Lab (from
# http://www.babelcolor.com/colorchecker-2.htm, retrieved in March 2019, for
# colorchecker passports manufactured after November 2014)
reference_CC_lab =np.array([[37.54,14.37,14.92],[62.73,35.83,56.5],[28.37,15.42,-49.8],
[95.19,-1.03,2.93],[64.66,19.27,17.5],[39.43,10.75,-45.17],
[54.38,-39.72,32.27],[81.29,-0.57,0.44],[49.32,-3.82,-22.54],
[50.57,48.64,16.67],[42.43,51.05,28.62],[66.89,-0.75,-0.06],
[43.46,-12.74,22.72],[30.1,22.54,-20.87],[81.8,2.67,80.41],
[50.76,-0.13,0.14],[54.94,9.61,-24.79],[71.77,-24.13,58.19],
[50.63,51.28,-14.12],[35.63,-0.46,-0.48],[70.48,-32.26,-0.37],
[71.51,18.24,67.37],[49.57,-29.71,-28.32],[20.64,0.07,-0.46]])
# Reference data is in different order (from upper left to lower left, upper
# 2nd left to lower 2nd left...). This is the correct order:
order = list(range(0,21,4)) + list(range(1,22,4)) + list(range(2,23,4)) + list(range(3,24,4))
reference_CC_lab = reference_CC_lab[order]
# For debugging purposes, let's convert to RGB. --> Was ok!
#reference_CC_rgb = convert_color_space(reference_CC_lab, 'Lab', 'RGB')
# Let's extract the rgb colors from our Xrite color passport picture.
CC_rgb = rgb_extractor_Xrite_CC(pic_folder_Xrite, pic_name_Xrite,
crop_box_Xrite, offset_array_Xrite,
plot_images)
# Convert from RGB to Lab color space.
CC_lab = convert_color_space(CC_rgb, 'RGB', 'Lab')
sample_lab = convert_color_space(sample_rgb, 'RGB', 'Lab')
###########################
# Color calibration starts.
# Number of color patches in the color chart.
N_patches = CC_lab.shape[0]
# Let's create the weight matrix for color calibration using 3D thin plate
# spline.
# Data points of our color chart in the original space.
P = np.concatenate((np.ones((N_patches,1)), CC_lab), axis=1)
# Data points of our color chart in the transformed space.
V = reference_CC_lab
# Shape distortion matrix, K
K = np.zeros((N_patches,N_patches))
for i in range(N_patches):
for j in range(N_patches):
if i != j:
r_ij = np.sqrt((P[j,0+1]-P[i,0+1])**2 +
(P[j,1+1]-P[i,1+1])**2 +
(P[j,2+1]-P[i,2+1])**2)
U_ij = 2* (r_ij**2)* np.log(r_ij + 10**(-20))
K[i,j] = U_ij
# Linear and non-linear weights WA:
numerator = np.concatenate((V, np.zeros((4,3))), axis=0)
denominator = np.concatenate((K,P), axis=1)
denominator = np.concatenate((denominator,
np.concatenate((np.transpose(P),
np.zeros((4,4))),axis=1)), axis=0)
WA = np.matmul(np.linalg.pinv(denominator), numerator)
# Checking if went ok. We should get the same result than in V (exept for
# the 4 bottom rows)
CC_lab_double_transformation = np.matmul(denominator,WA)
#print('Color chart patches in reference Lab:', reference_CC_lab,
# 'Color chart patches transformed to color calibrated space and back - this should be the same than above apart from the last 4 rows',
# CC_lab_double_transformation, 'subtracted: ', reference_CC_lab-CC_lab_double_transformation[0:-4,:])
print('Checking if color transformation is successful - all values here should be near zero:/n', reference_CC_lab-CC_lab_double_transformation[0:-4,:])
# Let's perform color calibration for the sample points!
N_samples = sample_lab.shape[0]
N_times = sample_lab.shape[1]
sample_lab_cal = np.zeros((N_samples,N_times+4,3))
# We are recalculating P and K for each sample, but using the WA calculated above.
for s in range(N_samples):
# Data points of color chart in the original space.
P_new = np.concatenate((np.ones((N_times,1)), sample_lab[s,:,:]), axis=1)
K_new = np.zeros((N_times,N_patches))
# For each time point (i.e., picture):
for i in range(N_times):
# For each color patch in Xrite color chart:
for j in range(N_patches):
#if i != j:
r_ij = np.sqrt((P_new[i,0+1]-P[j,0+1])**2 + (P_new[i,1+1]-P[j,1+1])**2 + (P_new[i,2+1]-P[j,2+1])**2)
U_ij = 2* (r_ij**2)* np.log(r_ij + 10**(-20))
K_new[i,j] = U_ij
dennom = np.concatenate((K_new,P_new),axis=1)
denden = np.concatenate((np.transpose(P), np.zeros((4,4))), axis=1)
sample_lab_cal[s,:,:] = np.matmul(np.concatenate((dennom, denden), axis=0), WA)
# Remove zeros, i.e., the last four rows from the third dimension.
sample_lab_cal = sample_lab_cal[:,0:-4,:]
################################
# Color calibration is done now.
# Let's transform back to rgb.
sample_rgb_cal = convert_color_space(sample_lab_cal, 'Lab', 'RGB')
# Let's return both lab and rgb calibrated values.
return (sample_rgb_cal, sample_lab_cal)
def color_conversion_results(results_rgb):
# Let's convert these to Lab.
results_lab = [0,0,0,0, results_rgb[4], 0, 0, results_rgb[7]]
for i in range(0,4):
results_lab[i] = convert_color_space(results_rgb[i], 'RGB', 'Lab')
# Let's plot the data.
[results_lab[5], ax_CC, results_lab[6], ax_samples] = plot_results(
results_lab, 'Lab')
return results_lab
def color_calibration_results(results_rgb, color_cal_inputs):
pic_folder = color_cal_inputs[0]
pic_name_Xrite = color_cal_inputs[1]
crop_box_Xrite = color_cal_inputs[2]
offset_array_Xrite = color_cal_inputs[3]
results_rgb_cal = [0,0,0,0, results_rgb[4], 0, 0, results_rgb[7]]
results_lab_cal = [0,0,0,0, results_rgb[4], 0, 0, results_rgb[7]]
# Repeated color calibration for sample_rgb, sample_rgb_percentiles_lo,
# sample_rgb_percentiles_hi, and CC_rgb.
for i in range(0,4):
if i == 0:
plot_images = True
else:
plot_images = False
(results_rgb_cal[i], results_lab_cal[i]) = color_calibration(results_rgb[i],
pic_folder, pic_name_Xrite,
crop_box_Xrite, offset_array_Xrite, plot_images)
# Let's plot the data.
[results_rgb_cal[5], ax_CC, results_rgb_cal[6], ax_samples] = plot_results(
results_rgb_cal, 'RGB')
[results_lab_cal[5], ax_CC, results_lab_cal[6], ax_samples] = plot_results(
results_lab_cal, 'Lab')
return [results_rgb_cal, results_lab_cal]
def plot_results(results, datatype):
# Let's plot the data.
t_sort = results[4]
CC_r_sort = results[3][:,:,0]
CC_g_sort = results[3][:,:,1]
CC_b_sort = results[3][:,:,2]
r_sort = results[0][:,:,0]
g_sort = results[0][:,:,1]
b_sort = results[0][:,:,2]
r_lo_sort = results[1][:,:,0]
g_lo_sort = results[1][:,:,1]
b_lo_sort = results[1][:,:,2]
r_hi_sort = results[2][:,:,0]
g_hi_sort = results[2][:,:,1]
b_hi_sort = results[2][:,:,2]
[fig_CC, ax_CC, fig_samples, ax_samples] = plot_aging_data(
4,6,t_sort, CC_r_sort, CC_g_sort, CC_b_sort,
7,4,r_sort, g_sort, b_sort,
r_hi_sort, g_hi_sort, b_hi_sort,
r_lo_sort, g_lo_sort, b_lo_sort, datatype)
return [fig_CC, ax_CC, fig_samples, ax_samples]
###############################################################################
# Part 2/2: Functions related plotting color calibrated data and saving it as
# pictures.
###############################################################################
def separate_filename_and_folderpath(pic_path):
# The name of the videos is the name of the aging test.
slashes = substring_indexes('/', pic_path)
folder = pic_path[0:slashes[-1]]
filename = pic_path[(slashes[-1]+1)::]
return [folder, filename]
def create_pic_name_and_path(pic_path, save_to_folder, name_append, pic_format):
[folder, filename] = separate_filename_and_folderpath(pic_path)
new_pic_path = save_to_folder + '/' + filename[0:(-4)] + name_append + '.' + pic_format
return new_pic_path
def plot_colors(pic_path, crop_box, offset_array, color_array, save_to_folder,
savefig, name_append, print_out):
#%%
# fetch picture for adjusting the cropping box
# should try find the optimum cropping box options
testfile = pic_path
image = Image.open(testfile, 'r')
im = Image.fromarray(np.array(image, dtype=np.uint8), 'RGB')
## Create figure and axes
#fig,ax = plt.subplots(1,figsize=(1248/100,1024/100))
## Display the image
#ax.imshow(im)
# Create a Rectangle patch
lw=1 # Line width
ec='r' # edge color
fc='none' # face color
box= crop_box
rect2 = patches.Rectangle((box[0],box[1]),box[2]-box[0],box[3]-box[1],
linewidth=lw,edgecolor=ec,facecolor=fc)
# Add the patch to the Axes
#ax.add_patch(rect2)
#plt.show()
# Color Card Cropping
[w,h,image_ROI]=get_image(testfile,crop_box)
########%%%%%%%%%%%%%%%%%%%%%%%%###############
# Row, Columns Settings and Offset pixels for each color square (TO BE CHANGED)
row_num_CC=7
col_num_CC=4
offset_array_CC = offset_array#[[25,25],[25,25]]#[[x_left,x_right],[y_upper,y_lower]]
########%%%%%%%%%%%%%%%%%%%%%%%%###############
[fig_CC, ax_CC, reconstr_CC, image_CC, fig_patches,
ax_patches]=color_patches_and_image_slicing(
image_ROI, col_num_CC, row_num_CC, offset_array_CC, color_array)
ax_CC.imshow(Image.fromarray(np.array(image_ROI, dtype=np.uint8), 'RGB'))
ax_patches.imshow(Image.fromarray(reconstr_CC, 'RGB'))
#print('print_out', print_out)
if print_out == 1:
plt.show(fig_CC)
if savefig == 1:
if not os.path.exists(save_to_folder):
os.makedirs(save_to_folder)
name1 = create_pic_name_and_path(pic_path, save_to_folder, name_append + '1', 'jpg')
#name2 = create_pic_name_and_path(pic_path, save_to_folder, name_append + '2', 'jpg')
fig_CC.savefig(name1)
#fig_patches.savefig(name1)
plt.close(fig_CC)
return None#(Xrite_rgb)
def color_patches_and_image_slicing(image_array, col_num, row_num, offset_array, color_array):
"""slice the ROIs from an image of an array of samples/colorcard"""
row_h = int(image_array.shape[0]/row_num)
col_w = int(image_array.shape[1]/col_num)
fig,ax = plt.subplots(1)#,figsize=(5,5))
fig_patches, ax_patches = plt.subplots(1)#,figsize=(5,5))
#all_patches = []
images = []
imagecol = []
for y in np.arange(row_num):
imagerow = []
for x in np.arange(col_num):
# slicing indices for each color square
y1 = row_h*y+offset_array[1][0]
y2 = row_h*(y+1)-offset_array[1][1]
x1 = col_w*x+offset_array[0][0]
x2 = col_w*(x+1)-offset_array[0][1]
image = image_array[y1:y2,x1:x2]
imagerow.append(image)#append every images in a row into a row list
images.append(image)#append every images into a list
# Add the rectangular patch to the Axes
# Create a Rectangle patch
lw=1 # Line width
ec='r' # edge color
fc=tuple(color_array[y*col_num + x]/255) # face color
rect = patches.Rectangle((x1,y1),x2-x1,y2-y1,
linewidth=lw,edgecolor=ec,facecolor=fc)
rect2 = patches.Rectangle(((x2-x1)*x,(y2-y1)*y),x2-x1,y2-y1,
linewidth=lw,edgecolor=ec,facecolor=fc)
ax.add_patch(rect)
ax_patches.add_patch(rect2)
imagecol.append(np.concatenate(imagerow, axis=1))
image_reconstr = np.array(np.concatenate(imagecol, axis=0), dtype=np.uint8)
return [fig, ax, image_reconstr, images, fig_patches, ax_patches]
#pic_folder='.'
#pic_name='20190328112533_40_np.jpg'
#crop_box=(350+60,250+245,900-80,850-80)
#offset_array=[[20,20],[20,20]]
#color_array=results_rgb[0][:,-1,:]
#testfile = pic_folder+'/'+pic_name
#[w,h,image_ROI]=get_image(testfile,crop_box)
#image_array=image_ROI
#col_num=4
#row_num=7
#test=plot_colors(pic_folder, pic_name, crop_box,
# offset_array, color_array)