pnm 1.0.3
peclet.pnm — pore-network extraction from SDF geometry (Kokkos)
Loading...
Searching...
No Matches
vti.py
Go to the documentation of this file.
1"""
2VTI (VTK ImageData) file I/O utilities.
3
4Provides functions to save field data in VTK ImageData format for visualization
5with ParaView and other VTK-compatible tools.
6"""
7
8import numpy as np
9
10def save_vti(filename, fields, spacing=(1.0, 1.0, 1.0), origin=(0.0, 0.0, 0.0)):
11 """
12 General VTI saver that infers grid dimensions from the input fields.
13
14 Parameters
15 ----------
16 fields : dict { "Name": ndarray }
17 Arrays should be shape (Nz, Ny, Nx) for scalars
18 or (Nz, Ny, Nx, 3) for vectors.
19 spacing : tuple (dz, dy, dx)
20 origin : tuple (oz, oy, ox)
21 """
22 # 1. Infer shape from the first field
23 first_field = next(iter(fields.values()))
24 shape_3d = first_field.shape[:3] # Get (Nz, Ny, Nx)
25 nz, ny, nx = shape_3d
26
27 # 2. Map to VTK Point-Extents (0 to N points for N cells)
28 # VTK XML order is ALWAYS X, Y, Z
29 ex, ey, ez = nx, ny, nz
30 dz, dy, dx = spacing
31 oz, oy, ox = origin
32
33 with open(filename, 'wb') as f:
34 # XML Boilerplate
35 f.write(b'<?xml version="1.0"?>\n')
36 f.write(b'<VTKFile type="ImageData" version="1.0" byte_order="LittleEndian" header_type="UInt64">\n')
37 f.write(f' <ImageData WholeExtent="0 {ex} 0 {ey} 0 {ez}" Origin="{ox} {oy} {oz}" Spacing="{dx} {dy} {dz}">\n'.encode('ascii'))
38 f.write(f' <Piece Extent="0 {ex} 0 {ey} 0 {ez}">\n'.encode('ascii'))
39 f.write(b' <CellData>\n')
40
41 # 3. Write Metadata and calculate offsets
42 offset = 0
43 data_to_write = []
44
45 for name, arr in fields.items():
46 if arr.shape[:3] != shape_3d:
47 raise ValueError(f"Field '{name}' shape {arr.shape} inconsistent with {shape_3d}")
48
49 # Determine Types
50 if np.issubdtype(arr.dtype, np.integer):
51 dtype_str = "Int32"
52 arr_fixed = arr.astype('<i4')
53 elif arr.dtype == np.float64:
54 dtype_str = "Float64"
55 arr_fixed = arr.astype('<f8')
56 else:
57 dtype_str = "Float32"
58 arr_fixed = arr.astype('<f4')
59
60 n_comp = 3 if arr.ndim == 4 else 1
61 n_bytes = arr_fixed.nbytes
62
63 f.write(f' <DataArray type="{dtype_str}" Name="{name}" NumberOfComponents="{n_comp}" format="appended" offset="{offset}"/>\n'.encode('ascii'))
64
65 data_to_write.append(arr_fixed)
66 offset += n_bytes + 8 # Data size + 8-byte length header
67
68 f.write(b' </CellData>\n </Piece>\n </ImageData>\n')
69
70 # 4. Appended Binary Section
71 f.write(b' <AppendedData encoding="raw">\n_')
72
73 for arr in data_to_write:
74 # Because arrays are (Nz, Ny, Nx), ravel() is naturally X-fastest.
75 # For vectors (Nz, Ny, Nx, 3), ravel() produces [u0,v0,w0, u1,v1,w1...]
76 flat_bytes = arr.tobytes()
77 f.write(np.array([len(flat_bytes)], dtype='<u8').tobytes())
78 f.write(flat_bytes)
79
80 f.write(b'\n </AppendedData>\n</VTKFile>\n')