Ubicación incorrecta del rectángulo en matplotlib

Estoy haciendo un gráfico con barras y estoy tratando de encontrar su ubicación absoluta (en píxeles) en el gráfico para su posterior procesamiento. Me parece que esto debería poder determinarse a partir de la información de transformación de matplotlib en la instancia de los ejes. Específicamente, estoy usando ax.transData para ir desde las coordenadas de datos (donde conozco las posiciones de la caja) para mostrar las coordenadas. Aquí hay un código:

 import matplotlib.pyplot as plt x = range(10) y = range(1, 11) fig = plt.figure() ax = fig.add_subplot(111) bars = ax.bar(x, y, width=.5, label="foo") ax.monkey_rectangles = bars ax.legend() def get_useful_info(fig): for ax in fig.get_axes(): for rect in ax.monkey_rectangles: corners = rect.get_bbox().corners()[::3] pos = ax.transData.transform(corners) left = pos[0,0] width = pos[1,0] - pos[0,0] bottom = pos[0,1] height = pos[1,1] - pos[0,1] yield left, width, bottom, height fig.savefig('foo.png') for l, w, b, h in get_useful_info(fig): print l, w, b, h 

Esto imprime lo siguiente:

 80.0 24.8 48.0 38.4 129.6 24.8 48.0 76.8 179.2 24.8 48.0 115.2 228.8 24.8 48.0 153.6 278.4 24.8 48.0 192.0 328.0 24.8 48.0 230.4 377.6 24.8 48.0 268.8 427.2 24.8 48.0 307.2 476.8 24.8 48.0 345.6 526.4 24.8 48.0 384.0 

Entonces, matplotlib cree que mis cajas tienen 24.8 unidades (píxeles, supongo) de ancho. Eso está bien, excepto cuando realmente voy a medir el ancho de la caja, obtengo algo más como 32 píxeles de ancho. ¿Cuál es la discrepancia aquí?

Como de costumbre, la revelación ocurre al publicar la pregunta … El problema aquí es que las unidades no son píxeles , son puntos. Cuando se inicializa la figura, se configura con puntos por pulgada predeterminados ( fig.set_dpi() y fig.get_dpi() permiten cambiar / consultar la resolución de la figura actual). Por supuesto, las cosas no pueden ser tan fáciles: cuando guarda la figura, los ppp cambian según la configuración de rc para el backend en particular. Para mí, con el backend predeterminado y la salida png, esto va a 100 dpi. Sin embargo, una cosa que es invariante aquí es el tamaño de la figura fig.get_size_inches() . Entonces, si califico mis resultados, las cosas parecen cambiar un poco más consistentemente …

 def get_useful_info(fig): dpi = fig.get_dpi() pngdpi = 100 scale_x = pngdpi / dpi scale_y = pngdpi / dpi for ax in fig.get_axes(): for rect in ax.monkey_rectangles: corners = rect.get_bbox().corners()[::3] pos = ax.transData.transform(corners) left = pos[0,0] * scale_x width = (pos[1,0] - pos[0,0]) * scale_x bottom = pos[0,1] * scale_y height = (pos[1,1] - pos[0,1]) * scale_y yield left, width, bottom, height 

Sin embargo, tengo la sensación de que esta no es la solución completa. Después de todo, todavía estamos trabajando en puntos. No estoy completamente seguro de cómo se traduce un punto en un píxel para imágenes png … Mi esperanza es que el motor de visualización (en mi caso particular, un navegador web) muestre cada punto en un solo píxel y no le importe el Tamaño reportado de la imagen. Supongo que solo un poco de experimentación lo solucionará …

Como se refiere a los navegadores web, ¿asumo que está construyendo mapas de imágenes?

 import matplotlib.pyplot as plt x = range(10) y = range(1, 11) fig = plt.figure() ax = fig.add_subplot(111) bars = ax.bar(x, y, width=.5, label="foo") ax.monkey_rectangles = bars ax.legend() def fig_fraction_info(fig): for ax in fig.get_axes(): inv = fig.transFigure.inverted() # transformations are applied left to right my_trans = ax.transData + inv for rect in ax.monkey_rectangles: corners = rect.get_bbox().corners()[::3] pos = my_trans.transform(corners) left = pos[0,0] width = pos[1,0] - pos[0,0] bottom = pos[0,1] height = pos[1,1] - pos[0,1] yield left, width, bottom, height 

Creo que algo como esto es lo que quieres. Si alimenta estos datos y el png en el siguiente nivel de código, puede ordenar exactamente dónde está todo.

Cuestiones como esta son (creo) una consecuencia de la forma en que matplotlib abstrae el backend del front-end. Pensar en el tamaño en píxeles no tiene mucho sentido para las salidas vectoriales.

Alternativamente:

 dpi = 300 ... fig.set_dpi(dpi) ... fig.savefig(..., dpi=dpi)