1

Let's say I have a picture taken with a sensor where the pixel size is 1mm. I would like to show the image with imshow: the main axes should show the pixel while the secondary axes should show the mm.

frassino.png is the following picture

enter image description here

from matplotlib import pyplot as plt
import cv2
import numpy as np

a = cv2.imread('frassino.png')
fig,ax = plt.subplots(1)
ax.imshow(a,aspect='equal')
ax.set_xlabel('pixel')
ax.set_ylabel('pixel')
ax.figure.savefig('1.png')

1.png is the following picture, all is fine (I need the pixel to be square and so I add the argument aspect='equal'.

enter image description here

Now I add a secondary y axis:

v2 = ax.twinx()
v2.set_yticks(np.linspace(0,48,12))
v2.set_xlabel('mm')
ax.figure.savefig('2.png')

2.png is the following picture and I have two problems: first, the image is cropped and the upper part of the tree, like the foreground grass, is not visible; second, the mm label is truncated.

enter image description here

Now I add the secondary x axis:

h2 = ax.twiny()
h2.set_xticks(np.linspace(0,64,8))
h2.set_xlabel('mm')
ax.figure.savefig('3.png')

The following picture is 3.png, the mm label is there but the image is still cropped.

enter image description here

How can the crop be avoided?

How can the y mm label be fixed?

Alessandro Jacopson
  • 18,047
  • 15
  • 98
  • 153
  • 1
    The second question is due to typo: you wrote `v2.set_xlabel('mm')` instead of `v2.set_ylabel('mm')` – Andrey Sobolev Feb 13 '23 at 13:04
  • @AndreySobolev Thank you, I've fixed the typo; now the label is just slightly truncated :-) – Alessandro Jacopson Feb 13 '23 at 14:18
  • For the truncated label, you can call `plt.tight_layout()`, which usually solves this type of issues. – JohanC Feb 13 '23 at 14:36
  • You want to adjust the right side of your figure so that the label fits. I added `fig.subplots_adjust(right=0.850)` between the figure creation and `ax.imshow`. This has to be done before imshow because the `aspect="equal"` will make further changes throw runtime errors. – Guimoute Feb 13 '23 at 14:37
  • Did you try a ["secondary" axis](https://matplotlib.org/stable/gallery/subplots_axes_and_figures/secondary_axis.html) instead of `twinx()` and `twiny()`. Such a secondary axis only add extra labels, while `twinx()` internally creates a new transparent subplot on top of the existing one. (Also related: [imshow with second y-axis](https://stackoverflow.com/questions/48255824/matplotlib-imshow-with-second-y-axis), but is unclear whether you have the same issue). – JohanC Feb 13 '23 at 14:40

1 Answers1

2

Based on an example found in the documentation, it seems the solution is to use secondary axes rather than twin axes. That will prevent datalim conflicts between the axes holding the image and the other ones that are only there to hold a different scale and ticks but no artist.

from matplotlib import pyplot as plt
import cv2
import numpy as np

a = cv2.imread('frassino.png')
fig, ax = plt.subplots(1)
ax.imshow(a, aspect='equal')
ax.set_xlabel('pixel')
ax.set_ylabel('pixel')
ax.figure.savefig('1.png')

def px_to_mm(values):
    return values/10

def mm_to_px(values):
    return 10*values

v2 = ax.secondary_yaxis('right', functions=(px_to_mm, mm_to_px))
v2.set_yticks(np.linspace(0, 64, 12, endpoint=True))
v2.set_ylabel('mm')
ax.figure.savefig('2.png')

h2 = ax.secondary_xaxis('top', functions=(px_to_mm, mm_to_px))
h2.set_xticks(np.linspace(0, 48, 8, endpoint=True))
h2.set_xlabel('mm')
ax.figure.savefig('3.png')

enter image description here

Guimoute
  • 4,407
  • 3
  • 12
  • 28