numpy_to_vtk

@author: Alexandre Sac–Morane alexandre.sac-morane@enpc.fr

This is the file to convert numpy array into a vtk file.

Expand source code
"""Write a legacy VTK STRUCTURED_POINTS file from a NumPy array.

Usage:
  - Import `write_vtk_structured_points` and call with a 2D or 3D NumPy array.
  - The array should be shaped (Z, Y, X) for 3D or (Y, X) for 2D.

This writes an ASCII legacy .vtk file which is readable by ParaView and other VTK tools.
"""
from __future__ import annotations
import numpy as np
from typing import Tuple


_DTYPE_MAP = {
    np.dtype('float32'): 'float',
    np.dtype('float64'): 'double',
    np.dtype('int32'): 'int',
    np.dtype('int64'): 'long',
    np.dtype('uint8'): 'unsigned_char',
    np.dtype('int8'): 'char',
}


def _map_dtype(dtype: np.dtype) -> str:
    dt = np.dtype(dtype)
    if dt in _DTYPE_MAP:
        return _DTYPE_MAP[dt]
    if np.issubdtype(dt, np.floating):
        return 'double'
    if np.issubdtype(dt, np.integer):
        return 'int'
    raise ValueError(f'Unsupported dtype: {dtype}')


def write_vtk_structured_points(
    filename: str,
    array: np.ndarray,
    spacing: Tuple[float, float, float] = (1.0, 1.0, 1.0),
    origin: Tuple[float, float, float] = (0.0, 0.0, 0.0),
    scalar_name: str = 'scalars',
    binary: bool = False,
):
    """Write `array` to a legacy ASCII VTK file as STRUCTURED_POINTS.

    Parameters
    - filename: target .vtk filename
    - array: 2D (Y,X) or 3D (Z,Y,X) NumPy array. Use shape (Z,Y,X) for stacks.
    - spacing: voxel spacing (sx, sy, sz)
    - origin: origin coordinates (ox, oy, oz)
    - scalar_name: name for the scalar field

    The data are written in ASCII with X varying fastest (C-order ravel).
    """
    if not isinstance(array, np.ndarray):
        raise TypeError('array must be a NumPy ndarray')

    if array.ndim == 2:
        # interpret as (Y, X) -> make (Z=1, Y, X)
        arr = array[np.newaxis, :, :]
    elif array.ndim == 3:
        arr = array
    else:
        raise ValueError('array must be 2D or 3D (Y,X) or (Z,Y,X)')

    zdim, ydim, xdim = arr.shape
    npoints = xdim * ydim * zdim

    # VTK expects X fastest; for arr shaped (Z,Y,X), ravel(order='C') does that
    flat = arr.ravel(order='C')

    vtk_dtype = _map_dtype(arr.dtype)

    mode = 'wb' if binary else 'w'

    header_lines = [
        '# vtk DataFile Version 3.0',
        'VTK output from numpy_to_vtk.py',
        'BINARY' if binary else 'ASCII',
        'DATASET STRUCTURED_POINTS',
        f'DIMENSIONS {xdim} {ydim} {zdim}',
        f'ORIGIN {origin[0]} {origin[1]} {origin[2]}',
        f'SPACING {spacing[0]} {spacing[1]} {spacing[2]}',
        f'POINT_DATA {npoints}',
        f'SCALARS {scalar_name} {vtk_dtype}',
        'LOOKUP_TABLE default',
    ]

    if binary:
        # For binary VTK legacy files, data must be big-endian (network order)
        # Map to numpy dtype strings with big-endian specifiers
        if arr.dtype == np.dtype('float32'):
            be_dtype = '>f4'
        elif arr.dtype == np.dtype('float64'):
            be_dtype = '>f8'
        elif arr.dtype == np.dtype('int32'):
            be_dtype = '>i4'
        elif arr.dtype == np.dtype('int64'):
            be_dtype = '>i8'
        elif arr.dtype == np.dtype('uint8'):
            be_dtype = '>u1'
        elif arr.dtype == np.dtype('int8'):
            be_dtype = '>i1'
        else:
            # Fallback: choose big-endian of same kind/size if possible
            be_dtype = '>' + str(arr.dtype)

        with open(filename, mode) as f:
            for line in header_lines:
                f.write((line + '\n').encode('utf-8'))

            # write binary blob
            data_bytes = arr.astype(be_dtype).ravel(order='C').tobytes()
            f.write(data_bytes)
    else:
        with open(filename, mode) as f:
            for line in header_lines:
                f.write(line + '\n')

            # Write data values, keep lines reasonably short
            per_line = 9
            for i in range(0, flat.size, per_line):
                chunk = flat[i : i + per_line]
                f.write(' '.join(map(str, chunk.tolist())) + '\n')


if __name__ == '__main__':
    # demo: create a small 3D ramp and write sample.vtk in current folder
    import os

    demo = np.zeros((16, 32, 48), dtype=np.float32)
    # shape (Z, Y, X)
    Z, Y, X = demo.shape
    for z in range(Z):
        for y in range(Y):
            for x in range(X):
                demo[z, y, x] = x + y * 0.5 + z * 0.2

    out_ascii = os.path.join(os.path.dirname(__file__), 'sample.vtk')
    out_bin = os.path.join(os.path.dirname(__file__), 'sample_bin.vtk')
    write_vtk_structured_points(out_ascii, demo, spacing=(1.0, 1.0, 1.0), origin=(0, 0, 0), binary=False)
    write_vtk_structured_points(out_bin, demo, spacing=(1.0, 1.0, 1.0), origin=(0, 0, 0), binary=True)
    print('Wrote demo VTK to', out_ascii)
    print('Wrote demo binary VTK to', out_bin)

def write_vtk_structured_points()

Write `array` to a legacy ASCII VTK file as STRUCTURED_POINTS.

Parameters
    - filename: target .vtk filename
    - array: 2D (Y,X) or 3D (Z,Y,X) NumPy array. Use shape (Z,Y,X) for stacks.
    - spacing: voxel spacing (sx, sy, sz)
    - origin: origin coordinates (ox, oy, oz)
    - scalar_name: name for the scalar field

    The data are written in ASCII with X varying fastest (C-order ravel).
Expand source code

def write_vtk_structured_points(
    filename: str,
    array: np.ndarray,
    spacing: Tuple[float, float, float] = (1.0, 1.0, 1.0),
    origin: Tuple[float, float, float] = (0.0, 0.0, 0.0),
    scalar_name: str = 'scalars',
    binary: bool = False,
):
    """Write `array` to a legacy ASCII VTK file as STRUCTURED_POINTS.

    Parameters
    - filename: target .vtk filename
    - array: 2D (Y,X) or 3D (Z,Y,X) NumPy array. Use shape (Z,Y,X) for stacks.
    - spacing: voxel spacing (sx, sy, sz)
    - origin: origin coordinates (ox, oy, oz)
    - scalar_name: name for the scalar field

    The data are written in ASCII with X varying fastest (C-order ravel).
    """
    if not isinstance(array, np.ndarray):
        raise TypeError('array must be a NumPy ndarray')

    if array.ndim == 2:
        # interpret as (Y, X) -> make (Z=1, Y, X)
        arr = array[np.newaxis, :, :]
    elif array.ndim == 3:
        arr = array
    else:
        raise ValueError('array must be 2D or 3D (Y,X) or (Z,Y,X)')

    zdim, ydim, xdim = arr.shape
    npoints = xdim * ydim * zdim

    # VTK expects X fastest; for arr shaped (Z,Y,X), ravel(order='C') does that
    flat = arr.ravel(order='C')

    vtk_dtype = _map_dtype(arr.dtype)

    mode = 'wb' if binary else 'w'

    header_lines = [
        '# vtk DataFile Version 3.0',
        'VTK output from numpy_to_vtk.py',
        'BINARY' if binary else 'ASCII',
        'DATASET STRUCTURED_POINTS',
        f'DIMENSIONS {xdim} {ydim} {zdim}',
        f'ORIGIN {origin[0]} {origin[1]} {origin[2]}',
        f'SPACING {spacing[0]} {spacing[1]} {spacing[2]}',
        f'POINT_DATA {npoints}',
        f'SCALARS {scalar_name} {vtk_dtype}',
        'LOOKUP_TABLE default',
    ]

    if binary:
        # For binary VTK legacy files, data must be big-endian (network order)
        # Map to numpy dtype strings with big-endian specifiers
        if arr.dtype == np.dtype('float32'):
            be_dtype = '>f4'
        elif arr.dtype == np.dtype('float64'):
            be_dtype = '>f8'
        elif arr.dtype == np.dtype('int32'):
            be_dtype = '>i4'
        elif arr.dtype == np.dtype('int64'):
            be_dtype = '>i8'
        elif arr.dtype == np.dtype('uint8'):
            be_dtype = '>u1'
        elif arr.dtype == np.dtype('int8'):
            be_dtype = '>i1'
        else:
            # Fallback: choose big-endian of same kind/size if possible
            be_dtype = '>' + str(arr.dtype)

        with open(filename, mode) as f:
            for line in header_lines:
                f.write((line + '\n').encode('utf-8'))

            # write binary blob
            data_bytes = arr.astype(be_dtype).ravel(order='C').tobytes()
            f.write(data_bytes)
    else:
        with open(filename, mode) as f:
            for line in header_lines:
                f.write(line + '\n')

            # Write data values, keep lines reasonably short
            per_line = 9
            for i in range(0, flat.size, per_line):
                chunk = flat[i : i + per_line]
                f.write(' '.join(map(str, chunk.tolist())) + '\n')