Невозможно сохранить соотношение сторон пикселя при определении экстента

Я работаю с изображениями DICOM, и хотя я могу отображать их с правильным соотношением сторон при использовании imshow с осями extent, разработанными matplotlib, у меня возникают проблемы с соотношением сторон графика при ручной установке extent. Цель: изменить оси с номеров пикселей на физические координаты вокселя (в мм).

Изображения представляют собой изображения SimpleITK — в этом случае каждое 3D-изображение состоит из стопки 2D-изображений. Для простоты ниже представлен только один срез (SliceInd = 18) каждого изображения. Для тех, кто не знаком с SimpleITK, команда:

PixArr3D = sitk.GetArrayFromImage(Image)

создает массив numpy из изображения ситка (Image).

Чтобы проиллюстрировать проблемы, приведенный ниже код предназначен для подграфика 2x3 (2 строки x 3 столбца). Я перебираю два 3D-изображения (FixIm и MovIm) (показаны вдоль строк), и для каждого изображения я рисую изображение 3 раза: один раз без определения экстента для imshow (столбец 1) и дважды с заданным вручную extent: один раз с использованием соотношение сторон пикселя (x и y) (PixAR, столбец 2) и один раз с использованием соотношения сторон самих осей (AxisAR, столбец 3).

Я следовал ответам участников на аналогичные вопросы по SO относительно использования extent и aspect для imshow (например, Pyplot imshow не показывает квадратные пиксели при настройке соотношения сторон; и Изменить значения на оси графика matplotlib imshow()). Несмотря на мои попытки следовать указаниям других, мне не удалось сохранить пропорции пикселей.

import numpy as np
import SimpleITK as sitk
import matplotlib.pyplot as plt
%matplotlib inline
%matplotlib notebook

SliceInd = 18

Images = [FixIm, MovIm]

Txts = ['Session 1', 'Session 2']

Nrows = 2
Ncols = 3

fig, ax = plt.subplots(Nrows, Ncols, figsize=(14, 7*Nrows))

SubPlotNum = 0

for i in range(len(Images)):
    Image = Images[i]
    Txt = Txts[i]
    
    
    PixArr3D = sitk.GetArrayFromImage(Image)


    # Get the variables required for pixel and axis aspect ratios, and 
    # the axis extent.

    PixArrShape = PixArr3D.shape

    # Get dimensions in [x, y, z] order:
    PixDims = [PixArrShape[i] for i in [1, 2, 0]]

    Origin = Image.GetOrigin()

    PixSpacings = Image.GetSpacing() # e.g. (0.8984375, 0.8984375, 5.0) => (x, y, z)

    Orient = [Image.GetDirection()[i] for i in range(len(Image.GetDirection()))]

    # Reverse the sign of the cross terms so that the vectors are defined in
    # the same way as Pydicom:
    for i in [1, 2, 3, 5, 6, 7]:
        Orient[i] = - Orient[i]

    # Pixel aspect ratio:
    PixAR = PixSpacings[1]/PixSpacings[0] # y/x
    

    # Get the axis extent and aspect ratio.

    # Since Origin is defined at voxel [0, 0, 0], the min/max x and y 
    # coordinates are given by Origin[0] and Origin[1] + the x and y
    # components of the z-direction cosine (Orient[6] and Orient[7]):
    Xmin = Origin[0] + SliceInd*PixSpacings[2]*Orient[6]
    Ymin = Origin[1] + SliceInd*PixSpacings[2]*Orient[7]

    Xmax = Xmin + PixDims[0]*PixSpacings[0]*Orient[0] + PixDims[1]*PixSpacings[1]*Orient[3]
    Ymax = Ymin + PixDims[0]*PixSpacings[0]*Orient[1] + PixDims[1]*PixSpacings[1]*Orient[4]

    AxisExtent = [Xmin, Xmax, Ymax, Ymin]

    AxisDx = (Xmax - Xmin) / PixDims[0]
    AxisDy = (Ymax - Ymin) / PixDims[1]

    # Axis aspect ratio:
    AxisAR = AxisDx / AxisDy


    # Get 2D pixel array for axial perspective:
    PixArr2D = PixArr3D[SliceInd,:,:]


    # Sub-plots:

    # Plot with default extent:
    SubPlotNum += 1
    ax = plt.subplot(Nrows, Ncols, SubPlotNum)
    
    ax.imshow(PixArr2D, cmap=plt.cm.Greys_r, aspect=PixAR)
    
    ax.set_title(Txt + f'\naspect = PixAR = {PixAR}' \
                 + '\nextent = default')
    
    # Plot with extent=AxisExtent and aspect=PixAR:
    SubPlotNum += 1
    ax = plt.subplot(Nrows, Ncols, SubPlotNum)
    
    ax.imshow(PixArr2D, cmap=plt.cm.Greys_r, extent=AxisExtent,
              aspect=PixAR)
    
    ax.set_title(Txt + f'\naspect = PixAR = {PixAR}' \
                 + '\nextent = AxisExtent')
    
    # Plot with extent=AxisExtent and aspect=AxisAR:
    SubPlotNum += 1
    ax = plt.subplot(Nrows, Ncols, SubPlotNum)
    
    ax.imshow(PixArr2D, cmap=plt.cm.Greys_r, extent=AxisExtent,
              aspect=AxisAR)
    
    ax.set_title(Txt + f'\naspect = AxisAR = {round(AxisAR,2)}' \
                 + '\nextent = AxisExtent')

Я также попытался добавить следующую строку:

ax.set_aspect(PixAR)

после команды imshow.

Моя интуиция подсказывает мне, что aspect должно быть установлено на PixAR, а не на AxisAR, но, возможно, я неверно истолковал аргумент aspect в imshow. В любом случае, я пытался использовать как PixAR, так и AxisAR, и стало ясно, что оба они не сохраняют соотношение сторон.

Я изо всех сил пытаюсь найти какие-либо подсказки относительно того, почему это не работает, как я ожидаю, поэтому любые советы будут очень признательны.

Результирующий_график


person critor    schedule 14.09.2020    source источник
comment
Предлагаю начать здесь. matplotlib.org/3.3.1/tutorials/intermediate/imshow_extent.html Обратите внимание, что aspect указывается в единицах данных. extent — это просто края изображения в единицах данных, и ячейки будут линейно распределены между этими краями.   -  person Jody Klymak    schedule 15.09.2020