Loaded grayscale image is converted into 3 channel

username: rukhmini
cooked: <p>I am using a dataset of grayscale images of size 64X64 to train a network for two class image classification. The images are stored in train folder with class labels as subfolder name inside train folder but when ever I am loading the images it is converting into 3 channel images. The piece of code is:</p>
<p>data_dir_train = ‘C:/Users/rukhm/nodule_classification/data/train/’<br>
data_dir_test = ‘C:/Users/rukhm/nodule_classification/data/test/’<br>
train_dataset = torchvision.datasets.ImageFolder(data_dir_train,transform = transforms.ToTensor())<br>
test_dataset = torchvision.datasets.ImageFolder(data_dir_test,transform = transforms.ToTensor())<br>
trainloader = torch.utils.data.DataLoader(dataset=train_dataset,<br>
batch_size=batch_size,<br>
shuffle=True);<br>
testloader = torch.utils.data.DataLoader(dataset=test_dataset,<br>
batch_size=batch_size,<br>
shuffle=True);<br>
dataiter = iter(trainloader)<br>
images, labels = dataiter.next()</p>
<p>print(images.shape) <span class="hashtag">#shape</span> of all images<br>
print(images[1].shape) <span class="hashtag">#shape</span> of one image<br>
print(labels[1].item()) <span class="hashtag">#label</span> number</p>
<p>Output:<br>
torch.Size([100, 3, 64, 64])<br>
torch.Size([3, 64, 64])<br>
0</p>

username: ptrblck
cooked: <p>Could you try to load a single image via <code>img = PIL.Image.open(path)</code> and check its shape?<br>
If that returns an RGB image, you could add <a href="https://pytorch.org/docs/stable/torchvision/transforms.html#torchvision.transforms.Grayscale">transforms.GrayScale</a> to the transformations.</p>

username: rukhmini
cooked: <p>img = Image.open(‘C:/Users/rukhm/nodule_classification/data/train/1/LIDC-IDRI-0003_74_1.png’)<br>
im = (transforms.ToTensor()(img))<br>
im.shape<br>
Output:<br>
torch.Size([1, 64, 64])<br>
For a single image it is showing as a grayscale image.<br>
Is it possible to check the shape of train_dataset/test_dataset?<br>
I need to check the line where it is converted into a 3 channel image.<br>
Thanks for the help!!</p>

username: marcoramos
cooked: <p>I am having the same problem, no matter what I do I cannot load my greyscale images with 1 channel</p>

username: MaxM
cooked: <p>I am currently facing the same issue. My .bmp images with 1 channel are somehow converted into 3 channels. As I have found out now the issue seems to be caused by the ImageFolder function offered by pytorch. Is there any possibility to avoid ImageFolder converting the images into 3 channels? The only way I currently see is to use the grayscale transform, as a counter.</p>

username: ptrblck
cooked: <p><code>ImageFolder</code> will use the <code>default_loader</code>, which would then either use <code>accimage_loader</code>, if it’s available, or <code>pil_loader</code>, which will convert the loaded images to RGB as seen <a href="https://github.com/pytorch/vision/blob/2ab93592529243862ce8ad5b6acf2628ef8d0dc8/torchvision/datasets/folder.py#L247-L251">here</a>.<br>
You can create a custom <code>loader</code> by e.g. just reusing the <code>pil_loader</code> and removing the <code>convert</code> call, and pass it into the <code>loader</code> argument in <code>ImageFolder</code>.</p>

username: RaLo4
cooked: <p>Doing what ptrblck  suggested is definitely the “proper” way of doing this.</p>
<p>But if you want a quick fix that does not involve a custom <code>loader</code> you can just use <code>torchvision.transforms.Grayscale(num_output_channels=1)</code> (<a href="https://pytorch.org/vision/stable/transforms.html#torchvision.transforms.Grayscale" rel="noopener nofollow ugc">doc</a>) as an extra <code>transformation</code>.</p>
<p>In your above code:</p>
<aside class="quote no-group" data-username="rukhmini" data-post="1" data-topic="75862">
<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/rukhmini/48/12121_2.png" class="avatar"> rukhmini:</div>
<blockquote>
<p>train_dataset = torchvision.datasets.ImageFolder(data_dir_train,transform = transforms.ToTensor())</p>
</blockquote>
</aside>
<p>It would look like this:</p>
<pre><code class="lang-auto">train_dataset = datasets.ImageFolder(data_dir_train,transform = 
                                     transforms.Compose([transforms.Grayscale(num_output_channels=1), 
                                                         transforms.ToTensor()]))
</code></pre>
<p>Since all PILs <code>Image.convert('RGB')</code> does is r==g==b<br>
and transforms.Grayscale(num_output_channels=1) does <code>(0.2989 * r + 0.587 * g + 0.114 * b)</code><br>
this should not do too much to your data except for maybe a small change in the 0.00x margin.</p>

username: MaxM
cooked: <p>Thanks for you help!<br>
I want to try to implement a custom pil loader but I am currently struggling a bit with this.</p>
<p>My plan was to add the custom pil loader within folder.py.<br>
So I copied the normal pil_loader function, where ptrblck showed me it is located, and pasted it right above it.<br>
I renamed it to custom_pil_loader and removed the convert call.</p>
<pre><code>def custom_pil_loader(path):
# open path as file to avoid ResourceWarning (https://github.com/python-pillow/Pillow/issues/835)
    with open(path, 'rb') as f:
        img = Image.open(f)
        return img

def pil_loader(path):
# open path as file to avoid ResourceWarning (https://github.com/python-pillow/Pillow/issues/835)
    with open(path, 'rb') as f:
        img = Image.open(f)
        return img.convert('RGB')
</code></pre>
<p>After this I thought that it would be enough to call the new loader during the ImageFolder call.<br>
I tried this here but it didn`t worked.<br>
Could you tell me what I am doing wrong?</p>
<pre><code> train_ds = ImageFolder(os.path.join(opt.dataroot, 'train'), transform, loader='custom_pil_loader')
 valid_ds = ImageFolder(os.path.join(opt.dataroot, 'test'), transform, loader='custom_pil_loader')
</code></pre>
<p>That was the way I called the ImageFolder before what worked well, with the problem of the conversion in RGB of single channel images.</p>
<pre><code>train_ds = ImageFolder(os.path.join(opt.dataroot, 'train'), transform)
valid_ds = ImageFolder(os.path.join(opt.dataroot, 'test'), transform)
</code></pre>
<p>I tried then to access the first batch of images within the dataloader</p>
<pre><code>train_dl = DataLoader(dataset=train_ds, batch_size=opt.batchsize, shuffle=True, drop_last=True)
valid_dl = DataLoader(dataset=valid_ds, batch_size=opt.batchsize, shuffle=False, drop_last=False)
</code></pre>
<p>I run the following command</p>
<pre><code>iter(train_dl).next()[0].shape
</code></pre>
<p>With the default_loader I get the following output</p>
<pre><code>Out[1]: torch.Size([32, 3, 32, 32])
</code></pre>
<p>But when I try to run this command using my custom_pil_loader I get the following error message</p>
<pre><code>Traceback (most recent call last):
  File "/fibus/fs1/16/cql7772/.local/lib/python3.7/site-packages/IPython/core/interactiveshell.py", line 
3326, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "&lt;ipython-input-1-405944bb4251&gt;", line 1, in &lt;module&gt;
    iter(train_dl).next()[0].shape
  File "/fibus/fs1/16/cql7772/.local/lib/python3.7/site-packages/torch/utils/data/dataloader.py", line 
346, in __next__
    data = self._dataset_fetcher.fetch(index)  # may raise StopIteration
  File "/fibus/fs1/16/cql7772/.local/lib/python3.7/site-packages/torch/utils/data/_utils/fetch.py", line 44, 
in fetch
    data = [self.dataset[idx] for idx in possibly_batched_index]
  File "/fibus/fs1/16/cql7772/.local/lib/python3.7/site-packages/torch/utils/data/_utils/fetch.py", line 44, 
in &lt;listcomp&gt;
    data = [self.dataset[idx] for idx in possibly_batched_index]
  File "/fibus/fs1/16/cql7772/.local/lib/python3.7/site-packages/torchvision/datasets/folder.py", line 
138, in __getitem__
    sample = self.loader(path)
TypeError: 'str' object is not callable</code></pre>

username: RaLo4
cooked: <p>What you did works.<br>
I tried it. You just need to give the callable function <code>custom_pil_loader</code> when calling <code>ImageFolder</code> and not the name as a <code>string</code>.</p>
<p>The following worked for me:</p>
<pre><code class="lang-auto">import torch
import torchvision.transforms as transforms
import torchvision.datasets as dset
from PIL import Image

DATA_ROOT = '../data/dataset/'

def custom_pil_loader(path):
# open path as file to avoid ResourceWarning (https://github.com/python-pillow/Pillow/issues/835)
    with open(path, 'rb') as f:
        img = Image.open(f)
        img.load()
        return img

transform=transforms.Compose([transforms.Resize(256),
                              transforms.CenterCrop(256),
                              transforms.ToTensor(),
                              ])
    
default_ds = dset.ImageFolder(DATA_ROOT, transform,)
custom_ds = dset.ImageFolder(DATA_ROOT, transform, loader=custom_pil_loader)

default_dl = torch.utils.data.DataLoader(dataset=default_ds, batch_size=32)
custom_dl = torch.utils.data.DataLoader(dataset=custom_ds, batch_size=32)
     
print(iter(default_dl).next()[0].shape)
print(iter(custom_dl).next()[0].shape)
</code></pre>
<pre><code class="lang-auto">torch.Size([32, 3, 256, 256])
torch.Size([32, 1, 256, 256])
</code></pre>
<p>I had to also add <code>img.load()</code> at the end of the custom loader or I would get <code>ValueError: seek of closed file</code>.<br>
Not sure what this is about tho. I found this fix on <a href="https://stackoverflow.com/questions/56223661/pil-throws-valueerror-seek-of-closed-file" rel="noopener nofollow ugc">stackoverflow</a></p>

username: MaxM
cooked: <p>Many thanks RaLo4!<br>
I did it your way and it works perfectly well!<br>
I had the same problem with the</p>
<pre><code> ValueError: seek of closed file
</code></pre>
<p>when I commented out the</p>
<pre><code>img.load()
</code></pre>
<p>It seems to be necessary.</p>