Repository: python-pillow/Pillow

Issue:
TIFF orientation tag handled differently from most other software when loading G4 TIFF files
Most image software handles TIFF orientation tags  (tag 274) differently from Pillow when loading  binary G4 TIFF images. This means that only images having the orientation tag == 1 (The 0th row represents the visual top of the image, and the 0th column represents the visual left-hand side) are loaded correctly (in the same way as in other software), other images should be corrected manually. 
I am not sure whether Pillow regards the orientation tags while other software ignores or vice versa. At least  they are loaded transposed.
I use Pillow-6.1.0 with Python 3.8 on Windows.

I've developed the following procedure in my script to load TIFF files correctly (basically I only have files with orientation==8 from my office copier machine, so I have not tested other orientations). I think this something similar should be part of the TIFFImagePlugin

```python
from PIL import Image
from tkinter import filedialog, Tk
ORIENTATION = 274
root = Tk()
root.withdraw()
file_selected = filedialog.askopenfilename()
srcim = Image.open(filename)
orientation = srcim.tag[ORIENTATION][0]
format = os.path.splitext(filename)[1]
if format.lower() in [".tif", ".tiff"]:
     try:
        if orientation == 8:
            srcim = srcim.transpose(Image.ROTATE_90)
        elif orientation == 3:
            srcim = srcim.transpose(Image.ROTATE_180)
        elif orientation == 6:
            srcim = srcim.transpose(Image.ROTATE_270)
        elif orientation == 2:
            srcim = srcim.transpose(Image.FLIP_LEFT_RIGHT)
        elif orientation == 4:
            srcim = srcim.transpose(Image.FLIP_TOP_BOTTOM)
        elif orientation == 5:
            srcim = srcim.transpose(Image.ROTATE_90).transpose(Image.FLIP_LEFT_RIGHT)
        elif orientation == 7:
            srcim = srcim.transpose(Image.ROTATE_270).transpose(Image.FLIP_LEFT_RIGHT)
     except:
          print("Can't detect orientation")
```




Discussion:
Hi. In Pillow 6.0, `ImageOps.exif_tranpose` was added - https://github.com/python-pillow/Pillow/blob/1e3c2c9ce1aea8536e95ce74086a79793e467618/src/PIL/ImageOps.py#L523-L530

So you should be able to simplify your code down to -

```python
from PIL import Image, ImageOps
from tkinter import filedialog, Tk
root = Tk()
root.withdraw()
file_selected = filedialog.askopenfilename()
srcim = Image.open(filename)
srcim = ImageOps.exif_transpose(src_im)
```

If you're asking why this is not applied automatically, the answer would be that `ImageOps.exif_transpose` has not always been a part of Pillow, and we value backwards compatibility highly.
Hi, thanks for the reply. No, `exif_transpose` does not help. I mean TIFF tags, not the EXIF, it is a different thing. Try loading this file with PIL 
It shows upright in most graphic software, but PIL displays it rotated, because it disregards the TIFF 274 tag (orientation)
[test1.zip](https://github.com/python-pillow/Pillow/files/3587110/test1.zip)


Here is the updated procedure for correcting TIFF file orientation: 

```python
from PIL import Image
TIFFTAG_ORIENTATION = 274
def correct_tiff(srcim):
    try:
        orientation = srcim.tag[TIFFTAG_ORIENTATION][0]
        

        if orientation == 2:
            srcim = srcim.transpose(Image.FLIP_LEFT_RIGHT)
        elif orientation == 3:
            srcim = srcim.transpose(Image.ROTATE_180)            
        elif orientation == 4:
            srcim = srcim.transpose(Image.FLIP_TOP_BOTTOM)
        elif orientation == 5:
            srcim = srcim.transpose(Image.TRANSPOSE)
        elif orientation == 6:
            srcim = srcim.transpose(Image.ROTATE_270)            
        elif orientation == 7:
            srcim = srcim.transpose(Image.TRANSVERSE)
        elif orientation == 8:
            srcim = srcim.transpose(Image.ROTATE_90)       
    except:
        pass
    return srcim
```
and a small test script with a full set of test files (tiff G4 and uncompressed)
attached.
 
[tiff orientation test.zip](https://github.com/python-pillow/Pillow/files/3587366/tiff.orientation.test.zip)


Thanks for all that. Could any of those images be be added to the test suite, under the Pillow license?
Yes, both code snippets and the images are absolutely free to use under any license. 
Okay. I've created #4062 to add an `ImageOps.auto_transpose` method. See what you think.

Resolving patch:
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -1098,6 +1098,20 @@ def load(self):
         return super(TiffImageFile, self).load()
 
     def load_end(self):
+        if self._tile_orientation:
+            method = {
+                2: Image.FLIP_LEFT_RIGHT,
+                3: Image.ROTATE_180,
+                4: Image.FLIP_TOP_BOTTOM,
+                5: Image.TRANSPOSE,
+                6: Image.ROTATE_270,
+                7: Image.TRANSVERSE,
+                8: Image.ROTATE_90,
+            }.get(self._tile_orientation)
+            if method is not None:
+                self.im = self.im.transpose(method)
+                self._size = self.im.size
+
         # allow closing if we're on the first frame, there's no next
         # This is the ImageFile.load path only, libtiff specific below.
         if not self._is_animated:
@@ -1181,6 +1195,9 @@ def _load_libtiff(self):
 
         self.tile = []
         self.readonly = 0
+
+        self.load_end()
+
         # libtiff closed the fp in a, we need to close self.fp, if possible
         if self._exclusive_fp and not self._is_animated:
             self.fp.close()
@@ -1388,6 +1405,8 @@ def _setup(self):
             palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]]
             self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
 
+        self._tile_orientation = self.tag_v2.get(0x0112)
+
     def _close__fp(self):
         try:
             if self.__fp != self.fp:


Test patch:
diff --git a/Tests/images/g4_orientation_1.tif b/Tests/images/g4_orientation_1.tif
new file mode 100755
Binary files /dev/null and b/Tests/images/g4_orientation_1.tif differ
diff --git a/Tests/images/g4_orientation_2.tif b/Tests/images/g4_orientation_2.tif
new file mode 100755
Binary files /dev/null and b/Tests/images/g4_orientation_2.tif differ
diff --git a/Tests/images/g4_orientation_3.tif b/Tests/images/g4_orientation_3.tif
new file mode 100755
Binary files /dev/null and b/Tests/images/g4_orientation_3.tif differ
diff --git a/Tests/images/g4_orientation_4.tif b/Tests/images/g4_orientation_4.tif
new file mode 100755
Binary files /dev/null and b/Tests/images/g4_orientation_4.tif differ
diff --git a/Tests/images/g4_orientation_5.tif b/Tests/images/g4_orientation_5.tif
new file mode 100755
Binary files /dev/null and b/Tests/images/g4_orientation_5.tif differ
diff --git a/Tests/images/g4_orientation_6.tif b/Tests/images/g4_orientation_6.tif
new file mode 100755
Binary files /dev/null and b/Tests/images/g4_orientation_6.tif differ
diff --git a/Tests/images/g4_orientation_7.tif b/Tests/images/g4_orientation_7.tif
new file mode 100755
Binary files /dev/null and b/Tests/images/g4_orientation_7.tif differ
diff --git a/Tests/images/g4_orientation_8.tif b/Tests/images/g4_orientation_8.tif
new file mode 100755
Binary files /dev/null and b/Tests/images/g4_orientation_8.tif differ
diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py
--- a/Tests/test_file_libtiff.py
+++ b/Tests/test_file_libtiff.py
@@ -831,3 +831,12 @@ def test_old_style_jpeg(self):
         self.assert_image_equal_tofile(
             im, "Tests/images/old-style-jpeg-compression.png"
         )
+
+    def test_orientation(self):
+        base_im = Image.open("Tests/images/g4_orientation_1.tif")
+
+        for i in range(2, 9):
+            im = Image.open("Tests/images/g4_orientation_" + str(i) + ".tif")
+            im.load()
+
+            self.assert_image_similar(base_im, im, 0.7)