pytuflow.NCMesh.mesh_dataset

pytuflow.NCMesh.mesh_dataset#

NCMesh.mesh_dataset(mesh_geometry='', time=-1, datasets=(), location_ref='local', reindex=False)#

Returns the mesh dataset as a pyvista.PolyData instance.

Warning

The method is only available when using pyvista mesh geometry drivers. This is the driver that PyTUFLOW uses in a Python environment. It will not be available if using PyTUFLOW in a QGIS environment.

Parameters:
  • mesh_geometry (str, optional) – The data type to use for the mesh geometry, e.g. "water level". If not provided, the base mesh geometry will be used e.g. this will be the "Bed Elevation" for XMDF results.

  • time (float | datetime, optional) – The time to export the data for. This is required if using a temporal dataset or mesh_geometry.

  • datasets (list, optional) – Additional datasets to add to the mesh. These do not change the mesh geometry, however can be used when plotting to assign a colour ramp.

  • location_ref (Mesh | str, optional) –

    The location reference to use. The options are:

    • "local" - Default. Use the mesh local coordinate system where the X,Y origin (0,0) is at the centre of the dataset.

    • "global" - Use the global coordinate system i.e. converts back to the original spatially referenced results.

    • Mesh - Another mesh object. The returned pyvista.PolyData object will be converted to the local coordinate system of the provided Mesh. This allows mesh objects to be aligned without having to use a global coordinate system.

  • reindex (bool, optional) – Whether to reindex the pyvista.PolyData object before returning it. Reindexing in this case is removing inactive cells.

Returns:

The mesh dataset.

Return type:

pyvista.PolyData

Examples

Plot the bed elevation and max water level from an XMDF results.

>>> import pytuflow
>>> import pyvista as pv
>>> res = pytuflow.XMDF('examples/datasets/xmdf/EG15_001.xmdf')
>>> mesh = res.mesh_dataset()
>>> wl_mesh = res.mesh_dataset('max water level', reindex=True) # reindex removes inactive cells
>>> pl = pv.Plotter() # init the plotter
>>> _ = pl.add_mesh(mesh, scalars='bed level', cmap='Spectral_r', smooth_shading=True)
>>> _ = pl.add_mesh(
...         wl_mesh,
...         scalars='max water level',
...         cmap='Blues',
...         smooth_shading=True,
...         opacity=0.75,
...         show_scalar_bar=False
...     )
>>> pl.set_scale(zscale=5) # exagerate the z scale
>>> pl.enable_terrain_style() # has no effect on interaction window below, but will work in other contexts
>>> pl.show()
../../_images/pytuflow-NCMesh-mesh_dataset-00d3c703d483e637_00_00.png

Left-click = rotate, Ctrl+left = rotate locked to camera, Shift+left = pan

Animate water level through time using a time slider.

import numpy as np
import pytuflow
import pyvista as pv

res = pytuflow.XMDF('/path/to/result.xmdf')
times = res.times()

# static bed-level mesh used as the base surface
bed_mesh = res.mesh_dataset()

# water-level mesh initialised at the first time step
wl_mesh = res.mesh_dataset('water level', times[0])

pl = pv.Plotter()
pl.set_scale(zscale=5)
_ = pl.add_mesh(bed_mesh, scalars='bed level', cmap='Spectral_r', smooth_shading=True)
_ = pl.add_mesh(wl_mesh, scalars='water level', cmap='Blues', smooth_shading=True, opacity=0.75)

def update_time(time_val):
    # snap slider value to the nearest available time step
    idx = int(np.argmin(np.abs(np.array(times) - time_val)))

    # don't need to re-copy mesh, extracting the surface is enough
    surf = res.surface('water level', times[idx])

    # update geometry and scalars in-place to avoid re-adding the actor
    wl_mesh.points[:,2] = surf['value']
    wl_mesh.point_data['water level'][:] = surf['value']

slider = pl.add_slider_widget(update_time, [times[0], times[-1]], value=times[0], title='Time')

# the default callback behaviour is only when the slider is released
# if you want to add a callback each time the slider is changed, add the following line
slider.AddObserver('InteractionEvent', lambda w, e: update_time(w.GetRepresentation().GetValue()))
pl.enable_terrain_style()
pl.show()

Example of using a time slider to dynamically update the plot

Render a movie. This example also shows how to add vectors to the map.

This example will require installing imageio[tifffile] and imageio-ffmpeg:

pip install imageio[tifffile] imageio-ffmpeg

import pytuflow
import pyvista as pv
from pathlib import Path


SCALE = 5.
DATASETS = ['water level', 'velocity', 'vector velocity']

res = pytuflow.XMDF('/path/to/result.xmdf')
times = res.times()

# static bed-level mesh used as the base surface
bed_mesh = res.mesh_dataset()

# result datasets - these can all be collected together
# as they can share the water level mesh geometry
res_mesh = res.mesh_dataset('water level', times[0], datasets=DATASETS)

# retrieve the min/max velocity for the colour bar
vel_min = 0.
vel_max = res.maximum('velocity')

# setup the arrow geometry
# scale the arrow geometry to counter
# the plot scaling that is applied later
arrow_geom = pv.Arrow().scale([1., 1., 1 / SCALE])
arrows = res_mesh.glyph(
    orient='vector velocity',
    scale='velocity',
    geom=arrow_geom,
    factor=5
)

# setup the plotter
pl = pv.Plotter(off_screen=True)
pl.set_scale(zscale=5)

# add the meshes to the plotter
pl.add_mesh(
    bed_mesh,
    scalars='bed level',
    cmap='Spectral_r',
    smooth_shading=True,
    show_scalar_bar=False
)
pl.add_mesh(
    res_mesh,
    scalars='water level',
    cmap='Blues',
    smooth_shading=True,
    opacity=0.75,
    show_scalar_bar=False
)
pl.add_mesh(
    arrows,
    cmap='coolwarm',
    clim=(vel_min, vel_max)
)

# this will need to be customised for your model
# the best way is to use an interactive plot first and find a good spot
# and then retrieve (and copy) the position in python with pl.camera_position
pl.camera_position = [
    (564.2798, 488.9518, 583.4595),
    (-33.3634, 91.8160, 175.7947),
    (-0.3921, -0.3020, 0.8690)
]

# open the movie file and loop through all the timesteps
pl.open_movie('/path/to/movie.mp4', framerate=10)
for time_ in times:
    updated_mesh = res.mesh_dataset('water level', time_, datasets=DATASETS)

    res_mesh.points[:, 2] = updated_mesh.points[:,2]
    res_mesh.point_data['water level'][:] = res_mesh.points[:, 2]

    new_arrows = updated_mesh.glyph(
        orient='vector velocity',
        scale='velocity',
        geom=arrow_geom,
        factor=5
    )
    arrows.copy_from(new_arrows)

    pl.write_frame()
    print(f'Rendered frame {time_}')

pl.close()

Output of the rendered movie with velocity vectors

The above example requires the camera position, the best way to obtain this is to set a keyboard shortcut to copy the camera position to the clipboard. This way the camera can be positioned in the plotter, then the position can be obtained and pasted into the animation script.

import pytuflow
import pyvista as pv
import tkinter as tk

def copy_camera_to_clipboard():
    formatted_code = f"camera_position = {pl.camera_position}"

    # Push the string into the system clipboard
    root = tk.Tk()
    root.withdraw()  # Hide the main window
    root.clipboard_clear()
    root.clipboard_append(formatted_code)
    root.update()  # Keep it in memory after closing
    root.destroy()

res = pytuflow.XMDF('/path/to/result.xmdf')
mesh = res.mesh_dataset()
wl_mesh = res.mesh_dataset('max water level', reindex=True) # reindex removes inactive cells
pl = pv.Plotter() # init the plotter
_ = pl.add_mesh(mesh, scalars='bed level', cmap='Spectral_r', smooth_shading=True)
_ = pl.add_mesh(
        wl_mesh,
        scalars='max water level',
        cmap='Blues',
        smooth_shading=True,
        opacity=0.75,
        show_scalar_bar=False
    )
pl.set_scale(zscale=5) # exagerate the z scale
pl.enable_terrain_style()

# setup the keyboard shortcut so that "c" copies the camera position
pl.add_key_event("c", copy_camera_to_clipboard)

pl.show()