Convert grayscale images to RGB

Tanya_Boone: <p>Hello, I am trying to classify ImageNet using vgg  and I am using a custom dataset as follows</p>
<pre><code class="lang-auto">train_dataset=CustomDataset(csv_file='/home/tboonesifuentes/Databases/ImageNet/Train/train.csv',root_dir='/home/tboonesifuentes/Databases/ImageNet/Train/Crops',
                                transform=transforms.Compose([  
                                transforms.ToPILImage(),
                                transforms.Resize([224,224]),
                                transforms.ToTensor()]))
    
    
test_dataset=CustomDataset(csv_file='/home/tboonesifuentes/Databases/ImageNet/Test/test.csv',root_dir='/home/tboonesifuentes/Databases/ImageNet/Test/Crops',
                                transform=transforms.Compose([
                                transforms.ToPILImage(),
                                transforms.Resize([224,224]),
                                #transforms.RandomCrop(24),
                                transforms.ToTensor()]))


batch_size=130


class TransformedDataset(torch.utils.data.Dataset):
    def __init__(self, dataset, transform_fn):
        self.dataset = dataset
        self.transform_fn = transform_fn

    def __len__(self):
        return len(self.dataset)

    def __getitem__(self, index):
        dp = self.dataset[index]
        return self.transform_fn(dp)
    

labels_mapping = {     
8:0,
9:1,
10:2,
11:3,
12:4,
13:5,
14:6,
15:7,
16:8,
17:9,
18:10,
19:11,
20:12,
21:13,
22:14,
23:15,
24:16,
25:17,
26:18,
27:19,
28:20,
29:21,
30:22,
31:23,
32:24,
33:25,
34:26,
35:27,
36:28,
37:29,
38:30,
39:31,
40:32,
41:33,
42:34,
43:35,   
}

def map_targets_fn(dp, target_mapping):
    x, y = dp
    new_y  = target_mapping[y.item()]
    return x, new_y


train_dataset = TransformedDataset(train_dataset, partial(map_targets_fn, target_mapping=labels_mapping))
test_dataset = TransformedDataset(test_dataset, partial(map_targets_fn, target_mapping=labels_mapping))

for idx, (data,image) in enumerate (train_dataset):
    
    if data.shape[0] == 1:
        print(data.shape)
        print('1D image')
        


train_loader = DataLoader(train_dataset, batch_size,num_workers=num_workers, 
                        shuffle=True, drop_last=True)    
  
test_loader = DataLoader(test_dataset, batch_size,num_workers=num_workers, 
                          shuffle=False, drop_last=False)    

</code></pre>
<p>I  didn’t know what ImageNet had grayscale images and I actually found some and read them on matlab and yes they are grayscale…that’s the reason Im getting the error of batch size mismatch at position 0. Now I know I have to convert these grayscale images if I want to train…my question is where can I catch the grayscale images and convert them to rgb? In matlab would be something like rgbImage = cat(3, A,A, A); where A is the grayscale image. But I don’t know how to do it or where exactly on  my special code. Please someone help <img src="https://discuss.pytorch.org/images/emoji/apple/slight_smile.png?v=9" title=":slight_smile:" class="emoji" alt=":slight_smile:"></p>

ptrblck: <p>Assuming the tensors are loaded as <code>[channels, height, width]</code>, you could probably use this lambda transformation:</p>
<pre><code class="lang-python">trans = transforms.Lambda(lambda x: x.repeat(3, 1, 1) if x.size(0)==1 else x)

x = torch.randn(3, 224, 224)
out = trans(x)
print(out.shape)
&gt; torch.Size([3, 224, 224])

x = torch.randn(1, 224, 224)
out = trans(x)
print(out.shape)
&gt; torch.Size([3, 224, 224])
</code></pre>
<p>If you are loading the images via <code>PIL.Image.open</code> inside your custom <code>Dataset</code>, you could also convert them directly to <code>RGB</code> via <code>PIL.Image.open(...).convert('RGB')</code>.<br>
However, since you are using <code>ToPILImage</code> as a transformation, I assume you are loading tensors directly.</p>

Tanya_Boone: <p>Hello ptrblck, Thanks for your quick response. Actually I discovered I also have images with four channels so I implemented this code in my  custom dataset</p>
<pre><code class="lang-auto">import os
import pandas as pd
import torch

from torch.utils.data import Dataset

from skimage import io

class CustomDataset(Dataset):
	
    def __init__(self,csv_file,root_dir,transform=None):
        self.annotations=pd.read_csv(csv_file)
        self.root_dir=root_dir
        self.transform=transform

    def __len__(self):
        return len(self.annotations)
    
    def __getitem__(self,index):
        img_path=os.path.join(self.root_dir,self.annotations.iloc[index,0])
        image=io.imread(img_path)
        
        if len(image.shape) == 2:
        #convert grayscale to RGB
        #image = Image.open(path).convert('RGB') 
            image=torch.from_numpy(image)
            image=torch.stack([image,image,image],0)
            image=torch.transpose(image,0,2)
            image=image.numpy()
            #print('this was 1d before')
            #print(image.shape)
        elif len(image.shape) == 3: 
        #image has 4 channels
            if image.shape[0]==4:
                image=torch.from_numpy(image)
                image = image[:,:,:3]
                image=torch.transpose(image,0,2)
                image=image.numpy()
            
        y_label=torch.tensor(int(self.annotations.iloc[index,2]))

        if self.transform:
            image=self.transform(image)
        return (image,y_label)
</code></pre>

brunoj: <p>I have mnist dataset that is in pytorch API its grayscale and I want to implement transfer learning using Alexnet. Alexnet accepts 227<em>227</em>3 rgb. How to convert it into rgb?<br>
My code is here:</p>
<p>import torch<br>
import torchvision<br>
from torchvision import transforms as transforms<br>
from torchvision import models<br>
dir(models)<br>
transform = transforms.Compose([<br>
transforms.Resize(256),<br>
transforms.CenterCrop(227),<br>
transforms.ToTensor(),<br>
transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225])<br>
])</p>
<p><span class="hashtag">#Downloading</span> training data</p>
<p>train_data = torchvision.datasets.MNIST(root=‘./data’, train=True, download=True, transform=transform)<br>
trainloader = torch.utils.data.DataLoader(train_data, batch_size=4, shuffle=True, num_workers=2)</p>
<p><span class="hashtag">#Downloading</span> test data</p>
<p>test_data = torchvision.datasets.MNIST(root=‘./data’, train=False, download=True, transform=transform)<br>
testloader = torch.utils.data.DataLoader(test_data, batch_size=4, shuffle=False, num_workers=2)</p>
<p>classes = (‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’)<br>
import matplotlib.pyplot as plt<br>
import numpy as np</p>
<p><span class="hashtag">#Function</span> to show some random images<br>
def imshow(img):<br>
img = img / 2 + 0.5     # unnormalize<br>
npimg = img.numpy()<br>
plt.imshow((npimg * 255).astype(np.uint8))<br>
plt.imshow(np.transpose(npimg, (1, 2, 0)))<br>
plt.show()</p>
<p><span class="hashtag">#Get</span> some random training images</p>
<p>dataiter = iter(trainloader)</p>
<p>images, labels = next(dataiter)<br>
<span class="hashtag">#Show</span> images</p>
<p>imshow(torchvision.utils.make_grid(images))</p>
<h1>
<a name="print-labels-1" class="anchor" href="#print-labels-1"></a>print labels</h1>
<p>print(’ ‘.join(’%5s’ % classes[labels[j]] for j in range(4)))</p>

ptrblck: <p>My <a href="https://discuss.pytorch.org/t/convert-grayscale-images-to-rgb/113422/2">previous post</a> describes different approaches including a minimal, executable code snippet showing how to repeat the single channel in the input channel via <code>transforms.Lambda</code>. Did you try to use any of these approaches?</p>

brunoj: <p>yeah I read later on. I used transforms.Grayscale(3).</p>

brunoj: <p>I have few questions which I couldnt find answers so asking here from experts.<br>
I have to train mnist data for alexnet transfer learning.<br>
My questions are:<br>
1- How important is to change grayscale-rgb and size of mnist 28<em>28 to 227</em>227?<br>
2-Why alexnet size is 227*227 not anything else? Like whats the significance?<br>
3- We put mean and standard deviation of pre trained model, like here in my case imagenet for alexnet. So my data std and mean is diff? I have read so much about this but what I understood is , its ok to use your own and even it you use pre trained data’s. You can make your argument like mine was diff so I relied on it or you may say the pre trained was this so I am using it.</p>

ptrblck: <ol>
<li>
<p>If you want to use the pretrained first conv layer, you need to pass an input with 3 channels to the model. Alternatively, you can also replace the first conv layer and train it from scratch. AlexNet should use spatial inputs of <code>224x224</code> as described in <a href="https://proceedings.neurips.cc/paper/2012/file/c399862d3b9d6b76c8436e924a68c45b-Paper.pdf">ImageNet Classification with Deep Convolutional Neural Networks</a>. If you don’t increase the spatial size of our inputs the forward pass will most likely raise a runtime error since your forward activations will be empty due to e.g. pooling layers.</p>
</li>
<li>
<p>I would recommend to read the original paper and check if some explanation was given. Generally, I would guess <code>224x224</code> is picked because it works and might be a sweet spot balancing training speed, memory usage, model performance etc.</p>
</li>
<li>
<p>Yes, the mean and stddev of your data might be different and you should consider using the stats of your dataset instead. Often the ImageNet stats are reused if your new custom datasets shares a similar domain, i.e. “natural” images in this case.</p>
</li>
</ol>

brunoj: <p>Alright. Thanks. But I feel like pytorch is a mess. I don’t understand things but sadly tensorflow doesnt have pre trained alexnet. Here I have few questions-&gt; I have frozen the layers  like:<br>
for param in alexnet.features.parameters():<br>
param.requires_grad = False</p>
<p>and then updated last one like this:<br>
alexnet.classifier[6] = nn.Linear(4096,10)</p>
<p>this is the snap of my summary after frozen layers and after updated last layer I got 10 classes output in place of 10000.<br>
<div class="lightbox-wrapper"><a class="lightbox" href="https://discuss.pytorch.org/uploads/default/original/3X/a/1/a1cff15a9aa43640f9642d6fb55e5d327613789c.png" data-download-href="https://discuss.pytorch.org/uploads/default/a1cff15a9aa43640f9642d6fb55e5d327613789c" title="image"><img src="https://discuss.pytorch.org/uploads/default/optimized/3X/a/1/a1cff15a9aa43640f9642d6fb55e5d327613789c_2_690x388.png" alt="image" data-base62-sha1="n5so3U4XLmBguuq6lkac7a8wllO" width="690" height="388" srcset="https://discuss.pytorch.org/uploads/default/optimized/3X/a/1/a1cff15a9aa43640f9642d6fb55e5d327613789c_2_690x388.png, https://discuss.pytorch.org/uploads/default/optimized/3X/a/1/a1cff15a9aa43640f9642d6fb55e5d327613789c_2_1035x582.png 1.5x, https://discuss.pytorch.org/uploads/default/optimized/3X/a/1/a1cff15a9aa43640f9642d6fb55e5d327613789c_2_1380x776.png 2x" data-dominant-color="9AC3F5"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">image</span><span class="informations">1920×1080 285 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<p>My question is why the sequential classifier in the image are true when I froze all layers. Moreover after freezing layers I directly changed the last layer output classes does it unfreezes ? Or do I need to manual set grad to true for this linear(6) ?</p>

ptrblck: <aside class="quote no-group" data-username="brunoj" data-post="9" data-topic="113422">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/user_avatar/discuss.pytorch.org/brunoj/48/56049_2.png" class="avatar"> brunoj:</div>
<blockquote>
<p>this is the snap of my summary after frozen layers and after updated last layer I got 10 classes output in place of 10000.</p>
</blockquote>
</aside>
<p>Sorry, but I don’t fully understand this claim. Could you describe what “snap” is why you are not expecting 10 output features even though you explicitly replace the last linear layer with <code>out_features=10</code>?<br>
The posted screenshot also doesn’t represent your code as I see:</p>
<pre data-code-wrap="python"><code class="lang-python">model = models.alexnet()
print(model.classifier)
# Sequential(
#   (0): Dropout(p=0.5, inplace=False)
#   (1): Linear(in_features=9216, out_features=4096, bias=True)
#   (2): ReLU(inplace=True)
#   (3): Dropout(p=0.5, inplace=False)
#   (4): Linear(in_features=4096, out_features=4096, bias=True)
#   (5): ReLU(inplace=True)
#   (6): Linear(in_features=4096, out_features=1000, bias=True)
# )

model.classifier[6] = nn.Linear(4096, 10)
print(model.classifier)
# Sequential(
#   (0): Dropout(p=0.5, inplace=False)
#   (1): Linear(in_features=9216, out_features=4096, bias=True)
#   (2): ReLU(inplace=True)
#   (3): Dropout(p=0.5, inplace=False)
#   (4): Linear(in_features=4096, out_features=4096, bias=True)
#   (5): ReLU(inplace=True)
#   (6): Linear(in_features=4096, out_features=10, bias=True)
# )
</code></pre>
<aside class="quote no-group" data-username="brunoj" data-post="9" data-topic="113422">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/user_avatar/discuss.pytorch.org/brunoj/48/56049_2.png" class="avatar"> brunoj:</div>
<blockquote>
<p>My question is why the sequential classifier in the image are true when I froze all layers.</p>
</blockquote>
</aside>
<p>I don’t know which tool you are using, but you could always check the <code>requires_grad</code> attribute to see if a parameter is frozen or not.</p>
<aside class="quote no-group" data-username="brunoj" data-post="9" data-topic="113422">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/user_avatar/discuss.pytorch.org/brunoj/48/56049_2.png" class="avatar"> brunoj:</div>
<blockquote>
<p>Moreover after freezing layers I directly changed the last layer output classes does it unfreezes ?</p>
</blockquote>
</aside>
<p>Yes, since you are creating a new trainable layer and are assigning it to an internal model attribute.</p>
<aside class="quote no-group" data-username="brunoj" data-post="9" data-topic="113422">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/user_avatar/discuss.pytorch.org/brunoj/48/56049_2.png" class="avatar"> brunoj:</div>
<blockquote>
<p>Alright. Thanks. But I feel like pytorch is a mess.</p>
</blockquote>
</aside>
<p>These kind of statements are not really helpful as your questions could easily be solved by reading through the tutorials and docs while nevertheless more experienced users use their time to help you.</p>

brunoj: <p>Well I meant to say the screenshot I attached has classifier part all true. This summary I printed after freezing features. My question was In the classifier you see the linear(6), I want to only unfreeze this and freeze rest of all.<br>
And my other question is what does these linear(x) in classifier mean?</p>

brunoj: <p>Im using google colab and My code is:<br>
import torch<br>
import torchvision<br>
from torchvision import transforms as transforms<br>
from torchvision import models<br>
dir(models)<br>
transform = transforms.Compose([<br>
transforms.Grayscale(num_output_channels=3),<br>
transforms.Resize(256),<br>
transforms.CenterCrop(227),<br>
transforms.ToTensor(),<br>
<span class="hashtag">#transforms</span>.Normalize(mean=[0.16592294], std=[0.31460345]),<br>
transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225])<br>
])<br>
train_data = torchvision.datasets.MNIST(root=‘./data’, train=True, download=True, transform=transform)</p>
<p>trainloader = torch.utils.data.DataLoader(train_data, batch_size=4, shuffle=True)<br>
test_data = torchvision.datasets.MNIST(root=‘./data’, train=False, download=True, transform=transform)</p>
<p>testloader = torch.utils.data.DataLoader(test_data, batch_size=4, shuffle=False)<br>
for images, labels in trainloader:<br>
print(‘Image batch dimensions:’, images.shape)<br>
print(‘Image label dimensions:’, labels.shape)<br>
print(‘Class labels of 10 examples:’, labels[:10])<br>
break<br>
<span class="hashtag">#Class</span> labels</p>
<p>classes = (‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’)<br>
import matplotlib.pyplot as plt<br>
import numpy as np</p>
<p><span class="hashtag">#Function</span> to show some random images<br>
<span class="hashtag">#def</span> imshow(img):<br>
<span class="hashtag">#img</span> = img / 2 + 0.5     # unnormalize<br>
<span class="hashtag">#npimg</span> = img.numpy()<br>
<span class="hashtag">#plt</span>.imshow(np.transpose(npimg, (1, 2, 0)))<br>
<span class="hashtag">#plt</span>.show()<br>
mean_cal=[0.485, 0.456, 0.406]<br>
std_cal=[0.229, 0.224, 0.225]</p>
<p>def imshow(inp, title=None):<br>
inp = inp.numpy().transpose((1, 2, 0))<br>
meann = np.array([mean_cal])<br>
std = np.array([std_cal])<br>
inp = std * inp + meann<br>
inp = np.clip(inp, 0, 1)<br>
plt.imshow(inp)<br>
if title is not None:<br>
plt.title(title)<br>
plt.pause(0.001)<br>
<span class="hashtag">#Get</span> some random training images</p>
<p>dataiter = iter(trainloader)</p>
<p>images, labels = next(dataiter)<br>
<span class="hashtag">#Show</span> images</p>
<p>imshow(torchvision.utils.make_grid(images))</p>
<h1>
<a name="print-labels-1" class="anchor" href="#print-labels-1"></a>print labels</h1>
<p>print(’ ‘.join(’%5s’ % classes[labels[j]] for j in range(4)))<br>
alexnet = models.alexnet(pretrained=True) # This will download the weights for the network first time it is run!</p>
<p>alexnet<br>
alexnet.eval()<br>
import torchinfo<br>
from torchinfo import summary<br>
summary(model=alexnet,<br>
input_size=(4, 3, 227, 227), # make sure this is “input_size”, not “input_shape”<br>
# col_names=[“input_size”], # uncomment for smaller output<br>
col_names=[“input_size”, “output_size”, “num_params”, “trainable”],<br>
col_width=20,<br>
row_settings=[“var_names”]<br>
)<br>
for name, child in alexnet.named_children():<br>
print(name)<br>
for param in alexnet.features.parameters():<br>
param.requires_grad = False<br>
summary(model=alexnet,<br>
input_size=(4, 3, 227, 227), # make sure this is “input_size”, not “input_shape”<br>
# col_names=[“input_size”], # uncomment for smaller output<br>
col_names=[“input_size”, “output_size”, “num_params”, “trainable”],<br>
col_width=20,<br>
row_settings=[“var_names”]<br>
)<br>
import torch.nn as nn<br>
alexnet.classifier[6] = nn.Linear(4096,10)<br>
import torch.optim as optim<br>
optimizer = optim.SGD(alexnet.classifier[6].parameters(), lr=0.001, momentum=0.9)<br>
<span class="hashtag">#Instantiating</span> CUDA device</p>
<p>device = torch.device(“cuda:0” if torch.cuda.is_available() else “cpu”)</p>
<p><span class="hashtag">#Verifying</span> CUDA → shouldn’t be</p>
<p>print(device)<br>
alexnet.to(device)<br>
loss_fn = nn.CrossEntropyLoss()<br>
from google.colab import drive</p>
<p>drive.mount(‘/content/drive’)<br>
for epoch in range(2):  # loop over the dataset multiple times<br>
running_loss = 0.0<br>
for i, data in enumerate(trainloader, 0):<br>
# get the inputs; data is a list of [inputs, labels]<br>
inputs, labels = data[0].to(device), data[1].to(device)</p>
<pre><code>    # zero the parameter gradients
    optimizer.zero_grad()

    # forward + backward + optimize
    output = alexnet(inputs)
    loss = loss_fn(output, labels)
    loss.backward()
    optimizer.step()

    # print statistics
    running_loss += loss.item()
    if i % 2000 == 1999:    # print every 2000 mini-batches
        print('[%d, %5d] loss: %.3f' %
              (epoch + 1, i + 1, running_loss / 2000))
        running_loss = 0.0
</code></pre>
<p>print(‘Finished Training of AlexNet’)</p>
<p>print(“Model’s state_dict:”)<br>
for param_tensor in alexnet.state_dict():<br>
print(param_tensor, “\t”, alexnet.state_dict()[param_tensor].size())</p>
<h1>
<a name="print-optimizers-state_dict-2" class="anchor" href="#print-optimizers-state_dict-2"></a>Print optimizer’s state_dict</h1>
<p>print(“Optimizer’s state_dict:”)<br>
for var_name in optimizer.state_dict():<br>
print(var_name, “\t”, optimizer.state_dict()[var_name])</p>
<p><span class="hashtag">#Specify</span> a path<br>
PATH = “/content/drive/MyDrive/Colab Notebooks/MNIST/state_dict_model.pt”</p>
<h1>
<a name="save-3" class="anchor" href="#save-3"></a>Save</h1>
<p>torch.save(alexnet.state_dict(), PATH)</p>

brunoj: <p>and by the way what does transform.ToTensor() and np.clip(inp, 0, 1) do? I mean when I dont clip it says in plotting that values must be 0-1 or 255. But as far as I know ToTensor() does the same thing set images into range 0-1 ?</p>