将1D numpy径向强度阵列旋转为空间强度的二维阵列

时间:2017-06-12 14:03:19

标签: python arrays numpy rotation

我有一个numpy数组,在不同半径的均匀圆圈中填充强度读数(对于上下文,这是原始星形成模型的一维辐射传输项目:虽然存在更好的模型,但我的主管让我有经验制作一个,所以我理解其他人是如何工作的。)

我想采用那个1d数组,"旋转"它通过一个圆圈,形成一个2D阵列的强度,然后可以用imshow(或者,有点工作,aplpy)显示。最终的数组必须是2d,投影需要是笛卡尔,而不是极坐标。

我可以用嵌套的for循环来做,我可以用查找表来做,但我觉得必须有一个简洁的方法来做numpy或其他东西。

有什么想法吗?

编辑:

我不得不回过头来重新创建我的(坦率地说是可怕的)乱七八糟的乱七八糟的声音以及我以前的声明。如果我真的尝试过,我可能会通过压缩事情来摆脱其中一个循环和一个if语句。但是,目的不是让它适用于for循环,而是看看是否有内置的方法来旋转数组。 impB是一个与我之前所说的略有不同的数组。它实际上只是一个检测到粒子的半径列表。然后我将它们分成半径区域,以获得每个半径中的强度(或者如果您愿意,可以选择频率)。当我以无量纲方式运行模型时,R是我的半径的比例因子。 iRes是一个分辨率比例因子,基本上我想要采样径向箱的频率。其他一切都应该清楚。

radJ = np.ndarray(shape=(2*iRes, 2*iRes))    # Create array of 2xRadius square

for i in range(iRes):
    n = len(impB[np.where(impB[:] < ((i+1.) * (R / iRes)))])    # Count number of things within this radius +1
    m = len(impB[np.where(impB[:] <= ((i) * (R / iRes)))])      # Count number of things in this radius
    a = (((i + 1) * (R / iRes))**2 - ((i) * (R / iRes))**2) * math.pi    # A normalisation factor based on area.....dont ask
    for x in range(iRes):
        for y in range(iRes):
            if (x**2 + y**2) < (i * iRes)**2:
                if (x**2 + y**2) >= (i * iRes)**2:    # Checks for radius, and puts in cartesian space
                    radJ[x+iRes,y+iRes] = (n-m) / a    # Put in actual intensity bins
                    radJ[x+iRes,-y+iRes] = (n-m) / a
                    radJ[-x+iRes,y+iRes] = (n-m) / a
                    radJ[-x+iRes,-y+iRes] = (n-m) / a

2 个答案:

答案 0 :(得分:0)

嵌套循环是一种简单的方法。如果ri_data_ry分别包含您的半径值(与中间像素的差异)和旋转数组,我建议:

from scipy import interpolate
import numpy as np
y = np.random.rand(100)
ri_data_r = np.linspace(-len(y)/2,len(y)/2,len(y))
interpol_index = interpolate.interp1d(ri_data_r, y)
xv = np.arange(-1, 1, 0.01) # adjust your matrix values here
X, Y = np.meshgrid(xv, xv)
profilegrid = np.ones(X.shape, float)
for i, x in enumerate(X[0, :]):
    for k, y in enumerate(Y[:, 0]):
        current_radius = np.sqrt(x ** 2 + y ** 2)
        profilegrid[i, k] = interpol_index(current_radius)
print(profilegrid)

这将为您提供您正在寻找的内容。您只需要接收数组并计算与数据数组长度相同的对称数组ri_data_r,并包含实际数据与数组中间的距离。代码自动执行此操作。

答案 1 :(得分:0)

我在不同的背景下偶然发现了这个问题,希望我理解正确。这是执行此操作的其他两种方法。第一个使用skimage.transform.warp进行所需顺序的插值(此处使用order = 0最近邻)。与第二种方法相比,此方法速度较慢,但​​更精确,并且所需的内存更少。

第二个不使用插值,因此速度更快,但精度也更低,并且需要更多的内存,因为它存储每个包含一个倾斜度的2D数组直到最后,并用np.nanmean()进行平均。

这两种解决方案之间的差异源于处理倾斜重叠最大的最终图像中心的问题,即第一个图像只会添加每个倾斜最终超出原始范围的值。通过在每个步骤中将矩阵裁剪为global_minglobal_max(请咨询代码)来“解决”此问题。第二个解决方案是采用重叠的倾斜度的平均值来解决,这迫使我们使用np.nan

请阅读用法示例完整性检查部分,以了解地块标题。

解决方案1:

import numpy as np
from skimage.transform import warp

def rotate_vector(vector, deg_angle):
    # Credit goes to skimage.transform.radon
    assert vector.ndim == 1, 'Pass only 1D vectors, e.g. use array.ravel()'
    center = vector.size // 2
    square = np.zeros((vector.size, vector.size))
    square[center,:] = vector
    rad_angle = np.deg2rad(deg_angle)
    cos_a, sin_a = np.cos(rad_angle), np.sin(rad_angle)
    R = np.array([[cos_a, sin_a, -center * (cos_a + sin_a - 1)],
                  [-sin_a, cos_a, -center * (cos_a - sin_a - 1)],
                  [0, 0, 1]])
    # Approx. 80% of time is spent in this function
    return warp(square, R, clip=False, output_shape=((vector.size, vector.size)))

def place_vectors(vectors, deg_angles):
    matrix = np.zeros((vectors.shape[-1], vectors.shape[-1]))
    global_min, global_max = 0, 0
    for i, deg_angle in enumerate(deg_angles):
        tilt = rotate_vector(vectors[i], deg_angle)
        global_min = tilt.min() if global_min > tilt.min() else global_min
        global_max = tilt.max() if global_max < tilt.max() else global_max
        matrix += tilt
        matrix = np.clip(matrix, global_min, global_max)
    return matrix

Solution 1: Visualization of outputs

解决方案2:

这个主意归功于我的同事Michael Scherbela。

import numpy as np

def rotate_vector(vector, deg_angle):
    assert vector.ndim == 1, 'Pass only 1D vectors, e.g. use array.ravel()'
    square = np.ones([vector.size, vector.size]) * np.nan
    radius = vector.size // 2
    r_values = np.linspace(-radius, radius, vector.size)
    rad_angle = np.deg2rad(deg_angle)
    ind_x = np.round(np.cos(rad_angle) * r_values + vector.size/2).astype(np.int)
    ind_y = np.round(np.sin(rad_angle) * r_values + vector.size/2).astype(np.int)
    ind_x = np.clip(ind_x, 0, vector.size-1)
    ind_y = np.clip(ind_y, 0, vector.size-1)
    square[ind_y, ind_x] = vector
    return square

def place_vectors(vectors, deg_angles):
    matrices = []
    for deg_angle, vector in zip(deg_angles, vectors):
        matrices.append(rotate_vector(vector, deg_angle))
    matrix = np.nanmean(np.array(matrices), axis=0)
    return np.nan_to_num(matrix, copy=False, nan=0.0)

Solution 2: Visualization of outputs

用法示例:

r = 100  # Radius of the circle, i.e. half the length of the vector
n = int(np.pi * r / 8)  # Number of vectors, e.g. number of tilts in tomography
v = np.ones(2*r)  # One vector, e.g. one tilt in tomography
V = np.array([v]*n)  # All vectors, e.g. a sinogram in tomography

# Rotate 1D vector to a specific angle (output is 2D)
angle = 45
rotated = rotate_vector(v, angle) 

# Rotate each row of a 2D array according to its angle (output is 2D)
angles = np.linspace(-90, 90, num=n, endpoint=False)
inplace = place_vectors(V, angles)

健全性检查:

这些只是简单的检查,绝不能涵盖所有可能的极端情况。根据您的用例,您可能需要扩展检查并调整方法。

# I. Sanity check
# Assuming n <= πr and v = np.ones(2r)
# Then sum(inplace) should be approx. equal to (n * (2πr - n)) / π 
# which is an area that should be covered by the tilts

desired_area = (n * (2 * np.pi * r - n)) / np.pi
covered_area = np.sum(inplace)
covered_frac = covered_area / desired_area
print(f'This method covered {covered_frac * 100:.2f}% '
      'of the area which should be covered in total.')

# II. Sanity check
# Assuming n <= πr and v = np.ones(2r)
# Then a circle M with radius m <= r should be the largest circle which
# is fully covered by the vectors. I.e. its mean should be no less than 1.
# If n = πr then m = r. 
# m = n / π

m = int(n / np.pi)
# Code for circular mask not included
mask = create_circular_mask(2*r, 2*r, center=None, radius=m) 
m_area = np.mean(inplace[mask])
print(f'Full radius r={r}, radius m={m}, mean(M)={m_area:.4f}.')

绘图代码:

import matplotlib.pyplot as plt

plt.figure(figsize=(16, 8))
plt.subplot(121)
rotated = np.nan_to_num(rotated)  # not necessary in case of the first method
plt.title(
    f'Output of rotate_vector(), angle={angle}°\n'
    f'Sum is {np.sum(rotated):.2f} and should be {np.sum(v):.2f}')
plt.imshow(rotated, cmap=plt.cm.Greys_r)

plt.subplot(122)
plt.title(
    f'Output of place_vectors(), r={r}, n={n}\n'
    f'Covered {covered_frac * 100:.2f}% of the area which should be covered.\n'
    f'Mean of the circle M is {m_area:.4f} and should be 1.0.')
plt.imshow(inplace)
circle=plt.Circle((r, r), m, color='r', fill=False)
plt.gcf().gca().add_artist(circle)
plt.gcf().gca().legend([circle], [f'Circle M (m={m})'])