How to generate a sphere in 3D Numpy array

后端 未结 3 1202
醉梦人生
醉梦人生 2020-12-10 15:11

Given a 3D numpy array of shape (256, 256, 256), how would I make a solid sphere shape inside? The code below generates a series of increasing and decreasing circles but is

相关标签:
3条回答
  • 2020-12-10 15:53

    Here is how to create voxels space without numpy, the main idea that you calculate distance between center and voxel and if voxel in radius you will create.

    from math import sqrt    
    
    def distance_dimension(xyz0 = [], xyz1 = []):
        delta_OX = pow(xyz0[0] - xyz1[0], 2)
        delta_OY = pow(xyz0[1] - xyz1[1], 2)
        delta_OZ = pow(xyz0[2] - xyz1[2], 2)
        return sqrt(delta_OX+delta_OY+delta_OZ)    
    
    
    
    def voxels_figure(figure = 'sphere', position = [0,0,0], size = 1):
        xmin, xmax = position[0]-size,  position[0]+size
        ymin, ymax = position[1]-size,  position[1]+size
        zmin, zmax = position[2]-size,  position[2]+size
    
        voxels = []
    
        if figure == 'cube':
            for local_z, world_z in zip(range(zmax-zmin), range(zmin, zmax)):
                for local_y, world_y in zip(range(ymax-ymin), range(ymin, ymax)):
                    for local_x, world_x in zip(range(xmax-xmin), range(xmin, xmax)):
                        voxels.append([world_x,world_y,world_z])
    
        elif figure == 'sphere':
            for local_z, world_z in zip(range(zmax-zmin), range(zmin, zmax)):
                for local_y, world_y in zip(range(ymax-ymin), range(ymin, ymax)):
                    for local_x, world_x in zip(range(xmax-xmin), range(xmin, xmax)):
                        radius = distance_dimension(xyz0 = [world_x, world_y,world_z], xyz1 = position)
                        if  radius < size:
                            voxels.append([world_x,world_y,world_z])
    
        return voxels
    
    voxels = voxels_figure(figure = 'sphere', position = [0,0,0], size = 3)
    

    After you will get voxels indexes, you can apply ~ones for cube matrix.

    0 讨论(0)
  • 2020-12-10 16:04

    Nice question. My answer to a similar question would be applicable here also.

    You can try the following code. In the below mentioned code AA is the matrix that you want.

    import numpy as np
    from copy import deepcopy
    
    ''' size : size of original 3D numpy matrix A.
        radius : radius of circle inside A which will be filled with ones. 
    '''
    size, radius = 5, 2
    
    ''' A : numpy.ndarray of shape size*size*size. '''
    A = np.zeros((size,size, size)) 
    
    ''' AA : copy of A (you don't want the original copy of A to be overwritten.) '''
    AA = deepcopy(A) 
    
    ''' (x0, y0, z0) : coordinates of center of circle inside A. '''
    x0, y0, z0 = int(np.floor(A.shape[0]/2)), \
            int(np.floor(A.shape[1]/2)), int(np.floor(A.shape[2]/2))
    
    
    for x in range(x0-radius, x0+radius+1):
        for y in range(y0-radius, y0+radius+1):
            for z in range(z0-radius, z0+radius+1):
                ''' deb: measures how far a coordinate in A is far from the center. 
                        deb>=0: inside the sphere.
                        deb<0: outside the sphere.'''   
                deb = radius - abs(x0-x) - abs(y0-y) - abs(z0-z) 
                if (deb)>=0: AA[x,y,z] = 1
    

    Following is an example of the output for size=5 and radius=2 (a sphere of radius 2 pixels inside a numpy array of shape 5*5*5):

    [[[0. 0. 0. 0. 0.]
      [0. 0. 0. 0. 0.]
      [0. 0. 1. 0. 0.]
      [0. 0. 0. 0. 0.]
      [0. 0. 0. 0. 0.]]
    
     [[0. 0. 0. 0. 0.]
      [0. 0. 1. 0. 0.]
      [0. 1. 1. 1. 0.]
      [0. 0. 1. 0. 0.]
      [0. 0. 0. 0. 0.]]
    
     [[0. 0. 1. 0. 0.]
      [0. 1. 1. 1. 0.]
      [1. 1. 1. 1. 1.]
      [0. 1. 1. 1. 0.]
      [0. 0. 1. 0. 0.]]
    
     [[0. 0. 0. 0. 0.]
      [0. 0. 1. 0. 0.]
      [0. 1. 1. 1. 0.]
      [0. 0. 1. 0. 0.]
      [0. 0. 0. 0. 0.]]
    
     [[0. 0. 0. 0. 0.]
      [0. 0. 0. 0. 0.]
      [0. 0. 1. 0. 0.]
      [0. 0. 0. 0. 0.]
      [0. 0. 0. 0. 0.]]]
    

    I haven't printed the output for the size and radius that you had asked for (size=32 and radius=4), as the output will be very long.

    0 讨论(0)
  • 2020-12-10 16:08

    EDIT: pymrt.geometry has been removed in favor of raster_geometry.


    DISCLAIMER: I am the author of both pymrt and raster_geometry.

    If you just need to have the sphere, you can use the pip-installable module pymrt, and particularly pymrt.geometry.sphere(), e.g:

    import pymrt as mrt
    import pymrt.geometry
    
    arr = mrt.geometry.sphere(3, 1)
    
    array([[[False, False, False],
            [False,  True, False],
            [False, False, False]],
    
            [[False,  True, False],
            [ True,  True,  True],
            [False,  True, False]],
    
            [[False, False, False],
            [False,  True, False],
            [False, False, False]]], dtype=bool)
    

    internally, this is implemented as an n-dimensional superellipsoid generator, you can check its source code for details. Briefly, the (simplified) code would reads like this:

    import numpy as np
    
    
    def sphere(shape, radius, position):
        # assume shape and position are both a 3-tuple of int or float
        # the units are pixels / voxels (px for short)
        # radius is a int or float in px
        semisizes = (radius,) * 3
    
        # genereate the grid for the support points
        # centered at the position indicated by position
        grid = [slice(-x0, dim - x0) for x0, dim in zip(position, shape)]
        position = np.ogrid[grid]
        # calculate the distance of all points from `position` center
        # scaled by the radius
        arr = np.zeros(shape, dtype=float)
        for x_i, semisize in zip(position, semisizes):
            # this can be generalized for exponent != 2
            # in which case `(x_i / semisize)`
            # would become `np.abs(x_i / semisize)`
            arr += (x_i / semisize) ** 2
    
        # the inner part of the sphere will have distance below 1
        return arr <= 1.0
    

    and testing it:

    arr = sphere((256, 256, 256), 10, (127, 127, 127))
    # this will save a sphere in a boolean array
    # the shape of the containing array is: (256, 256, 256)
    # the position of the center is: (127, 127, 127)
    # if you want is 0 and 1 just use .astype(int)
    # for plotting it is likely that you want that
    
    # just for fun you can check that the volume is matching what expected
    np.sum(arr)
    # gives: 4169
    
    4 / 3 * np.pi * 10 ** 3
    # gives: 4188.790204786391
    # (the two numbers do not match exactly because of the discretization error)
    

    I am failing to get how your code exactly works, but to check that this is actually producing spheres (using your numbers) you could try:

    import pymrt as mrt
    import pymrt.geometry
    
    arr = mrt.geometry.sphere(256, 10, 0.5)
    
    
    # plot in 3D
    import matplotlib.pyplot as plt
    from skimage import measure
    
    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1, projection='3d')
    
    verts, faces, normals, values = measure.marching_cubes(arr, 0.5, (2,) * 3)
    ax.plot_trisurf(
        verts[:, 0], verts[:, 1], faces, verts[:, 2], cmap='Spectral',
        antialiased=False, linewidth=0.0)
    plt.show()
    
    0 讨论(0)
提交回复
热议问题