We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Alpha channel and grayscale in image-to-text with -image_channel_size=3
For training image to text, the argument `-image_channel_size=3` imply that the images already have the good number of channel. However, some of my images are black and white and saved with only one channel or saved in RGB but with the alpha channel.
I could fix it with a change in `onmt/inputters/image_dataset.py` [here](https://github.com/OpenNMT/OpenNMT-py/blob/master/onmt/inputters/image_dataset.py#L78):

from this:
```
            if self.channel_size == 1:
                img = transforms.ToTensor()(
                    Image.fromarray(cv2.imread(img_path, 0)))
            else:
                img = transforms.ToTensor()(Image.open(img_path))
```
to this:
```
            if self.channel_size == 1:
                img = transforms.ToTensor()(
                    Image.fromarray(cv2.imread(img_path, 0)))
            else:
                img = transforms.ToTensor()(
                    Image.fromarray(cv2.imread(img_path, 1)))
```
The flag in `cv2.imread` with value of 1 tell cv2 to convert to RGB no matter what the original image is.

Should I do a PR ?

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `onmt/inputters/image_dataset.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 
3 import os
4 
5 import torch
6 from torchtext.data import Field
7 
8 from onmt.inputters.datareader_base import DataReaderBase
9 
10 # domain specific dependencies
11 try:
12     from PIL import Image
13     from torchvision import transforms
14     import cv2
15 except ImportError:
16     Image, transforms, cv2 = None, None, None
17 
18 
19 class ImageDataReader(DataReaderBase):
20     """Read image data from disk.
21 
22     Args:
23         truncate (tuple[int] or NoneType): maximum img size. Use
24             ``(0,0)`` or ``None`` for unlimited.
25         channel_size (int): Number of channels per image.
26 
27     Raises:
28         onmt.inputters.datareader_base.MissingDependencyException: If
29             importing any of ``PIL``, ``torchvision``, or ``cv2`` fail.
30     """
31 
32     def __init__(self, truncate=None, channel_size=3):
33         self._check_deps()
34         self.truncate = truncate
35         self.channel_size = channel_size
36 
37     @classmethod
38     def from_opt(cls, opt):
39         return cls(channel_size=opt.image_channel_size)
40 
41     @classmethod
42     def _check_deps(cls):
43         if any([Image is None, transforms is None, cv2 is None]):
44             cls._raise_missing_dep(
45                 "PIL", "torchvision", "cv2")
46 
47     def read(self, images, side, img_dir=None):
48         """Read data into dicts.
49 
50         Args:
51             images (str or Iterable[str]): Sequence of image paths or
52                 path to file containing audio paths.
53                 In either case, the filenames may be relative to ``src_dir``
54                 (default behavior) or absolute.
55             side (str): Prefix used in return dict. Usually
56                 ``"src"`` or ``"tgt"``.
57             img_dir (str): Location of source image files. See ``images``.
58 
59         Yields:
60             a dictionary containing image data, path and index for each line.
61         """
62         if isinstance(images, str):
63             images = DataReaderBase._read_file(images)
64 
65         for i, filename in enumerate(images):
66             filename = filename.decode("utf-8").strip()
67             img_path = os.path.join(img_dir, filename)
68             if not os.path.exists(img_path):
69                 img_path = filename
70 
71             assert os.path.exists(img_path), \
72                 'img path %s not found' % filename
73 
74             if self.channel_size == 1:
75                 img = transforms.ToTensor()(
76                     Image.fromarray(cv2.imread(img_path, 0)))
77             else:
78                 img = transforms.ToTensor()(Image.open(img_path))
79             if self.truncate and self.truncate != (0, 0):
80                 if not (img.size(1) <= self.truncate[0]
81                         and img.size(2) <= self.truncate[1]):
82                     continue
83             yield {side: img, side + '_path': filename, 'indices': i}
84 
85 
86 def img_sort_key(ex):
87     """Sort using the size of the image: (width, height)."""
88     return ex.src.size(2), ex.src.size(1)
89 
90 
91 def batch_img(data, vocab):
92     """Pad and batch a sequence of images."""
93     c = data[0].size(0)
94     h = max([t.size(1) for t in data])
95     w = max([t.size(2) for t in data])
96     imgs = torch.zeros(len(data), c, h, w).fill_(1)
97     for i, img in enumerate(data):
98         imgs[i, :, 0:img.size(1), 0:img.size(2)] = img
99     return imgs
100 
101 
102 def image_fields(**kwargs):
103     img = Field(
104         use_vocab=False, dtype=torch.float,
105         postprocessing=batch_img, sequential=False)
106     return img
107
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/onmt/inputters/image_dataset.py b/onmt/inputters/image_dataset.py
--- a/onmt/inputters/image_dataset.py
+++ b/onmt/inputters/image_dataset.py
@@ -75,7 +75,8 @@
                 img = transforms.ToTensor()(
                     Image.fromarray(cv2.imread(img_path, 0)))
             else:
-                img = transforms.ToTensor()(Image.open(img_path))
+                img = Image.open(img_path).convert('RGB')
+                img = transforms.ToTensor()(img)
             if self.truncate and self.truncate != (0, 0):
                 if not (img.size(1) <= self.truncate[0]
                         and img.size(2) <= self.truncate[1]):

{"golden_diff": "diff --git a/onmt/inputters/image_dataset.py b/onmt/inputters/image_dataset.py\n--- a/onmt/inputters/image_dataset.py\n+++ b/onmt/inputters/image_dataset.py\n@@ -75,7 +75,8 @@\n                 img = transforms.ToTensor()(\n                     Image.fromarray(cv2.imread(img_path, 0)))\n             else:\n-                img = transforms.ToTensor()(Image.open(img_path))\n+                img = Image.open(img_path).convert('RGB')\n+                img = transforms.ToTensor()(img)\n             if self.truncate and self.truncate != (0, 0):\n                 if not (img.size(1) <= self.truncate[0]\n                         and img.size(2) <= self.truncate[1]):\n", "issue": "Alpha channel and grayscale in image-to-text with -image_channel_size=3\nFor training image to text, the argument `-image_channel_size=3` imply that the images already have the good number of channel. However, some of my images are black and white and saved with only one channel or saved in RGB but with the alpha channel.\r\nI could fix it with a change in `onmt/inputters/image_dataset.py` [here](https://github.com/OpenNMT/OpenNMT-py/blob/master/onmt/inputters/image_dataset.py#L78):\r\n\r\nfrom this:\r\n```\r\n            if self.channel_size == 1:\r\n                img = transforms.ToTensor()(\r\n                    Image.fromarray(cv2.imread(img_path, 0)))\r\n            else:\r\n                img = transforms.ToTensor()(Image.open(img_path))\r\n```\r\nto this:\r\n```\r\n            if self.channel_size == 1:\r\n                img = transforms.ToTensor()(\r\n                    Image.fromarray(cv2.imread(img_path, 0)))\r\n            else:\r\n                img = transforms.ToTensor()(\r\n                    Image.fromarray(cv2.imread(img_path, 1)))\r\n```\r\nThe flag in `cv2.imread` with value of 1 tell cv2 to convert to RGB no matter what the original image is.\r\n\r\nShould I do a PR ?\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\nimport os\n\nimport torch\nfrom torchtext.data import Field\n\nfrom onmt.inputters.datareader_base import DataReaderBase\n\n# domain specific dependencies\ntry:\n    from PIL import Image\n    from torchvision import transforms\n    import cv2\nexcept ImportError:\n    Image, transforms, cv2 = None, None, None\n\n\nclass ImageDataReader(DataReaderBase):\n    \"\"\"Read image data from disk.\n\n    Args:\n        truncate (tuple[int] or NoneType): maximum img size. Use\n            ``(0,0)`` or ``None`` for unlimited.\n        channel_size (int): Number of channels per image.\n\n    Raises:\n        onmt.inputters.datareader_base.MissingDependencyException: If\n            importing any of ``PIL``, ``torchvision``, or ``cv2`` fail.\n    \"\"\"\n\n    def __init__(self, truncate=None, channel_size=3):\n        self._check_deps()\n        self.truncate = truncate\n        self.channel_size = channel_size\n\n    @classmethod\n    def from_opt(cls, opt):\n        return cls(channel_size=opt.image_channel_size)\n\n    @classmethod\n    def _check_deps(cls):\n        if any([Image is None, transforms is None, cv2 is None]):\n            cls._raise_missing_dep(\n                \"PIL\", \"torchvision\", \"cv2\")\n\n    def read(self, images, side, img_dir=None):\n        \"\"\"Read data into dicts.\n\n        Args:\n            images (str or Iterable[str]): Sequence of image paths or\n                path to file containing audio paths.\n                In either case, the filenames may be relative to ``src_dir``\n                (default behavior) or absolute.\n            side (str): Prefix used in return dict. Usually\n                ``\"src\"`` or ``\"tgt\"``.\n            img_dir (str): Location of source image files. See ``images``.\n\n        Yields:\n            a dictionary containing image data, path and index for each line.\n        \"\"\"\n        if isinstance(images, str):\n            images = DataReaderBase._read_file(images)\n\n        for i, filename in enumerate(images):\n            filename = filename.decode(\"utf-8\").strip()\n            img_path = os.path.join(img_dir, filename)\n            if not os.path.exists(img_path):\n                img_path = filename\n\n            assert os.path.exists(img_path), \\\n                'img path %s not found' % filename\n\n            if self.channel_size == 1:\n                img = transforms.ToTensor()(\n                    Image.fromarray(cv2.imread(img_path, 0)))\n            else:\n                img = transforms.ToTensor()(Image.open(img_path))\n            if self.truncate and self.truncate != (0, 0):\n                if not (img.size(1) <= self.truncate[0]\n                        and img.size(2) <= self.truncate[1]):\n                    continue\n            yield {side: img, side + '_path': filename, 'indices': i}\n\n\ndef img_sort_key(ex):\n    \"\"\"Sort using the size of the image: (width, height).\"\"\"\n    return ex.src.size(2), ex.src.size(1)\n\n\ndef batch_img(data, vocab):\n    \"\"\"Pad and batch a sequence of images.\"\"\"\n    c = data[0].size(0)\n    h = max([t.size(1) for t in data])\n    w = max([t.size(2) for t in data])\n    imgs = torch.zeros(len(data), c, h, w).fill_(1)\n    for i, img in enumerate(data):\n        imgs[i, :, 0:img.size(1), 0:img.size(2)] = img\n    return imgs\n\n\ndef image_fields(**kwargs):\n    img = Field(\n        use_vocab=False, dtype=torch.float,\n        postprocessing=batch_img, sequential=False)\n    return img\n", "path": "onmt/inputters/image_dataset.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\nimport os\n\nimport torch\nfrom torchtext.data import Field\n\nfrom onmt.inputters.datareader_base import DataReaderBase\n\n# domain specific dependencies\ntry:\n    from PIL import Image\n    from torchvision import transforms\n    import cv2\nexcept ImportError:\n    Image, transforms, cv2 = None, None, None\n\n\nclass ImageDataReader(DataReaderBase):\n    \"\"\"Read image data from disk.\n\n    Args:\n        truncate (tuple[int] or NoneType): maximum img size. Use\n            ``(0,0)`` or ``None`` for unlimited.\n        channel_size (int): Number of channels per image.\n\n    Raises:\n        onmt.inputters.datareader_base.MissingDependencyException: If\n            importing any of ``PIL``, ``torchvision``, or ``cv2`` fail.\n    \"\"\"\n\n    def __init__(self, truncate=None, channel_size=3):\n        self._check_deps()\n        self.truncate = truncate\n        self.channel_size = channel_size\n\n    @classmethod\n    def from_opt(cls, opt):\n        return cls(channel_size=opt.image_channel_size)\n\n    @classmethod\n    def _check_deps(cls):\n        if any([Image is None, transforms is None, cv2 is None]):\n            cls._raise_missing_dep(\n                \"PIL\", \"torchvision\", \"cv2\")\n\n    def read(self, images, side, img_dir=None):\n        \"\"\"Read data into dicts.\n\n        Args:\n            images (str or Iterable[str]): Sequence of image paths or\n                path to file containing audio paths.\n                In either case, the filenames may be relative to ``src_dir``\n                (default behavior) or absolute.\n            side (str): Prefix used in return dict. Usually\n                ``\"src\"`` or ``\"tgt\"``.\n            img_dir (str): Location of source image files. See ``images``.\n\n        Yields:\n            a dictionary containing image data, path and index for each line.\n        \"\"\"\n        if isinstance(images, str):\n            images = DataReaderBase._read_file(images)\n\n        for i, filename in enumerate(images):\n            filename = filename.decode(\"utf-8\").strip()\n            img_path = os.path.join(img_dir, filename)\n            if not os.path.exists(img_path):\n                img_path = filename\n\n            assert os.path.exists(img_path), \\\n                'img path %s not found' % filename\n\n            if self.channel_size == 1:\n                img = transforms.ToTensor()(\n                    Image.fromarray(cv2.imread(img_path, 0)))\n            else:\n                img = Image.open(img_path).convert('RGB')\n                img = transforms.ToTensor()(img)\n            if self.truncate and self.truncate != (0, 0):\n                if not (img.size(1) <= self.truncate[0]\n                        and img.size(2) <= self.truncate[1]):\n                    continue\n            yield {side: img, side + '_path': filename, 'indices': i}\n\n\ndef img_sort_key(ex):\n    \"\"\"Sort using the size of the image: (width, height).\"\"\"\n    return ex.src.size(2), ex.src.size(1)\n\n\ndef batch_img(data, vocab):\n    \"\"\"Pad and batch a sequence of images.\"\"\"\n    c = data[0].size(0)\n    h = max([t.size(1) for t in data])\n    w = max([t.size(2) for t in data])\n    imgs = torch.zeros(len(data), c, h, w).fill_(1)\n    for i, img in enumerate(data):\n        imgs[i, :, 0:img.size(1), 0:img.size(2)] = img\n    return imgs\n\n\ndef image_fields(**kwargs):\n    img = Field(\n        use_vocab=False, dtype=torch.float,\n        postprocessing=batch_img, sequential=False)\n    return img\n", "path": "onmt/inputters/image_dataset.py"}]}