Search code examples
pythonmathnumpyvolumediscrete-mathematics

Good algorithm for computing volume or surface area in python


I am trying to compute the volume (or surface area) of a 3D numpy array. The voxels are anisotropic in a lot of cases, and I have the pixel to cm conversion factor in each direction.

Does anyone know a good place to find a toolkit or package to do the above??

Right now, I have some in-house code, but I am looking to upgrade to something more industrial strength in terms of accuracy.

Edit1: Here is some (poor) sample data. This is much smaller than the typical sphere. I will add better data when I can generate it! It is in (self.)tumorBrain.tumor.


Solution

  • One option is to use VTK. (I'm going to use the tvtk python bindings for it here...)

    At least in some circumstances, getting the area within the isosurface will be a bit more accurate.

    Also, as far as surface area goes, tvtk.MassProperties calculates surface area as well. It's mass.surface_area (with the mass object in the code below).

    import numpy as np
    from tvtk.api import tvtk
    
    def main():
        # Generate some data with anisotropic cells...
        # x,y,and z will range from -2 to 2, but with a 
        # different (20, 15, and 5 for x, y, and z) number of steps
        x,y,z = np.mgrid[-2:2:20j, -2:2:15j, -2:2:5j]
        r = np.sqrt(x**2 + y**2 + z**2)
    
        dx, dy, dz = [np.diff(it, axis=a)[0,0,0] for it, a in zip((x,y,z),(0,1,2))]
    
        # Your actual data is a binary (logical) array
        max_radius = 1.5
        data = (r <= max_radius).astype(np.int8)
    
        ideal_volume = 4.0 / 3 * max_radius**3 * np.pi
        coarse_volume = data.sum() * dx * dy * dz
        est_volume = vtk_volume(data, (dx, dy, dz), (x.min(), y.min(), z.min()))
    
        coarse_error = 100 * (coarse_volume - ideal_volume) / ideal_volume
        vtk_error = 100 * (est_volume - ideal_volume) / ideal_volume
    
        print 'Ideal volume', ideal_volume
        print 'Coarse approximation', coarse_volume, 'Error', coarse_error, '%'
        print 'VTK approximation', est_volume, 'Error', vtk_error, '%'
    
    def vtk_volume(data, spacing=(1,1,1), origin=(0,0,0)):
        data[data == 0] = -1
        grid = tvtk.ImageData(spacing=spacing, origin=origin)
        grid.point_data.scalars = data.T.ravel() # It wants fortran order???
        grid.point_data.scalars.name = 'scalars'
        grid.dimensions = data.shape
    
        iso = tvtk.ImageMarchingCubes(input=grid)
        mass = tvtk.MassProperties(input=iso.output)
        return mass.volume
    
    main()
    

    This yields:

    Ideal volume 14.1371669412
    Coarse approximation 14.7969924812 Error 4.66731094565 %
    VTK approximation 14.1954890878 Error 0.412544796894 %