Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • samanost/sicom_image_analysis_project
  • gerayelk/sicom_image_analysis_project
  • jelassiy/sicom_image_analysis_project
  • chardoto/sicom_image_analysis_project
  • chaarim/sicom_image_analysis_project
  • domers/sicom_image_analysis_project
  • elmurrt/sicom_image_analysis_project
  • sadonest/sicom_image_analysis_project
  • kouddann/sicom_image_analysis_project
  • mirabitj/sicom-image-analysis-project-mirabito
  • plotj/sicom_image_analysis_project
  • torrem/sicom-image-analysis-project-maxime-torre
  • dzike/sicom_image_analysis_project
  • daip/sicom_image_analysis_project
  • casanovv/sicom_image_analysis_project
  • girmarti/sicom_image_analysis_project
  • lioretn/sicom_image_analysis_project
  • lemoinje/sicom_image_analysis_project
  • ouahmanf/sicom_image_analysis_project
  • vouilloa/sicom_image_analysis_project
  • diopb/sicom_image_analysis_project
  • davidale/sicom_image_analysis_project
  • enza/sicom_image_analysis_project
  • conversb/sicom_image_analysis_project
  • mullemat/sicom_image_analysis_project
25 results
Show changes
Showing
with 2193 additions and 0 deletions
%% Cell type:code id: tags:
```
# from google.colab import drive
# drive.mount('/content/drive')
```
%% Output
Mounted at /content/drive
%% Cell type:code id: tags:
```
# Download and unzip (2.2GB)
# !wget http://data.csail.mit.edu/places/places205/testSetPlaces205_resize.tar.gz
# !mkdir images/
# !tar -xzf drive/MyDrive/testSetPlaces205_resize.tar.gz -C 'images/'
```
%% Cell type:code id: tags:
```
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.transforms as transforms
from tqdm import tqdm
from network import ColorizeNet
from utils import GrayscaleImageFolder
```
%% Cell type:code id: tags:
```
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
```
%% Cell type:code id: tags:
```
transform = transforms.Compose([
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip()
])
train_set = GrayscaleImageFolder('images/', transform=transform)
train_loader = torch.utils.data.DataLoader(train_set, batch_size=128, shuffle=True, num_workers=4)
```
%% Cell type:code id: tags:
```
criterion = nn.MSELoss()
model = ColorizeNet().to(device)
optimizer = optim.Adam(model.parameters(), lr=0.001)
# scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=[8, 24], gamma=1/3)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=1/3, patience=5, verbose=True)
```
%% Cell type:code id: tags:
```
try:
checkpoint = torch.load('drive/MyDrive/checkpoint_0.001.pth', map_location=device)
start_epoch = checkpoint['next_epoch']
model.load_state_dict(checkpoint['model'])
optimizer.load_state_dict(checkpoint['optimizer'])
scheduler.load_state_dict(checkpoint['scheduler'])
except:
start_epoch = 0
```
%% Cell type:code id: tags:
```
num_epochs = 64
for epoch in range(start_epoch, num_epochs):
train_loss = 0
loop = tqdm(train_loader)
for batch in loop:
in_gray, in_ab = batch[0].to(device), batch[1].to(device)
out_ab = model(in_gray)
loss = criterion(out_ab, in_ab)
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_loss += loss.item()*in_gray.size(0)
loop.set_description(f'Epoch [{epoch+1:2d}/{num_epochs}]')
loop.set_postfix(loss=train_loss)
scheduler.step(train_loss)
checkpoint = {
'next_epoch': epoch + 1,
'model': model.state_dict(),
'optimizer': optimizer.state_dict(),
'scheduler': scheduler.state_dict()
}
torch.save(checkpoint, f'drive/MyDrive/checkpoint_{scheduler._last_lr[0]}.pth')
```
%% Output
Epoch [39/64]: 100%|██████████| 321/321 [12:00<00:00, 2.24s/it, loss=97.4]
Epoch [40/64]: 100%|██████████| 321/321 [11:30<00:00, 2.15s/it, loss=98]
Epoch [41/64]: 100%|██████████| 321/321 [11:18<00:00, 2.12s/it, loss=97.4]
Epoch [42/64]: 100%|██████████| 321/321 [11:28<00:00, 2.15s/it, loss=103]
Epoch [43/64]: 100%|██████████| 321/321 [11:23<00:00, 2.13s/it, loss=98.4]
Epoch [44/64]: 100%|██████████| 321/321 [11:15<00:00, 2.10s/it, loss=97.3]
Epoch [45/64]: 100%|██████████| 321/321 [11:29<00:00, 2.15s/it, loss=97.6]
Epoch [46/64]: 100%|██████████| 321/321 [11:25<00:00, 2.14s/it, loss=96.5]
Epoch [47/64]: 100%|██████████| 321/321 [11:40<00:00, 2.18s/it, loss=96.7]
Epoch [48/64]: 100%|██████████| 321/321 [11:44<00:00, 2.19s/it, loss=96.3]
Epoch [49/64]: 100%|██████████| 321/321 [11:13<00:00, 2.10s/it, loss=96.5]
Epoch [50/64]: 100%|██████████| 321/321 [11:09<00:00, 2.09s/it, loss=96.4]
Epoch [51/64]: 100%|██████████| 321/321 [11:09<00:00, 2.09s/it, loss=95.7]
Epoch [52/64]: 100%|██████████| 321/321 [11:13<00:00, 2.10s/it, loss=95.9]
Epoch [53/64]: 100%|██████████| 321/321 [11:08<00:00, 2.08s/it, loss=95.8]
Epoch [54/64]: 100%|██████████| 321/321 [11:31<00:00, 2.15s/it, loss=95.4]
Epoch [55/64]: 100%|██████████| 321/321 [11:54<00:00, 2.23s/it, loss=95.5]
Epoch [56/64]: 100%|██████████| 321/321 [12:11<00:00, 2.28s/it, loss=95.5]
Epoch [57/64]: 100%|██████████| 321/321 [12:00<00:00, 2.24s/it, loss=94.9]
Epoch [58/64]: 100%|██████████| 321/321 [11:54<00:00, 2.23s/it, loss=94.9]
Epoch [59/64]: 100%|██████████| 321/321 [11:56<00:00, 2.23s/it, loss=94.9]
Epoch [60/64]: 100%|██████████| 321/321 [12:06<00:00, 2.26s/it, loss=94.3]
Epoch [61/64]: 100%|██████████| 321/321 [12:03<00:00, 2.26s/it, loss=95]
Epoch [62/64]: 100%|██████████| 321/321 [12:01<00:00, 2.25s/it, loss=95]
Epoch [63/64]: 100%|██████████| 321/321 [12:05<00:00, 2.26s/it, loss=96.7]
Epoch [64/64]: 100%|██████████| 321/321 [12:10<00:00, 2.27s/it, loss=95]
%% Cell type:code id: tags:
```
torch.save(model.state_dict(), f'./models/model.pth') # save the model separately from the checkpoint file
```
import torch
from torchvision import transforms, datasets
import numpy as np
from PIL import Image
from skimage.color import rgb2lab, rgb2gray, lab2rgb
def count_params(model):
'''
returns the number of trainable parameters in some model
'''
return sum(p.numel() for p in model.parameters() if p.requires_grad)
class GrayscaleImageFolder(datasets.ImageFolder):
'''
Custom dataloader for various operations on images before loading them.
'''
def __getitem__(self, index):
path, target = self.imgs[index]
img = self.loader(path)
if self.transform is not None:
img_orig = self.transform(img) # apply transforms
img_orig = np.asarray(img_orig) # convert to numpy array
img_lab = rgb2lab(img_orig) # convert RGB image to LAB
img_ab = img_lab[:, :, 1:3] # separate AB channels from LAB
img_ab = (img_ab + 128) / 255 # normalize the pixel values
# transpose image from HxWxC to CxHxW and turn it into a tensor
img_ab = torch.from_numpy(img_ab.transpose((2, 0, 1))).float()
img_orig = rgb2gray(img_orig) # convert RGB to grayscale
# add a channel axis to grascale image and turn it into a tensor
img_orig = torch.from_numpy(img_orig).unsqueeze(0).float()
if self.target_transform is not None:
target = self.target_transform(target)
return img_orig, img_ab, target
def load_gray(path, max_size=360, shape=None):
'''
load an image as grayscale, change the shape as per input,
perform transformations and convert it to model compatable shape.
'''
img_gray = Image.open(path).convert('L')
if max(img_gray.size) > max_size:
size = max_size
else:
size = max(img_gray.size)
if shape is not None:
size = shape
img_transform = transforms.Compose([
transforms.Resize(size),
transforms.ToTensor()
])
img_gray = img_transform(img_gray).unsqueeze(0)
return img_gray
def to_rgb(img_l, img_ab):
'''
concatinates Lightness (grayscale) and AB channels,
and converts the resulting LAB image to RGB
'''
if img_l.shape == img_ab.shape:
img_lab = torch.cat((img_l, img_ab), 1).numpy().squeeze()
else:
img_lab = torch.cat(
(img_l, img_ab[:, :, :img_l.size(2), :img_l.size(3)]),
dim=1
).numpy().squeeze()
img_lab = img_lab.transpose(1, 2, 0) # transpose image to HxWxC
img_lab[:, :, 0] = img_lab[:, :, 0] * 100 # range pixel values from 0-100
img_lab[:, :, 1:] = img_lab[:, :, 1:] * 255 - 128 # un-normalize
img_rgb = lab2rgb(img_lab.astype(np.float64)) # convert LAB image to RGB
return img_rgb
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Tue Jan 30 19:35:12 2024
@author: stani1
"""
"""The main file for the baseline reconstruction.
This file should NOT be modified.
"""
import numpy as np
from PIL import Image
import subprocess
import os
from src.forward_model import CFA
from src.methods.baseline.demo_reconstruction import naive_interpolation
def run_reconstruction(y: np.ndarray, cfa: str) -> np.ndarray:
"""Performs demosaicking on y.
Args:
y (np.ndarray): Mosaicked image to be reconstructed.
cfa (str): Name of the CFA. Can be bayer or quad_bayer.
Returns:
np.ndarray: Demosaicked image.
"""
current_directory = os.getcwd() #we take the current directory to be sure we will have the correct one
input_path='src/methods/domer/image-colorization/input/0.jpg'
input_path = os.path.join(current_directory, input_path)#location to store the image
output_path='src/methods/domer/image-colorization/output/0.jpg'
output_path = os.path.join(current_directory, output_path)
# venv_path='src/methods/domer/image-colorization/image-colorization/bin/activate'
# venv_path = os.path.join(current_directory, venv_path) #activation of the venv, but no need for a lot of versions of libraries
y2=Image.fromarray(y)
y2=y2.convert('L')
y2.save(input_path)
resolution=np.shape(y)[0]
# activation_venv = ['source ', venv_path]
# subprocess.run(activation_venv, shell=True)
n=y2.size[0]
# for i in range (n) :
# for j in range (n) :
# y2.putpixel((i,j),y.getpixel((i,j))*255)
command = [
'python', 'src/methods/domer/image-colorization/colorize.py',
'-i', input_path,
'-o', output_path,
'-r', str(resolution)
]
subprocess.run(command) #calling of the NN with the location of the input and output and the resolution
# deactivation_venv = f"deactivate"
# subprocess.run(deactivation_venv, shell=True)
res=Image.open(output_path) #We take the result of the NN
res=np.asarray(res)#reseting the result in a numpy array
return res
File added
source diff could not be displayed: it is too large. Options to address this: view the blob.
import numpy as np
from src.forward_model import CFA
def HQ_interpolation(op: CFA, y: np.ndarray) -> np.ndarray:
"""Performs a simple interpolation of the lost pixels.
Args:
op (CFA): CFA operator.
y (np.ndarray): Mosaicked image.
Returns:
np.ndarray: Demosaicked image.
"""
z = op.adjoint(y)
if op.cfa == 'bayer':
res = np.empty(op.input_shape)
res[:, :, 0] = varying_kernel_convolution_HQ(y[:, :], K_red_list)
res[:, :, 1] = varying_kernel_convolution_HQ(y[:, :], K_green_list)
res[:, :, 2] = varying_kernel_convolution_HQ(y[:, :], K_blue_list)
else:
res = np.empty(op.input_shape)
bayer_y = quad_bayer_to_bayer(y)
res[:, :, 0] = varying_kernel_convolution_HQ(bayer_y[:, :], K_red_list)
res[:, :, 1] = varying_kernel_convolution_HQ(bayer_y[:, :], K_green_list)
res[:, :, 2] = varying_kernel_convolution_HQ(bayer_y[:, :], K_blue_list)
return res
def extract_padded(M, size, i, j):
N_i, N_j = M.shape
res = np.zeros((size, size))
middle_size = int((size - 1) / 2)
for ii in range(- middle_size, middle_size + 1):
for jj in range(- middle_size, middle_size + 1):
if i + ii >= 0 and i + ii < N_i and j + jj >= 0 and j + jj < N_j:
res[middle_size + ii, middle_size + jj] = M[i + ii, j + jj]
return res
def varying_kernel_convolution(M, K_list):
N_i, N_j = M.shape
res = np.zeros_like(M)
for i in range(N_i):
for j in range(N_j):
res[i, j] = np.sum(extract_padded(M, K_list[4 * (i % 4) + j % 4].shape[0], i, j) * K_list[4 * (i % 4) + j % 4])
np.clip(res, 0, 1, res)
return res
def varying_kernel_convolution_HQ(M, K_list):
res = np.zeros_like(M)
M_padded = np.pad(M,2,"constant",constant_values=0)
N_i, N_j = M_padded.shape
for i in range(2,N_i-2):
for j in range(2,N_j-2):
res[i-2, j-2] = np.sum(extract_padded(M_padded, 5, i ,j) * K_list[2* (i % 2) + j % 2])
np.clip(res, 0, 1, res)
return res
def quad_bayer_to_bayer(y):
res = y
N_i, N_j = y.shape
for j in range(int(N_j/4)):
buff = res[:,j*4+1]
res[:,j*4+1] = res[:,j*4+2]
res[:,j*4+2] = buff
for i in range(int(N_i/4)):
buff = res[i*4+1,:]
res[i*4+1,:] = res[i*4+2,:]
res[i*4+2,:] = buff
for i in range(int(N_i/4)):
for j in range(int(N_j/4)):
buff = res[i*4+2,j*4+1]
res[i*4+2,j*4+1] = res[i*4+1,j*4+2]
res[i*4+1,j*4+2] = buff
return res
K_identity = np.zeros((5,5))
K_identity[2,2] = 1
K_g_r_and_b = np.array([[0, 0, -1, 0, 0], [0, 0, 2, 0, 0], [-1, 2, 4, 2, -1], [0, 0, 2, 0, 0], [0, 0, -1, 0, 0]]) / 8
K_r_and_b_g_rrow_bcol = np.array([[0, 0, 1/2, 0, 0], [0, -1, 0, -1, 0], [-1, 4, 5, 4, -1], [0, -1, 0, -1, 0], [0, 0, 1/2, 0, 0]]) / 8
K_r_and_b_g_brow_rcol = K_r_and_b_g_rrow_bcol.T
K_r_b = np.array([[0, 0, -3/2, 0, 0], [0, 2, 0, 2, 0], [-3/2, 0, 6, 0, -3/2], [0, 2, 0, 2, 0], [0, 0, -3/2, 0, 0]]) / 8
K_green_list = [K_identity,
K_g_r_and_b,
K_g_r_and_b,
K_identity]
K_red_list = [K_r_and_b_g_rrow_bcol,
K_identity,
K_r_b,
K_r_and_b_g_brow_rcol]
K_blue_list = [K_r_and_b_g_brow_rcol,
K_r_b,
K_identity,
K_r_and_b_g_rrow_bcol]
\ No newline at end of file
import numpy as np
from src.forward_model import CFA
from src.methods.girardey.methods import HQ_interpolation
def run_reconstruction_HQ(y: np.ndarray, cfa: str) -> np.ndarray:
"""Performs demosaicking on y.
Args:
y (np.ndarray): Mosaicked image to be reconstructed.
cfa (str): Name of the CFA. Can be bayer or quad_bayer.
Returns:
np.ndarray: Demosaicked image.
"""
input_shape = (y.shape[0], y.shape[1], 3)
op = CFA(cfa, input_shape)
res = HQ_interpolation(op, y)
return res
File added
import numpy as np
from scipy import signal
from src.forward_model import CFA
def compute_gradient(channel, points):
""" Compute the gradient for the channel based on the specified points. """
gradient = np.zeros_like(channel)
for point in points:
gradient += np.roll(channel, shift=point, axis=(0, 1))
gradient /= len(points)
gradient -= channel
return gradient
def gradient_correction_interpolation(op: CFA, z: np.ndarray,alpha=0.5, beta=0.5, gamma=0.5) -> np.ndarray:
# defining bilinear interpolation filters for the different channels (bayer pattern case)
bilinear_filter_red = np.array([[1, 2, 1], [2, 4, 2], [1, 2, 1]]) / 4
bilinear_filter_green = np.array([[0, 1, 0], [1, 4, 1], [0, 1, 0]]) / 4
bilinear_filter_blue = np.array([[1, 2, 1], [2, 4, 2], [1, 2, 1]]) / 4
interpolated_img = np.empty(op.input_shape)
# applying the convolution product with the bilinear filters
interpolated_img[:, :, 0] = signal.convolve2d(z[:, :, 0], bilinear_filter_red, boundary='symm',mode='same')
interpolated_img[:, :, 1] = signal.convolve2d(z[:, :, 1], bilinear_filter_green, boundary='symm',mode='same')
interpolated_img[:, :, 2] = signal.convolve2d(z[:, :, 2], bilinear_filter_blue, boundary='symm', mode='same')
""" Applying gradient correction to the bilinearly interpolated image. """
R = interpolated_img[:, :, 0]
G = interpolated_img[:, :, 1]
B = interpolated_img[:, :, 2]
# computing gradients for R and B channels
points_R = [(0, -2), (0, 2), (-2, 0), (2, 0)]
points_B = [(-1, -1), (-1, 1), (1, -1), (1, 1), (0, 0)]
grad_R = compute_gradient(R, points_R)
grad_B = compute_gradient(B, points_B)
G_corrected = np.copy(G)
G_corrected[0::2, 0::2] = G[0::2, 0::2] + alpha * grad_R[0::2, 0::2]
G_corrected[1::2, 1::2] = G[1::2, 1::2] + gamma * grad_B[1::2, 1::2]
# correction of R at G locations and B at G locations
R_corrected = np.copy(R)
B_corrected = np.copy(B)
R_corrected[1::2, 0::2] = R[1::2, 0::2] + beta * grad_R[1::2, 0::2]
R_corrected[0::2, 1::2] = R[0::2, 1::2] + beta * grad_R[0::2, 1::2]
B_corrected[1::2, 0::2] = B[1::2, 0::2] + beta * grad_B[1::2, 0::2]
B_corrected[0::2, 1::2] = B[0::2, 1::2] + beta * grad_B[0::2, 1::2]
corrected_image = np.stack((R_corrected, G_corrected, B_corrected), axis=-1)
return corrected_image
def swapping(op, y):
"""
Convert both the CFA operator's mask and an image from Quad Bayer to Bayer by swapping method.
Args:
op: CFA operator.
y (np.ndarray): Mosaicked image.
Returns:
tuple: A tuple containing the updated CFA operator and the converted image.
"""
if op.cfa == 'quad_bayer':
# swapping 2 columns every 2 columns
op.mask[:, 1::4], op.mask[:, 2::4] = op.mask[:, 2::4].copy(), op.mask[:, 1::4].copy()
# swapping 2 lines every 2 lines
op.mask[1::4, :], op.mask[2::4, :] = op.mask[2::4, :].copy(), op.mask[1::4, :].copy()
# swapping the diagonal pairs
op.mask[1::4, 1::4], op.mask[2::4, 2::4] = op.mask[2::4, 2::4].copy(), op.mask[1::4, 1::4].copy()
converted_image = y.copy()
converted_image[:, 1::4], converted_image[:, 2::4] = converted_image[:, 2::4].copy(), converted_image[:, 1::4].copy()
converted_image[1::4, :], converted_image[2::4, :] = converted_image[2::4, :].copy(), converted_image[1::4, :].copy()
converted_image[1::4, 1::4], converted_image[2::4, 2::4] = converted_image[2::4, 2::4].copy(), converted_image[1::4, 1::4].copy()
# updating to bayer pattern
op.cfa = 'bayer'
return op, converted_image
\ No newline at end of file
import numpy as np
from src.methods.inssaf_cherki.other_functions import gradient_correction_interpolation, swapping, compute_gradient
from src.forward_model import CFA
def run_reconstruction(y: np.ndarray, cfa: str) -> np.ndarray:
"""Performs demosaicking on y.
Args:
y (np.ndarray): Mosaicked image to be reconstructed.
cfa (str): Name of the CFA. Can be bayer or quad_bayer.
Returns:
np.ndarray: Demosaicked image.
"""
# Performing the reconstruction.
input_shape = (y.shape[0], y.shape[1], 3)
op = CFA(cfa, input_shape)
if op.cfa == 'quad_bayer':
op, y = swapping(op, y)
z = op.adjoint(y)
reconstructed_image = gradient_correction_interpolation(op,z,alpha=0.5, beta=0.5, gamma=0.5)
return reconstructed_image
####
####
####
#### #### #### #############
#### ###### #### ##################
#### ######## #### ####################
#### ########## #### #### ########
#### ############ #### #### ####
#### #### ######## #### #### ####
#### #### ######## #### #### ####
#### #### ######## #### #### ####
#### #### ## ###### #### #### ######
#### #### #### ## #### #### ############
#### #### ###### #### #### ##########
#### #### ########## #### #### ########
#### #### ######## #### ####
#### #### ############ ####
#### #### ########## ####
#### #### ######## ####
#### #### ###### ####
# 2023
# Authors: Mauro Dalla Mura and Matthieu Muller
File added
"""The main file for the reconstruction.
This file should NOT be modified except the body of the 'run_reconstruction' function.
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.khattabi.utils import HA
def run_reconstruction(y: np.ndarray, cfa: str) -> np.ndarray:
"""Performs demosaicking on y.
Args:
y (np.ndarray): Mosaicked image to be reconstructed.
cfa (str): Name of the CFA. Can be bayer or quad_bayer.
Returns:
np.ndarray: Demosaicked image.
"""
# Performing the reconstruction.
# TODO
input_shape = (y.shape[0], y.shape[1], 3)
op = CFA(cfa, input_shape)
y_adjoint=op.adjoint(y)
res = HA(y_adjoint)
return res
####
####
####
#### #### #### #############
#### ###### #### ##################
#### ######## #### ####################
#### ########## #### #### ########
#### ############ #### #### ####
#### #### ######## #### #### ####
#### #### ######## #### #### ####
#### #### ######## #### #### ####
#### #### ## ###### #### #### ######
#### #### #### ## #### #### ############
#### #### ###### #### #### ##########
#### #### ########## #### #### ########
#### #### ######## #### ####
#### #### ############ ####
#### #### ########## ####
#### #### ######## ####
#### #### ###### ####
# 2023
# Authors: Mauro Dalla Mura and Matthieu Muller
"""A file containing a (pretty useless) reconstruction.
It serves as example of how the project works.
This file should NOT be modified.
"""
import numpy as np
from scipy.signal import convolve2d
from src.forward_model import CFA
def HA(y_adjoint):
height, width = y_adjoint.shape[:2]
# Green channel interpolation
for i in range(2 , height-2):
for j in range(2, width-2):
# Red pixels
if y_adjoint[i,j,1] == 0 and y_adjoint[i,j,0] != 0:
grad_y = np.abs(y_adjoint[i-1,j,1] - y_adjoint[i+1,j,1]) + np.abs(2*y_adjoint[i,j,0] - y_adjoint[i-2,j,0] - y_adjoint[i+2,j,0])
grad_x = np.abs(y_adjoint[i,j-1,1] - y_adjoint[i,j+1,1]) + np.abs(2*y_adjoint[i,j,0] - y_adjoint[i,j-2,0] - y_adjoint[i,j+2,0])
if grad_x < grad_y:
y_adjoint[i,j,1] = (y_adjoint[i,j-1,1] + y_adjoint[i,j+1,1])/2 + (2*y_adjoint[i,j,0] - y_adjoint[i,j-2,0] - y_adjoint[i,j+2,0])/4
elif grad_x > grad_y:
y_adjoint[i,j,1] = (y_adjoint[i-1,j,1] + y_adjoint[i+1,j,1])/2 + (2*y_adjoint[i,j,0] - y_adjoint[i-2,j,0] - y_adjoint[i+2,j,0])/4
else:
y_adjoint[i,j,1] = (y_adjoint[i-1,j,1] + y_adjoint[i+1,j,1] + y_adjoint[i,j-1,1] + y_adjoint[i,j+1,1])/4 + \
(2*y_adjoint[i,j,0] - y_adjoint[i,j-2,0] - y_adjoint[i,j+2,0] + 2*y_adjoint[i,j,0] - y_adjoint[i-2,j,0] - y_adjoint[i+2,j,0])/8
# Blue Pixels
elif y_adjoint[i,j,1] == 0 and y_adjoint[i,j,2] != 0:
grad_y = np.abs(y_adjoint[i-1,j,1] - y_adjoint[i+1,j,1]) + np.abs(2*y_adjoint[i,j,2] - y_adjoint[i-2,j,2] - y_adjoint[i+2,j,2])
grad_x = np.abs(y_adjoint[i,j-1,1] - y_adjoint[i,j+1,1]) + np.abs(2*y_adjoint[i,j,2] - y_adjoint[i,j-2,2] - y_adjoint[i,j+2,2])
if grad_x < grad_y:
y_adjoint[i,j,1] = (y_adjoint[i,j-1,1] + y_adjoint[i,j+1,1])/2 + (2*y_adjoint[i,j,2] - y_adjoint[i,j-2,2] - y_adjoint[i,j+2,2])/4
elif grad_x > grad_y:
y_adjoint[i,j,1] = (y_adjoint[i-1,j,1] + y_adjoint[i+1,j,1])/2 + (2*y_adjoint[i,j,2] - y_adjoint[i-2,j,2] - y_adjoint[i+2,j,2])/4
else:
y_adjoint[i,j,1] = (y_adjoint[i-1,j,1] + y_adjoint[i+1,j,1] + y_adjoint[i,j-1,1] + y_adjoint[i,j+1,1])/4 + \
(2*y_adjoint[i,j,2] - y_adjoint[i,j-2,2] - y_adjoint[i,j+2,2] + 2*y_adjoint[i,j,2] - y_adjoint[i-2,j,2] - y_adjoint[i+2,j,2])/8
# Red and blue channel interpolation
for i in range(1 , height-1):
for j in range(1, width-1):
if y_adjoint[i,j,2] != 0 :
y_adjoint[i,j,0] = (y_adjoint[i-1,j-1,0] + y_adjoint[i-1,j+1,0] + y_adjoint[i+1,j-1,0] + y_adjoint[i+1,j+1,0]) / 4
elif y_adjoint[i,j,0] != 0:
y_adjoint[i,j,2] = (y_adjoint[i-1,j-1,2] + y_adjoint[i-1,j+1,2] + y_adjoint[i+1,j-1,2] + y_adjoint[i+1,j+1,2]) / 4
else:
y_adjoint[i,j] = y_adjoint[i,j]
for i in range(1 , height-1):
for j in range(1, width-1):
if y_adjoint[i,j,0] == y_adjoint[i,j,2]:
y_adjoint[i,j,0] = (y_adjoint[i-1,j,0] + y_adjoint[i,j-1,0] + y_adjoint[i+1,j,0] + y_adjoint[i,j+1,0]) / 4
y_adjoint[i,j,2] = (y_adjoint[i-1,j,2] + y_adjoint[i,j-1,2] + y_adjoint[i+1,j,2] + y_adjoint[i,j+1,2]) / 4
return y_adjoint
####
####
####
#### #### #### #############
#### ###### #### ##################
#### ######## #### ####################
#### ########## #### #### ########
#### ############ #### #### ####
#### #### ######## #### #### ####
#### #### ######## #### #### ####
#### #### ######## #### #### ####
#### #### ## ###### #### #### ######
#### #### #### ## #### #### ############
#### #### ###### #### #### ##########
#### #### ########## #### #### ########
#### #### ######## #### ####
#### #### ############ ####
#### #### ########## ####
#### #### ######## ####
#### #### ###### ####
# 2023
# Authors: Mauro Dalla Mura and Matthieu Muller
File added
"""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
"""The main file for the reconstruction.
This file should NOT be modified except the body of the 'run_reconstruction' function.
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:
"""Performs demosaicking on y.
Args:
y (np.ndarray): Mosaicked image to be reconstructed.
cfa (str): Name of the CFA. Can be bayer or quad_bayer.
Returns:
np.ndarray: Demosaicked image.
"""
#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 z
####
####
####
#### #### #### #############
#### ###### #### ##################
#### ######## #### ####################
#### ########## #### #### ########
#### ############ #### #### ####
#### #### ######## #### #### ####
#### #### ######## #### #### ####
#### #### ######## #### #### ####
#### #### ## ###### #### #### ######
#### #### #### ## #### #### ############
#### #### ###### #### #### ##########
#### #### ########## #### #### ########
#### #### ######## #### ####
#### #### ############ ####
#### #### ########## ####
#### #### ######## ####
#### #### ###### ####
# 2023
# Authors: Mauro Dalla Mura and Matthieu Muller
File added
"""The main file for the reconstruction.
This file should NOT be modified except the body of the 'run_reconstruction' function.
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 scipy.signal import medfilt2d
def freeman_median_demosaicking(op: CFA, y: np.ndarray) -> np.ndarray:
"""Performs the Freeman's method with median for demosaicking.
Args:
op (CFA): CFA operator.
y (np.ndarray): Mosaicked image.
Returns:
np.ndarray: Demosaicked image.
"""
z = op.adjoint(y)
res = np.empty(op.input_shape)
mask_r = np.zeros_like(z[:, :, 0], dtype = float)
mask_g = np.zeros_like(z[:, :, 1], dtype = float)
mask_b = np.zeros_like(z[:, :, 2], dtype = float)
D_rg = z[:, :, 0] - z[:, :, 1]
M1 = medfilt2d(D_rg, kernel_size=5)
D_gb = z[:, :, 1] - z[:, :, 2]
M2 = medfilt2d(D_gb, kernel_size=5)
D_rb = z[:, :, 0] - z[:, :, 2]
M3 = medfilt2d(D_rb, kernel_size=5)
res[:, :, 0] = z[:, :, 0] + (M1 * mask_g + z[:, :, 1]) + (M3 * mask_b + z[:, :, 2])
res[:, :, 1] = z[:, :, 1] + (z[:, :, 0] - M1 * mask_r) + (M2 * mask_b + z[:, :, 2])
res[:, :, 2] = z[:, :, 2] + (z[:, :, 1] - M2 * mask_g) + (z[:, :, 0] - M3 * mask_r)
return res
def run_reconstruction(y: np.ndarray, cfa: str) -> np.ndarray:
"""Performs demosaicking on y.
Args:
y (np.ndarray): Mosaicked image to be reconstructed.
cfa (str): Name of the CFA. Can be bayer or quad_bayer.
Returns:
np.ndarray: Demosaicked image.
"""
input_shape = (y.shape[0], y.shape[1], 3)
op = CFA(cfa, input_shape)
res = freeman_median_demosaicking(op, y)
return res
####
####
####
#### #### #### #############
#### ###### #### ##################
#### ######## #### ####################
#### ########## #### #### ########
#### ############ #### #### ####
#### #### ######## #### #### ####
#### #### ######## #### #### ####
#### #### ######## #### #### ####
#### #### ## ###### #### #### ######
#### #### #### ## #### #### ############
#### #### ###### #### #### ##########
#### #### ########## #### #### ########
#### #### ######## #### ####
#### #### ############ ####
#### #### ########## ####
#### #### ######## ####
#### #### ###### ####
# 2023
# Authors: Mauro Dalla Mura and Matthieu Muller
"""A file containing the Malvar et al. reconstruction.
"""
import matplotlib.pyplot as plt
import numpy as np
from src.forward_model import CFA
from scipy.ndimage.filters import convolve
# Masks definition
M0 = np.multiply(1/8,[ # Recover G at R and B locations
[ 0, 0, -1, 0, 0],
[ 0, 0, 2, 0, 0],
[-1, 2, 4, 2, -1],
[ 0, 0, 2, 0, 0],
[ 0, 0, -1, 0, 0]
] )
M1 = np.multiply(1/8,[ # Recover R at G in R row, B col; Recover B at G in B row, R col
[ 0, 0, 1/2, 0, 0],
[ 0, -1, 0, -1, 0],
[-1, 4, 5, 4, -1],
[ 0, -1, 0, -1, 0],
[ 0, 0, 1/2, 0, 0]
] )
M2 = M1.T # Recover R at G in B row, R col; Recover B at G in R row, B col
M3 = np.multiply(1/8,[ # Recover R at B in B row, B col; Recover B at R in R row, R col
[ 0, 0, -3/2, 0, 0],
[ 0, 2, 0, 2, 0],
[-3/2, 0, 6, 0, -3/2],
[ 0, 2, 0, 2, 0],
[ 0, 0, -3/2, 0, 0]
] )
def run_reconstruction(y: np.ndarray, cfa: str) -> np.ndarray:
"""Performs a high quality linear interpolation of the lost pixels from Malvar et al's (2004) paper.
Args:
op (CFA): CFA operator.
y (np.ndarray): Mosaicked image.
Returns:
np.ndarray: Demosaicked image.
"""
input_shape = (y.shape[0], y.shape[1], 3)
op = CFA(cfa, input_shape)
# If CFA is Quadratic it needs be transformed into simple Bayer pattern
if op.cfa == 'quad_bayer':
op.mask, y = adapt_quad(op, y)
# Applies the adjoint operation to y
z = op.adjoint(y)
# Construct the CFA masks (R,G,B) of z
R = np.where(z[:,:,0] != 0., 1, 0) # Red
G = np.where(z[:,:,1] != 0., 1, 0) # Green
B = np.where(z[:,:,2] != 0., 1, 0) # Blue
# R, G, B channels of the image to be reconstructed
R_demosaic = z[:,:,0]
G_demosaic = z[:,:,1]
B_demosaic = z[:,:,2]
# Padding to recover the edges
y_pad = np.pad(y, 2, 'constant', constant_values = 0)
# Recover red and blue columns/rows
# Red rows
Rr = np.any(R == 1, axis = 1)[None].T * np.ones(R.shape)
# Red columns
Rc = np.any(R == 1, axis = 0)[None] * np.ones(R.shape)
# Blue rows
Br = np.any(B == 1, axis = 1)[None].T * np.ones(B.shape)
# Blue columns
Bc = np.any(B == 1, axis = 0)[None] * np.ones(B.shape)
# Recover the lost pixels
# Red channel
R_demosaic = np.where(np.logical_and(Rr == 1, Bc == 1), convolve(y_pad[2:y_pad.shape[0]-2,2:y_pad.shape[1]-2], M1), R_demosaic )
R_demosaic = np.where(np.logical_and(Br == 1, Rc == 1), convolve(y_pad[2:y_pad.shape[0]-2,2:y_pad.shape[1]-2], M2), R_demosaic )
R_demosaic = np.where(np.logical_and(Br == 1, Bc == 1), convolve(y_pad[2:y_pad.shape[0]-2,2:y_pad.shape[1]-2], M3), R_demosaic )
# Green channel
G_demosaic = np.where(np.logical_or(R == 1, B == 1), convolve(y_pad[2:y_pad.shape[0]-2,2:y_pad.shape[1]-2], M0), G_demosaic )
# Blue channel
B_demosaic = np.where(np.logical_and(Br == 1, Rc == 1), convolve(y_pad[2:y_pad.shape[0]-2,2:y_pad.shape[1]-2], M1), B_demosaic )
B_demosaic = np.where(np.logical_and(Rr == 1, Bc == 1), convolve(y_pad[2:y_pad.shape[0]-2,2:y_pad.shape[1]-2], M2), B_demosaic )
B_demosaic = np.where(np.logical_and(Rr == 1, Rc == 1), convolve(y_pad[2:y_pad.shape[0]-2,2:y_pad.shape[1]-2], M3), B_demosaic )
# Stack the 3 channels
demosaiced = np.stack((R_demosaic, G_demosaic, B_demosaic), axis=2)
return np.clip(demosaiced, 0, 1, demosaiced)
def adapt_quad(op: CFA, y: np.ndarray):
"""Performs an adaptation of the quadratic bayer pattern to a simple bayer pattern.
Args:
op (CFA): CFA operator with Quadratic Bayer pattern.
y (np.ndarray): Mosaicked image.
Returns:
bayer (np.ndarray): CFA operator with simple Bayer pattern.
new (np.ndarray): Demosaicked image re-arranged.
"""
L, l, d = op.mask.shape
bayer = op.mask.copy()
new = y.copy()
# Swap 2 columns every 2 columns
for j in range(1, l, 4):
bayer[:,j], bayer[:,j+1] = bayer[:,j+1].copy(), bayer[:,j].copy()
new[:,j], new[:,j+1] = new[:,j+1].copy(), new[:,j].copy()
# Swap 2 lines every 2 lines
for i in range(1, L, 4):
bayer[i, :], bayer[i+1,:] = bayer[i+1,:].copy(), bayer[i,:].copy()
new[i, :], new[i+1,:] = new[i+1,:].copy(), new[i,:].copy()
# Swap back some diagonal greens
for i in range(1, L, 4):
for j in range(1, 1, 4):
bayer[i,j], bayer[i+1,j+1] = op.mask[i+1,j+1].copy(), op.mask[i,j].copy()
new[i,j], new[i+1,j+1] = new[i+1,j+1].copy(), new[i,j].copy()
return bayer, new
\ No newline at end of file
File added