Можете ли вы заставить wspace и hspace подзаголовков фигуры иметь фиксированное значение в matplotlib независимо от размера фигуры

avatar
Josh Duran
1 июля 2021 в 19:25
108
1
0

Я пытаюсь создать функцию для построения нескольких изображений в сетке с одной цветовой полосой и гистограммой. Я хотел бы, чтобы расстояние между всеми графиками было фиксированным значением, а цветная полоса охватывала высоту всех изображений, а гистограмма - ширину изображений/цветной полосы. У меня есть некоторый код, который работает, но для его работы требуется, чтобы размер фигуры был установлен на определенное соотношение сторон. Это не идеально, потому что я хочу использовать эту функцию для изображений с разным соотношением сторон и для разного количества изображений 2x1, 1x2, 2x2 и т. д.

Этот код выводит 3 цифры с разным соотношением сторон. Я хотел бы, чтобы какой-либо избыточный размер применялся к интервалу между границами, а не к подзаголовку wspace, hspace spacing.

ширина риса: https://i.stack.imgur.com/BB1Cz.png

высота инжира: https://i.stack.imgur.com/G5C34.png

рис хороший: https://i.stack.imgur.com/AVX6C.png

Вот код:

import math
import numpy as np
import matplotlib as mpl
from matplotlib import pyplot as plt


def compare_frames(frames, columns, bins=256, alpha=.5, vmin=None, vmax=None, fig=None):
    if vmin is None:
        vmin = min([f.min() for f in frames])
    if vmax is None:
        vmax = max([f.max() for f in frames])
    if fig == None:
        fig = plt.figure()
    color_cycle = plt.get_cmap('tab10')
    rows = math.ceil(len(frames)/columns)
    width_ratios = [1 for col in range(columns)] + [.05]
    
    gs = mpl.gridspec.GridSpec(rows + 1, columns + 1, figure=fig, width_ratios=width_ratios)
    images = []
    for row in range(rows):
        for col in range(columns):
            idx = row*columns + col
            if idx < len(frames):
                ax = fig.add_subplot(gs[row, col])
                ax.get_xaxis().set_ticks([])
                ax.get_yaxis().set_ticks([])
                for spine in ['bottom', 'top', 'left', 'right']:
                    ax.spines[spine].set_color(color_cycle(idx))
                    ax.spines[spine].set_linewidth(3)
                images.append(ax.imshow(frames[idx], vmin=vmin, vmax=vmax))
    
    cax = fig.add_subplot(gs[0:-1, -1])
    plt.colorbar(images[0], cax=cax)

    hax = fig.add_subplot(gs[-1, :])
    for i, frame in enumerate(frames):
        hax.hist(frame.ravel(), bins=256, range=(vmin, vmax), color=color_cycle(i), alpha=alpha)
    
    fig.subplots_adjust(wspace=.05, hspace=.05)


if __name__ == '__main__':
    x_size = 640
    y_size = 512
    frames = []
    for i in range(4):
        frames.append(np.random.normal(i + 1, np.sqrt(i + 1), size=(y_size, x_size)))
    
    fig_wide = plt.figure(figsize=(12, 8))
    compare_frames(frames, 2, fig=fig_wide)
    fig_tall = plt.figure(figsize=(6, 8))
    compare_frames(frames, 2, fig=fig_tall)
    fig_nice = plt.figure(figsize=(6.9, 8))
    compare_frames(frames, 2, fig=fig_nice)
    plt.show()
Источник
Jody Klymak
2 июля 2021 в 19:55
0

Вам нужно, чтобы соотношение сторон «изображений» было один к одному? Если это так, то вы просите о чем-то, что относительно сложно сделать в целом. В частности, вы можете, конечно, просто разместить оси вручную, используя set_position

Josh Duran
6 июля 2021 в 01:44
0

Да, мне нужно сохранить ряд изображений. Реальные изображения, которые я буду использовать, — это данные сенсора камеры, которые я хочу сохранить в соотношении сторон. Однако я не буду путать пропорции изображений (все кадры, которые я сравниваю, будут иметь одинаковое соотношение сторон).

Ответы (1)

avatar
Josh Duran
6 июля 2021 в 01:55
0

Я понял, что мне, вероятно, следует использовать matplotlib axes_grid1 из mpl_toolkits. У них есть встроенный класс ImageGrid, который делает многое из того, что я хотел бы сделать (фиксированный интервал для изображений и цветовой полосы):

def compare_frames(frames, columns, bins=256, alpha=.5, vmin=None, vmax=None, fig=None):
    if vmin is None:
        vmin = min([f.min() for f in frames])
    if vmax is None:
        vmax = max([f.max() for f in frames])
    if fig == None:
        fig = plt.figure()
    color_cycle = plt.get_cmap('tab10')
    rows = math.ceil(len(frames)/columns)
    
    im_grid = axes_grid1.ImageGrid(fig, 111, nrows_ncols=(rows, columns), axes_pad=.1,
        cbar_mode='single', cbar_pad=.1, cbar_size=.3)
    for i, ax in enumerate(im_grid):
        im = ax.imshow(frames[i], vmin=vmin, vmax=vmax)
        ax.get_xaxis().set_ticks([])
        ax.get_yaxis().set_ticks([])
        for spine in ['bottom', 'top', 'left', 'right']:
            ax.spines[spine].set_color(color_cycle(i))
            ax.spines[spine].set_linewidth(3)
    cbar = fig.colorbar(im, cax=im_grid.cbar_axes[0])

Это здорово, и я хотел бы найти способ использовать этот класс ImageGrid для выполнения большей части работы, а затем добавить еще одну ось внизу для гистограммы. Однако я не смог взломать, как это сделать, поскольку все примеры, которые я нашел, используют «append_axes()» в классе Divider. Однако ImageGrid формирует SubplotDivider, который не имеет функции append_axes.