diff --git a/src/methods/laporte_auriane/Project_report_LAPORTE.pdf b/src/methods/laporte_auriane/Project_report_LAPORTE.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a60362af40c78f667e3dbb1b1cf15a849c8ef585 Binary files /dev/null and b/src/methods/laporte_auriane/Project_report_LAPORTE.pdf differ diff --git a/src/methods/laporte_auriane/fct_to_demosaick.py b/src/methods/laporte_auriane/fct_to_demosaick.py new file mode 100644 index 0000000000000000000000000000000000000000..22c383c97dd108a10b58c5be6cbc5b991eafd422 --- /dev/null +++ b/src/methods/laporte_auriane/fct_to_demosaick.py @@ -0,0 +1,332 @@ +"""This file contains all the functions that are used in order to perform a Kimmel algorithm demosaicking""" + +##### Importations ##### +import numpy as np +import matplotlib.pyplot as plt +from src.forward_model import CFA + +##### Some functions used ##### +def neigh(i, j, colour_chan, img): + + """Finds the neighbours of the pixel (i, j) of a colour channel + Args: + i: column of the pixel + j: line of the pixel + colour_chan: colour channel chosen + img: image + Returns: + List of the neighbours of the pixel (i, j) and the pixel (i, j) + """ + + X = img[:, :, colour_chan].shape[0] + Y = img[:, :, colour_chan].shape[1] + + # Place each Pi compared to P5 (i horizontal and j vertical) + # See P1 to P9 drawing, page 2 of http://ultra.sdk.free.fr/docs/Image-Processing/Demosaic/An%20Improved%20Demosaicing%20Algorithm.pdf + # % (modulo) to make sure it is a multiple (avoid error index 1024 out of bounds) + P1 = img[(i-1)%Y, (j-1)%X, colour_chan] + P2 = img[i%Y, (j-1)%X, colour_chan] + P3 = img[(i+1)%Y, (j-1)%X, colour_chan] + P4 = img[(i-1)%Y, j%X, colour_chan] + P5 = img[i%Y, j%X, colour_chan] + P6 = img[(i+1)%Y, j%X, colour_chan] + P7 = img[(i-1)%Y, (j+1)%X, colour_chan] + P8 = img[i%Y, (j+1)%X, colour_chan] + P9 = img[(i+1)%Y, (j+1)%X, colour_chan] + #print(f'Size of P1: {P1.size}') # 1 + + return [P1, P2, P3, P4, P5, P6, P7, P8, P9] + +def derivat(neighbour_list): + + """Compute directional derivatives of pixel + Args: + neighbour_list: list of neighbours + Returns: + List of the directional derivatives + """ + + # Must be 9 (9P neigbours) + #print(f'neighbour_list len to derivate: {len(neighbour_list)}') # 9 + + # Assign P neighbours to the neighbour_list (to then compute the derivatives) + [P1, P2, P3, P4, P5, P6, P7, P8, P9] = neighbour_list + + # Compute the derivatives (independant of the colour component) + Dx = (P4 - P6) / 2 + Dy = (P2 - P8) / 2 + Dxd = (P3 - P7) / (2 * np.sqrt(2)) + Dyd = (P1 - P9) / (2 * np.sqrt(2)) + # Formula given at the top of page 3 (but very long computing time, and does not provide better results) + #Dxd = np.max( ( np.abs((P3 - P5) / (np.sqrt(2))), np.abs((P7 - P5) / (np.sqrt(2))) ) ) + #Dyd = np.max( ( np.abs((P1 - P5) / (np.sqrt(2))), np.abs((P9 - P5) / (np.sqrt(2))) ) ) + + # Store the computed values in deriv_list + deriv_list = [Dx, Dy, Dxd, Dyd] + #print(f'Len de deriv_list: {len(deriv_list)}') # 4 + + return deriv_list + +def weight_fct(i, j, neighbour_list, deriv_list, colour_chan, img): + + """Compute the weights Ei + Args: + i: column of the pixel + j: line of the pixel + neighbour_list: neighbour_list: list of neighbours + deriv_list: list of derivatives + colour_chan:colour channel chosen + img: image + Returns: + List of the weights + """ + + # Assignment + [P1, P2, P3, P4, P5, P6, P7, P8, P9] = neighbour_list + [Dx, Dy, Dxd, Dyd] = deriv_list + X = img[:,:,colour_chan].shape[0] + Y = img[:,:,colour_chan].shape[1] + + # E list to complete (weighting function) + E = [] + + # Fix the start + cur = 1 + + # Neighbourhood + for step in range(-1, 2): # from -1 to 1 (2-1) + for step in range(-1, 2): # otherwise only 3 values in E + # Find the neigbours + n = neigh(i+step, j+step, colour_chan, img) + # Derivatives + d = derivat(n) + + # See P1 to P9 drawing, page 2 of http://ultra.sdk.free.fr/docs/Image-Processing/Demosaic/An%20Improved%20Demosaicing%20Algorithm.pdf + if cur==4 or cur==6: + E.append( 1 / np.sqrt(1 + Dx**2 + d[0]**2) ) + elif cur==2 or cur==8: + E.append( 1 / np.sqrt(1 + Dy**2 + d[1]**2) ) + elif cur==7 or cur==3: + E.append( 1 / np.sqrt(1 + Dxd**2 + d[2]**2) ) + else: # 1 or 9 + E.append( 1 / np.sqrt(1 + Dyd**2 + d[3]**2) ) + cur = cur+1 + + #print(f'Len of E: {len(E)}') # 9 + # other way to code (Page 3/6 of http://elynxsdk.free.fr/ext-docs/Demosaicing/more/news0/Optimal%20Recovery%20Demosaicing.pdf, method 1)), but I did not understand what is or how T was chosen? + + return E + +def clip_extreme(img): + + """Clip the values of the image, so they are between 0-1 + Args: + img: image + Returns: + The clipped image + """ + + #max_val = np.amax(img) + #min_val = np.amin(img) + #print(f'Max value before clipping: {max_val}') + #print(f'Min value before clipping: {min_val}') + + # Put extreme values between 0 and 1 + img = np.clip(img, 0, 1) + + return img + +def quad_to_bayer(img, op): + + """Performs a conversion from Quad_bayer to Bayer pattern + Args: + img: image + op: CFA op + Returns: + The image Bayer pattern + """ + + # Based on p28/32 https://pyxalis.com/wp-content/uploads/2021/12/PYX-ImageViewer-User_Guide.pdf + ### 1. Swap 2 col every 2 columns + # Start: 2nd col --> 1 (bc start counting at 0) / Step: 4 (look at drawing of 1st swap p28 Pyxalis) / End: input_shape[0]-2 --> input_shape[0]-1 + for j in range (1, img.shape[0]-1, 4): + store = np.copy(img[:, j]) # Store col j of the img + img[:, j] = np.copy(img[:, j+1]) # Col j becomes like col j+1 + img[:, j+1] = store # Col j+1 become like col j (previously saved otherwise value lost) + store_op = np.copy(op.mask[:, j]) + op.mask[:, j] = np.copy(op.mask[:, j+1]) + op.mask[:, j+1] = store_op + + ### 2. Swap 2 lines every 2 lines (same process for the lines) + for i in range (1, img.shape[1]-1, 4): + store = np.copy(img[i, :]) + img[i, :] = np.copy(img[i+1, :]) + img[i+1, :] = store + store_op = np.copy(op.mask[i, :]) + op.mask[i, :] = np.copy(op.mask[i+1, :]) + op.mask[i+1, :] = store_op + + ### 3. Swap back some diagonal greens + # Starts: 0 and 2 / Steps: 4 / End: img.shape[nb]-1 --> img.shape[nb] + for k in range(0, img.shape[0], 4): + for l in range(2, img.shape[1], 4): + store = np.copy(img[k, l]) + img[k, l] = np.copy(img[k+1, l-1]) + img[k+1, l-1] = store + store_op = op.mask[k, l] + op.mask[k, l] = np.copy(op.mask[k+1, l-1]) + op.mask[k+1, l-1] = store_op + + return img + + +##### 1. Interpolation of green colour ##### +def interpol_green(neighbour_list, weights_list): + + """Interpolates missing green pixel + Args: + neighbour_list: list of neighbours + weights_list: list of weights + Returns: + The interpolated pixel + """ + + # Assignment + [P1, P2, P3, P4, P5, P6, P7, P8, P9] = neighbour_list + [E1, E2, E3, E4, E5, E6, E7, E8, E9] = weights_list + + # Compute missing green pixel (combination of 4 nearest neighbours) + G5 = (E2*P2 + E4*P4 + E6*P6 + E8*P8) / (E2+E4+E6+E8) + #print(f"G5 size: {G5.size}") # 1 + + return G5 + + +##### 2. Interpolation of red and blue colours ##### +def interpol_red_blue(neighbour_list, weights_list, green_neighbour): + + """Interpolates missing blue/red pixel + Args: + neighbour_list: list of neighbours in blue/red channel + weights_list: list of weights + green_neighbour: list of the neighbours in green channel + Returns: + The interpolated pixel (in blue/red channel) + """ + + # Assignment + [P1, P2, P3, P4, P5, P6, P7, P8, P9] = neighbour_list + [E1, E2, E3, E4, E5, E6, E7, E8, E9] = weights_list + [G1, G2, G3, G4, G5, G6, G7, G8, G9] = green_neighbour + + # Compute missing red/blue pixel (combination of 4 nearest neighbours) + R5_num = E1*(P1/G1) + E3*(P3/G3) + E7*(P7/G7) + E9*(P9/G9) + R5_denom = E1+E3+E7+E9 + R5 = G5 * R5_num / R5_denom + #print(f"R5 size: {R5.size}") # 1 + + return R5 + + +##### 3. Correction stage ##### +def green_correction(red_neighbour, green_neighbour, blue_neighbour, weights_list): + + """Correction of green pixels + Args: + red_neighbour: neighbours in red channel + green_neighbour: neighbours in green channel + blue_neighbour: neighbours in blue channel + weights_list: list of weights + Returns: + Corrected green pixel + """ + + # Assignment + [G1, G2, G3, G4, G5, G6, G7, G8, G9] = green_neighbour + [R1, R2, R3, R4, R5, R6, R7, R8, R9] = red_neighbour + [B1, B2, B3, B4, B5, B6, B7, B8, B9] = blue_neighbour + [E1, E2, E3, E4, E5, E6, E7, E8, E9] = weights_list + + # Compute correction + GB5_num = E2*(G2/B2) + E4*(G4/B4) + E6*(G6/B6) + E8*(G8/B8) + GR5_num = E2*(G2/R2) + E4*(G4/R4) + E6*(G6/R6) + E8*(G8/R8) + G5_denom = E2+E4+E6+E8 + GB5 = B5 * GB5_num / G5_denom + GR5 = R5 * GR5_num / G5_denom + + #print(f"GB5 size: {GB5.size}") # 1 + print(f"GB5: {GB5}") + #print(f"GR5 size: {GR5.size}") # 1 + print(f"GR5: {GR5}") + + G5 = (GR5 + GB5) / 2 + print(f"G5 size: {G5.size}") # 1 + print(f"G5: {G5}") + + return G5 + +def blue_correction(green_neighbour, blue_neighbour, weights_list): + + """Correction of blue pixels + Args: + green_neighbour: neighbours in green channel + blue_neighbour: neighbours in blue channel + weights_list: list of weights + Returns: + Corrected blue pixel + """ + + # Assignment + [G1, G2, G3, G4, G5, G6, G7, G8, G9] = green_neighbour + [B1, B2, B3, B4, B5, B6, B7, B8, B9] = blue_neighbour + [E1, E2, E3, E4, E5, E6, E7, E8, E9] = weights_list + + # Compute correction + B5_num = 0 + B5_denom = 0 + print(f'Len de weights_list: {len(weights_list)}') + + for i in range(len(weights_list)): + if i != 5: + B5_num += weights_list[i] * blue_neighbour[i] / green_neighbour[i] + B5_denom += weights_list[i] + + B5 = G5 * B5_num / B5_denom + + #print(f"B5_denom size: {B5_denom.size}") # 1 + print(f"B5_denom: {B5_denom}") # number + #print(f"B5_num size: {B5_num.size}") # 1 + print(f"B5_num: {B5_num}") # nan + #print(f"B5 size: {B5.size}") # 1 + print(f"B5: {B5}") + + return B5 + +def red_correction(red_neighbour, green_neighbour, weights_list): + + """Correction of red pixels + Args: + red_neighbour: neighbours in red channel + green_neighbour: neighbours in green channel + weights_list: list of weights + Returns: + Corrected red pixel + """ + + # Assignment + [G1, G2, G3, G4, G5, G6, G7, G8, G9] = green_neighbour + [R1, R2, R3, R4, R5, R6, R7, R8, R9] = red_neighbour + [E1, E2, E3, E4, E5, E6, E7, E8, E9] = weights_list + + # Compute correction + R5_num = 0 + R5_denom = 0 + for i in range(len(weights_list)): + if i != 5: + + R5_num += weights_list[i] * red_neighbour[i] / green_neighbour[i] + R5_denom += weights_list[i] + + R5 = G5 * R5_num / R5_denom + + return R5 \ No newline at end of file diff --git a/src/methods/laporte_auriane/reconstruct.py b/src/methods/laporte_auriane/reconstruct.py index a97bd3f6e3c68df763b36c46b2727461af078bd2..bac96a08738de8d3540462cc6617090ea910a210 100644 --- a/src/methods/laporte_auriane/reconstruct.py +++ b/src/methods/laporte_auriane/reconstruct.py @@ -3,10 +3,9 @@ This file should NOT be modified except the body of the 'run_reconstruction' fun Students can call their functions (declared in others files of src/methods/your_name). """ - import numpy as np - from src.forward_model import CFA +from src.methods.laporte_auriane.fct_to_demosaick import * def run_reconstruction(y: np.ndarray, cfa: str) -> np.ndarray: @@ -19,12 +18,121 @@ def run_reconstruction(y: np.ndarray, cfa: str) -> np.ndarray: Returns: np.ndarray: Demosaicked image. """ - # Performing the reconstruction. - # TODO + + #print(f'y shape: {y.shape}') # (1024, 1024) input_shape = (y.shape[0], y.shape[1], 3) op = CFA(cfa, input_shape) + + ########## TODO ########## + + # Colour channels + red = 0 + green = 1 + blue = 2 + correction = 0 + + # In case of Quad_bayer op + if op.cfa == 'quad_bayer': + print("Transformation into Bayer pattern") + y = quad_to_bayer(y, op) + print('Quad_bayer to Bayer done\n\nDemoisaicking processing...') + + z = op.adjoint(y) + # 1st and 2nd dim of a colour channel + X = z[:,:,0].shape[0] + Y = z[:,:,0].shape[1] + #print(f"X: {X}") # 1024 + #print(f"Y: {Y}") # 1024 + + + ### Kimmel algorithm ### + # About 3 to 4 minutes + + + # Interpolation of green colour + for i in range (X): + for j in range (Y): + # Control if value 0 in green channel + if z[i,j,1] == 0: + + # Find neigbours + n = neigh(i, j, green, z) + # Derivatives + d = derivat(n) + # Weighting fct + w = weight_fct(i, j, n, d, green, z) + # Green interpolation (cross) + z[i,j,1] = interpol_green(n, w) + # Clip (avoid values our of range 0-1) + z = clip_extreme(z) + + + # 2 steps for the blues + # Interpolate missing blues at the red locations + for i in range (0, X, 2): + for j in range (1, Y, 2): + + # Find neigbours + green + n = neigh(i, j, blue, z) + n_G = neigh(i, j, green, z) + d = derivat(n_G) + w = weight_fct(i, j, n, d, green, z) + # Blue interpolation (4 corners) + z[i,j,2] = interpol_red_blue(n, w, n_G) + # Interpolate missing blues at the green locations + for i in range (X): + for j in range (Y): + # Control if value 0 in blue channel + if z[i,j,2] == 0: + + # Find neigbours + n_B = neigh(i, j, blue, z) + d = derivat(n_B) + w = weight_fct(i, j, n_B, d, blue, z) + # Blue interpolation (cross) + z[i,j,2] = interpol_green(n_B,w) + z = clip_extreme(z) + + + # 2 steps for the reds + # Interpolate missing reds at the blue locations + for i in range (1, X, 2): + for j in range (0, Y, 2): + + # Find neigbours + green + n = neigh(i, j, red, z) + n_G = neigh(i, j, green, z) + d = derivat(n_G) + w = weight_fct(i, j, n_G, d, green, z) + # Red interpolation (4 corners) + z[i,j,0] = interpol_red_blue(n, w, n_G) + # Interpolate missing reds at the green locations + for i in range (X): + for j in range (Y): + # Control if value 0 in red channel + if z[i,j,0] == 0: + + # Find neigbours + n_R = neigh(i, j, red, z) + d = derivat(n_R) + w = weight_fct(i, j, n_R, d, red, z) + # Red interpolation (cross) + z[i,j,0] = interpol_green(n_R,w) + z = clip_extreme(z) + + + # Correction step (repeated 3 times) + if correction == 1: + for i in range(3): + + n_G[4] = green_correction(n_R, n_G, n_B, w) + n_B[4] = blue_correction(n_G, n_B, w) + n_R[4] = red_correction(n_R, n_G, w) + # nan + + #print(f'Image reconstructed shape: {z.shape}') # 1024, 1024, 3 - return np.zeros(op.input_shape) + return z ####