Saving Numpy Array as Lab Image via PIL
Hello everyone!

I am writing a function which gets two Pytorch-Tensors as input and merges parts of both tensors to a new array which will be converted to a Lab-image afterwards.

net_input is a tensor with 3 channels (L, a, b) and output is a tensor with 2 channels (a, b). 
Now, I want to take the L-channel from net_input and the a-channel and the b-channel from output.

The first tensor, net_input, has values from -1 to 1. The second tensor, output, has values from 0 - 1.

Thus, both of them must be mapped to values from -128 to 127. I do this, using interp1d

Apparently the saved image has some unwanted patterns and is incorrect.

original:  . grayscale:     

from PIL import Image as pil
import numpy as np
from PIL import Image
from scipy.interpolate import interp1d
from datetime import datetime

def output_to_image(net_input, output, index):
    # net_input.size()  --&gt;     torch.Size([1, 3, 225, 225]) # l, a, b channel
    # output.size()     --&gt;     torch.Size([1, 2, 225, 225]) # a, b channel

    # A - B - Channel
    map_a_b = interp1d([0.0, 1.0], [-128, 127])

    # L-Channel
    map_l = interp1d([-1.0, 1.0], [-128, 127])

    height = output.size()[2]
    width = output.size()[3]

    l = net_input.detach().numpy()[0][0]    # l channel
    a = output.detach().numpy()[0][0]   # a channel
    b = output.detach().numpy()[0][1]   # b channel

    # pdb.set_trace()

    img_arr = np.empty([height, width, 3])  # lab img array
    l_arr = np.empty([height, width])   # grayscale img array

    for h in range(0, height-1):
        for w in range(0, width-1):
            img_arr[h][w] = [map_l(l[h][w]), map_a_b(a[h][w]), map_a_b(b[h][w])]
            l_arr[h][w] = map_l(l[h][w])

    now = datetime.now()

    img = Image.fromarray(img_arr, "LAB")
    img.save(f"../images/output/{now.strftime('%d-%m-%Y %H:%M:%S')}-lab-img{index}.tiff")

    gray = Image.fromarray(l_arr, "L")
    gray.save(f"../images/output/{now.strftime('%d-%m-%Y %H:%M:%S')}-gray-img{index}.jpg")


This is how L, a and b look like:

(Pdb) &gt;? print(l)
[[-1. -1. -1. ... -1. -1. -1.]
 [-1. -1. -1. ... -1. -1. -1.]
 [-1. -1. -1. ... -1. -1. -1.]
 ...
 [-1. -1. -1. ... -1. -1. -1.]
 [-1. -1. -1. ... -1. -1. -1.]
 [-1. -1. -1. ... -1. -1. -1.]]
(Pdb) &gt;? print(a)
[[0.51877767 0.5208904  0.5310791  ... 0.5340722  0.51334995 0.522657  ]
 [0.5142181  0.50250506 0.5197009  ... 0.51169556 0.5332947  0.5155644 ]
 [0.53288984 0.51795006 0.5224927  ... 0.51704454 0.53655064 0.50311136]
 ...
 [0.5270468  0.5071506  0.52318716 ... 0.5217321  0.53424454 0.5011423 ]
 [0.5216123  0.53247094 0.5254119  ... 0.53089285 0.5259453  0.532716  ]
 [0.53135234 0.5184961  0.51334924 ... 0.5131047  0.51930845 0.51474   ]]
(Pdb) &gt;? print(b)
[[0.4812223  0.47910962 0.46892092 ... 0.46592775 0.48665005 0.47734302]
 [0.48578197 0.49749494 0.4802992  ... 0.4883045  0.46670532 0.48443565]
 [0.46711013 0.48204994 0.47750723 ... 0.4829555  0.46344927 0.4968886 ]
 ...
 [0.47295317 0.49284932 0.47681284 ... 0.47826794 0.46575552 0.49885774]
 [0.47838774 0.46752912 0.47458804 ... 0.46910718 0.47405463 0.46728405]
 [0.46864766 0.48150396 0.48665074 ... 0.48689532 0.48069155 0.48526   ]]
(Pdb) 


Edit:

After adjusting the interpolation range to 0..255, like Mark suggested, the output looks a little bit different:



import numpy as np
from PIL import Image
from datetime import datetime


def output_to_image(net_input, output, index):
    # net_input.size()  --&gt;     torch.Size([1, 3, 225, 225])
    # output.size()     --&gt;     torch.Size([1, 3, 225, 225])

    height = output.size()[2]
    width = output.size()[3]

    l = net_input.detach().numpy()[0][0]    # l channel
    a = output.detach().numpy()[0][0]   # a channel
    b = output.detach().numpy()[0][1]   # b channel

    #pdb.set_trace()

    img_arr = np.empty([height, width, 3])  # lab img array
    l_arr = np.empty([height, width])   # grayscale img array

    for h in range(0, height-1):
        for w in range(0, width-1):
            img_arr[h][w] = [map_l(l[h][w]), map_a_b(a[h][w]), map_a_b(b[h][w])]
            l_arr[h][w] = map_l(l[h][w])

    now = datetime.now()

    img = Image.fromarray(img_arr, "LAB")
    img.save(f"../images/output/{now.strftime('%d-%m-%Y %H:%M:%S')}-lab-img{index}.tiff")

    gray = Image.fromarray(l_arr, "L")
    gray.save(f"../images/output/{now.strftime('%d-%m-%Y %H:%M:%S')}-gray-img{index}.jpg")


def map_l(x):
    return (x+1)*127


def map_a_b(x):
    return x*127


If you do this with PIL:

im = Image.new('LAB',(80,80),color=(255,0,0)).save('a.tif') 


you'll get a cyan colour the same as if you use this colour converter and put in 

L/a/b = 100/-128/-128




If you do this:

im = Image.new('LAB',(80,80),color=(255,255,0)).save('a.tif') 


you'll find that corresponds to 

L/a/b=100/128/-128


on the online converter.



And if you do this:

im = Image.new('LAB',(80,80),color=(255,255,255)).save('a.tif')


you'll find that corresponds to

L/a/b=100/128/128


on the online converter.

So, I think you are targeting the wrong limits if using PIL. Your ranges for L, a and b should all be 0..255.



As regards scaling, I wouldn't use interp1d() at all. If I need to scale the range -1..1 to the range 0..255, I would add 1 which brings me to the range 0..2 and then multiply by 255/2.
