10

I'm trying to do some image processing with matplotlib. However the y-axis is decreasing bottom up. I want it to be increasing bottom up without flipping the image upside down

I have the following code

import matplotlib.pyplot as plt
import numpy as np
img_path = '/path/to/image.tif'
img = plt.imread(img_path)
plt.imshow(img, cmap = 'gray')

it produces the following image: enter image description here

The images can be obtained there

I tried plt.gca().invert_yaxis() without success

What shall I do

john-hen
  • 4,410
  • 2
  • 23
  • 40
ecjb
  • 5,169
  • 12
  • 43
  • 79
  • One probably cannot explain this better than [the matplotlib tutorial on origin and extent](https://matplotlib.org/3.1.0/tutorials/intermediate/imshow_extent.html) already does. – ImportanceOfBeingErnest Jul 06 '19 at 18:47
  • Thank you for your comment @ImportanceOfBeingErnest. That's a good reference indeed. I'm trying to find my answer yet, although I didn't find a straightforward answer to the question yet – ecjb Jul 06 '19 at 19:53

1 Answers1

14

The default behavior of imshow is to put the origin of the coordinate system in the upper left corner. This is different from plotting scientific data, such as two entities x and y against each other, where the origin, i.e. the point corresponding to the coordinate (0,0), is in the lower left corner, with the (positive) x-axis extending to the right and the (positive) y-axis extending towards the top.

The latter is just scientific convention, though one that goes back centuries. Arguably (albeit near impossible to back up with historical evidence), the x-axis is traditionally aligned left-to-right because that's how text is written in many languages, while the y-axis is oriented towards the top as that's how people intuit an increase — much like the elevation of terrain.

For images, on the other hand, the existing convention is rooted in the memory layout of pixel data and the way one might arrange consecutive pixels on the canvas: from left to right on the first line (by the same logic as above), then from the left again on the next line, and so on for all other lines, going from top to bottom. Just like words arranged on a page — in languages written from left to right and, much more universally so, top to bottom.

It is for that reason that the y-axis in your image is oriented the way it is. To have the y-values increase from the bottom up, you could invoke the option origin='lower' so that the input data is interpreted as per the scientific convention. However, you then also need to flip the image's lines upside down so that, when displayed on the screen, the image appears in its intended orientation. That's because what used to be the first line when the image was loaded into memory now corresponds to the last y-coordinate, the one at the top.

Bottom line (pun not unintended), just call imshow like so in the above code:

plt.imshow(np.flipud(img), cmap='gray', origin='lower')

To illustrate further, here is a self-contained example that demonstrates the behavior:

from imageio import imread
image = imread('https://upload.wikimedia.org/wikipedia/commons'
               '/thumb/6/6a/Mona_Lisa.jpg/158px-Mona_Lisa.jpg')

from matplotlib import pyplot
figure = pyplot.figure(tight_layout=True)
(axes1, axes2, axes3) = figure.subplots(nrows=1, ncols=3)

axes1.set_title("origin='upper'")
axes1.imshow(image)

axes2.set_title("origin='lower'")
axes2.imshow(image, origin='lower')

axes3.set_title("'lower' + flipped")
axes3.imshow(image[::-1], origin='lower')

pyplot.show()

The example requires ImageIO to be installed in order to retrieve the sample image. Its output is:

output of demo script

(In the example's code, I used image[::-1] to flip the image, instead of the aforementioned, and equivalent, syntax np.flipud(image). All that does is avoid an explicit import of NumPy (as np), i.e., an extra line of code. Implicitly, NumPy is still doing the work.)

Similar questions:

john-hen
  • 4,410
  • 2
  • 23
  • 40
  • 1
    So because the origin is "upper" by default anyways, we can keep it as such. What needs to change is the extent; in this case `plt.imshow(img, extent=[-.5, img.shape[1]-0.5, -0.5, img.shape[0]-0.5])`. This is the first row of the [explicit extent](https://matplotlib.org/3.1.0/tutorials/intermediate/imshow_extent.html#explicit-extent) section. – ImportanceOfBeingErnest Jul 06 '19 at 22:54