| name | ttk-viz |
| description | Headless topological data analysis and visualization with the Topology ToolKit (TTK) on top of ParaView. Use this skill when Claude needs to: (1) Compute and visualize persistence diagrams, contour trees, merge trees, and Morse-Smale segmentations, (2) Extract and render critical points of scalar/vector fields, (3) Apply persistence-based topological simplification to denoise scalar fields, (4) Run TTK filters headlessly via pvpython (loading TTK plugins and using the Scalars_ naming convention).
|
TTK Headless Topology Visualization
Execute all TTK tasks via self-contained Python scripts run with pvpython. Use batch/headless mode — never open a GUI.
TTK extends ParaView with topological analysis filters. Always import from paraview.simple import * and ensure TTK plugins are loaded before using TTK filters.
Rules
- Never open a GUI — always use
pvpython for headless batch execution
- Use
from paraview.simple import * at the top of every script
- Load TTK plugins explicitly before using TTK filters (see Canonical Template)
- TTK filters expect the scalar array to be named
Scalars_ — rename on load if needed
- Always call
UpdatePipeline() after filters before accessing data information
- After taking a screenshot, use the Read tool to view the image and verify correctness
- For visual matching tasks, iterate: screenshot → assess → adjust → re-screenshot (max 5 rounds)
- Use
pvpython (not python) to run scripts
Canonical Script Template
from paraview.simple import *
LoadPlugin("libTopologyToolKit.so", remote=False, ns=globals())
reader = XMLImageDataReader(FileName=['/path/to/data.vti'])
reader.PointArrayStatus = ['array_name']
reader.UpdatePipeline()
rename = RenameArrays(Input=reader)
rename.PointArrays = ['array_name', 'Scalars_']
rename.UpdatePipeline()
renderView = CreateView('RenderView')
renderView.ViewSize = [1920, 1080]
renderView.Background = [0.1, 0.1, 0.15]
layout = CreateLayout(name='Layout')
layout.AssignView(0, renderView)
display = Show(rename, renderView)
ResetCamera(renderView)
SaveScreenshot('/path/to/output.png', renderView,
ImageResolution=[1920, 1080],
OverrideColorPalette='WhiteBackground')
Loading Data
reader = XMLImageDataReader(FileName=['/path/to/field.vti'])
reader.PointArrayStatus = ['Pressure']
reader.TimeArray = 'None'
reader.UpdatePipeline()
rename = RenameArrays(Input=reader)
rename.PointArrays = ['Pressure', 'Scalars_']
rename.UpdatePipeline()
rename = RenameArrays(Input=reader)
rename.PointArrays = ['velocity_x', 'u', 'velocity_y', 'v']
reader = XMLPartitionedUnstructuredGridReader(FileName=['/path/to/data.pvtu'])
reader = PVDReader(FileName='/path/to/timeseries.pvd')
Get Data Range
reader.UpdatePipeline()
pd = reader.PointData
min_val, max_val = pd.GetArray('Scalars_').GetRange()
bounds = reader.GetDataInformation().GetBounds()
center = [(bounds[0]+bounds[1])/2, (bounds[2]+bounds[3])/2, (bounds[4]+bounds[5])/2]
TTK Filters
Persistence Diagram
pd_filter = TTKPersistenceDiagram(Input=rename)
pd_filter.ScalarField = ['POINTS', 'Scalars_']
pd_filter.InputOffsetField = ['POINTS', 'Scalars_']
pd_filter.Backend = 'FTM (IEEE TPSD 2019)'
pd_filter.UpdatePipeline()
pd_data = pd_filter.PointData
spheres = TTKIcospheresFromPoints(Input=pd_filter)
spheres.Radius = 0.02
sphereDisplay = Show(spheres, renderView)
ColorBy(sphereDisplay, ('POINTS', 'Persistence'))
sphereDisplay.RescaleTransferFunctionToDataRange(True)
surf = ExtractSurface(Input=pd_filter)
tube = Tube(Input=surf)
tube.Radius = 0.01
Show(tube, renderView)
Contour Tree
ct = TTKContourTree(Input=rename)
ct.ScalarField = ['POINTS', 'Scalars_']
ct.InputOffsetField = ['POINTS', 'Scalars_']
ct.UpdatePipeline()
ct_nodes = OutputPort(ct, 0)
ct_edges = OutputPort(ct, 1)
spheres = TTKIcospheresFromPoints(Input=ct_nodes)
spheres.Radius = 0.5
sphereDisplay = Show(spheres, renderView)
ColorBy(sphereDisplay, ('POINTS', 'CriticalType'))
surf = ExtractSurface(Input=ct_edges)
tube = Tube(Input=surf)
tube.Radius = 0.1
Show(tube, renderView)
lut = GetColorTransferFunction('CriticalType')
lut.RGBPoints = [
0, 0.0, 0.188, 1.0,
1, 0.459, 0.561, 1.0,
2, 1.0, 0.871, 0.906,
3, 1.0, 0.0, 0.275,
4, 0.0, 0.0, 0.0,
]
lut.ColorSpace = 'RGB'
lut.ScalarRangeInitialized = 1.0
sphereDisplay.LookupTable = lut
Merge Tree (Join or Split)
join = TTKMergeTree(Input=rename)
join.ScalarField = ['POINTS', 'Scalars_']
join.InputOffsetField = ['POINTS', 'Scalars_']
split = TTKMergeTree(Input=rename)
split.ScalarField = ['POINTS', 'Scalars_']
split.InputOffsetField = ['POINTS', 'Scalars_']
split.TreeType = 'Split Tree'
Scalar Field Critical Points
cp = TTKScalarFieldCriticalPoints(Input=rename)
cp.ScalarField = ['POINTS', 'Scalars_']
cp.InputOffsetField = ['POINTS', 'Scalars_']
cp.UpdatePipeline()
spheres = TTKIcospheresFromPoints(Input=cp)
spheres.Radius = 0.3
cpDisplay = Show(spheres, renderView)
ColorBy(cpDisplay, ('POINTS', 'CriticalType'))
thresh = Threshold(Input=cp)
thresh.Scalars = ['POINTS', 'CriticalType']
thresh.ThresholdRange = [0, 0]
minSpheres = TTKIcospheresFromPoints(Input=thresh)
minSpheres.Radius = 0.3
Show(minSpheres, renderView)
Topological Simplification by Persistence
simplified = TTKTopologicalSimplificationByPersistence(Input=rename)
simplified.InputArray = ['POINTS', 'Scalars_']
simplified.PersistenceThreshold = 0.1
simplified.UpdatePipeline()
ct = TTKContourTree(Input=simplified)
ct.ScalarField = ['POINTS', 'Scalars_']
Morse-Smale Segmentation
msc = TTKPathCompression(Input=rename)
msc.ScalarField = ['POINTS', 'Scalars_']
msc.OffsetField = ['POINTS', 'Scalars_']
msc.UpdatePipeline()
display = Show(msc, renderView, 'UniformGridRepresentation')
display.Representation = 'Slice'
ColorBy(display, ('POINTS', 'Scalars__MorseSmaleManifold'))
display.RescaleTransferFunctionToDataRange(True)
TTK Icospheres from Points
spheres = TTKIcospheresFromPoints(Input=point_data)
spheres.Radius = 0.5
Render View & Camera
renderView = CreateView('RenderView')
renderView.ViewSize = [1920, 1080]
renderView.Background = [0.1, 0.1, 0.15]
layout = CreateLayout(name='Layout')
layout.AssignView(0, renderView)
ResetCamera(renderView)
renderView.CameraPosition = [cx + d, cy, cz]
renderView.CameraFocalPoint = [cx, cy, cz]
renderView.CameraViewUp = [0, 0, 1]
SaveScreenshot('/path/to/output.png', renderView,
ImageResolution=[1920, 1080],
OverrideColorPalette='WhiteBackground')
Color Transfer Functions
lut = GetColorTransferFunction('Scalars_')
lut.ApplyPreset('Cool to Warm', True)
lut.RGBPoints = [min_val, 0.0, 0.0, 1.0,
(min_val+max_val)/2, 1.0, 1.0, 1.0,
max_val, 1.0, 0.0, 0.0]
lut.ScalarRangeInitialized = 1.0
lut = GetColorTransferFunction('CriticalType')
lut.RGBPoints = [
0, 0.0, 0.188, 1.0,
1, 0.459, 0.561, 1.0,
2, 1.0, 0.871, 0.906,
3, 1.0, 0.0, 0.275,
4, 0.0, 0.0, 0.0,
]
lut.ColorSpace = 'RGB'
lut.ScalarRangeInitialized = 1.0
lut = GetColorTransferFunction('CriticalType')
lut.RGBPoints = [
0, 0.616, 0.173, 0.0,
1, 0.941, 0.773, 0.443,
2, 0.043, 0.506, 0.635,
3, 0.886, 0.341, 0.349,
4, 0.494, 0.278, 0.580,
5, 0.349, 0.659, 0.612,
]
lut.ColorSpace = 'RGB'
lut.ScalarRangeInitialized = 1.0
colorBar = GetScalarBar(lut, renderView)
colorBar.Title = 'CriticalType'
colorBar.Visibility = 1
Outline & Background
outline = Outline(Input=rename)
outlineDisplay = Show(outline, renderView)
outlineDisplay.DiffuseColor = [0.5, 0.5, 0.5]
tube = Tube(Input=outline)
tube.Radius = max_dimension * 0.0025
Show(tube, renderView)
Common Workflow Patterns
Persistence Diagram Visualization
from paraview.simple import *
LoadPlugin("libTopologyToolKit.so", remote=False, ns=globals())
reader = XMLImageDataReader(FileName=['/path/to/field.vti'])
reader.PointArrayStatus = ['density']
reader.UpdatePipeline()
rename = RenameArrays(Input=reader)
rename.PointArrays = ['density', 'Scalars_']
rename.UpdatePipeline()
min_val, max_val = rename.PointData.GetArray('Scalars_').GetRange()
data_range = max_val - min_val
pd_filter = TTKPersistenceDiagram(Input=rename)
pd_filter.ScalarField = ['POINTS', 'Scalars_']
pd_filter.InputOffsetField = ['POINTS', 'Scalars_']
pd_filter.UpdatePipeline()
renderView = CreateView('RenderView')
renderView.ViewSize = [1920, 1080]
layout = CreateLayout(name='Layout')
layout.AssignView(0, renderView)
spheres = TTKIcospheresFromPoints(Input=pd_filter)
spheres.Radius = data_range * 0.02
sDisp = Show(spheres, renderView)
ColorBy(sDisp, ('POINTS', 'Persistence'))
sDisp.RescaleTransferFunctionToDataRange(True)
surf = ExtractSurface(Input=pd_filter)
tube = Tube(Input=surf)
tube.Radius = data_range * 0.01
Show(tube, renderView)
ResetCamera(renderView)
SaveScreenshot('/path/to/pd.png', renderView, ImageResolution=[1920, 1080])
Contour Tree with Simplified Input
from paraview.simple import *
LoadPlugin("libTopologyToolKit.so", remote=False, ns=globals())
reader = XMLImageDataReader(FileName=['/path/to/field.vti'])
reader.PointArrayStatus = ['pressure']
rename = RenameArrays(Input=reader)
rename.PointArrays = ['pressure', 'Scalars_']
rename.UpdatePipeline()
min_val, max_val = rename.PointData.GetArray('Scalars_').GetRange()
data_range = max_val - min_val
simplified = TTKTopologicalSimplificationByPersistence(Input=rename)
simplified.InputArray = ['POINTS', 'Scalars_']
simplified.PersistenceThreshold = 0.05 * data_range
ct = TTKContourTree(Input=simplified)
ct.ScalarField = ['POINTS', 'Scalars_']
ct.InputOffsetField = ['POINTS', 'Scalars_']
ct.UpdatePipeline()
renderView = CreateView('RenderView')
renderView.ViewSize = [1920, 1080]
layout = CreateLayout(name='Layout')
layout.AssignView(0, renderView)
fieldDisplay = Show(rename, renderView, 'UniformGridRepresentation')
fieldDisplay.Representation = 'Slice'
ColorBy(fieldDisplay, ('POINTS', 'Scalars_'))
lut = GetColorTransferFunction('Scalars_')
lut.ApplyPreset('Cool to Warm', True)
fieldDisplay.RescaleTransferFunctionToDataRange(True)
fieldDisplay.Opacity = 0.4
spheres = TTKIcospheresFromPoints(Input=OutputPort(ct, 0))
spheres.Radius = 0.01 * data_range
ctNodeDisp = Show(spheres, renderView)
ctLut = GetColorTransferFunction('CriticalType')
ctLut.RGBPoints = [
0, 0.0, 0.188, 1.0,
1, 0.459, 0.561, 1.0,
2, 1.0, 0.871, 0.906,
3, 1.0, 0.0, 0.275,
4, 0.0, 0.0, 0.0,
]
ctLut.ScalarRangeInitialized = 1.0
ctNodeDisp.LookupTable = ctLut
ctNodeDisp.ColorArrayName = ['POINTS', 'CriticalType']
surf = ExtractSurface(Input=OutputPort(ct, 1))
tube = Tube(Input=surf)
tube.Radius = 0.005 * data_range
Show(tube, renderView)
ResetCamera(renderView)
SaveScreenshot('/path/to/ct.png', renderView, ImageResolution=[1920, 1080])
Critical Points Overlay on Scalar Field
from paraview.simple import *
LoadPlugin("libTopologyToolKit.so", remote=False, ns=globals())
reader = XMLImageDataReader(FileName=['/path/to/field.vti'])
reader.PointArrayStatus = ['temperature']
rename = RenameArrays(Input=reader)
rename.PointArrays = ['temperature', 'Scalars_']
rename.UpdatePipeline()
bounds = rename.GetDataInformation().GetBounds()
max_dim = max(bounds[1]-bounds[0], bounds[3]-bounds[2], bounds[5]-bounds[4])
renderView = CreateView('RenderView')
renderView.ViewSize = [1920, 1080]
layout = CreateLayout(name='Layout')
layout.AssignView(0, renderView)
fieldDisp = Show(rename, renderView, 'UniformGridRepresentation')
fieldDisp.Representation = 'Slice'
ColorBy(fieldDisp, ('POINTS', 'Scalars_'))
GetColorTransferFunction('Scalars_').ApplyPreset('Viridis (matplotlib)', True)
fieldDisp.RescaleTransferFunctionToDataRange(True)
cp = TTKScalarFieldCriticalPoints(Input=rename)
cp.ScalarField = ['POINTS', 'Scalars_']
cp.InputOffsetField = ['POINTS', 'Scalars_']
cp.UpdatePipeline()
spheres = TTKIcospheresFromPoints(Input=cp)
spheres.Radius = max_dim * 0.01
cpDisp = Show(spheres, renderView)
lut = GetColorTransferFunction('CriticalType')
lut.RGBPoints = [
0, 0.0, 0.188, 1.0,
1, 0.459, 0.561, 1.0,
2, 1.0, 0.871, 0.906,
3, 1.0, 0.0, 0.275,
4, 0.0, 0.0, 0.0,
]
lut.ScalarRangeInitialized = 1.0
cpDisp.LookupTable = lut
cpDisp.ColorArrayName = ['POINTS', 'CriticalType']
ResetCamera(renderView)
SaveScreenshot('/path/to/cp.png', renderView, ImageResolution=[1920, 1080])
Morse-Smale Segmentation
from paraview.simple import *
LoadPlugin("libTopologyToolKit.so", remote=False, ns=globals())
reader = XMLImageDataReader(FileName=['/path/to/field.vti'])
reader.PointArrayStatus = ['elevation']
rename = RenameArrays(Input=reader)
rename.PointArrays = ['elevation', 'Scalars_']
rename.UpdatePipeline()
msc = TTKPathCompression(Input=rename)
msc.ScalarField = ['POINTS', 'Scalars_']
msc.OffsetField = ['POINTS', 'Scalars_']
msc.UpdatePipeline()
renderView = CreateView('RenderView')
renderView.ViewSize = [1920, 1080]
layout = CreateLayout(name='Layout')
layout.AssignView(0, renderView)
display = Show(msc, renderView, 'UniformGridRepresentation')
display.Representation = 'Slice'
ColorBy(display, ('POINTS', 'Scalars__MorseSmaleManifold'))
display.RescaleTransferFunctionToDataRange(True)
ResetCamera(renderView)
SaveScreenshot('/path/to/msc.png', renderView, ImageResolution=[1920, 1080])
Export
SaveData('/path/to/tree_nodes.vtk', proxy=OutputPort(ct, 0))
SaveData('/path/to/tree_edges.vtk', proxy=OutputPort(ct, 1))
SaveData('/path/to/critical_points.vtk', proxy=cp)
SaveData('/path/to/msc.vti', proxy=msc)
SaveData('/path/to/pd.vtk', proxy=pd_filter)
Debugging & Error Handling
| Problem | Solution |
|---|
TTKPersistenceDiagram not found | Call LoadPlugin("libTopologyToolKit.so", ...) before any TTK filter |
| TTK filter returns empty output | Check UpdatePipeline() was called; verify ScalarField array name is Scalars_ |
| Array not found in filter | Use RenameArrays to rename input array to Scalars_ before passing to TTK |
| Empty spheres / no geometry | TTKIcospheresFromPoints needs non-empty point input — check upstream filter output |
| Blank screenshot | Call ResetCamera(renderView) before SaveScreenshot |
| Wrong output port | Use OutputPort(filter, 0) for nodes, OutputPort(filter, 1) for edges (contour/merge tree) |
pvpython not found | Add $PARAVIEW_HOME/bin to PATH or use full path |
| Plugin already loaded warning | Safe to ignore; TTK is loaded |
Key Array Name Reference
| Source | Array Name | Values |
|---|
After RenameArrays | Scalars_ | Raw scalar values |
TTKScalarFieldCriticalPoints | CriticalType | 0=min, 1=1-saddle, 2=2-saddle, 3=max, 4=degenerate |
TTKContourTree (nodes port) | CriticalType, Scalar, NodeId | See above |
TTKPersistenceDiagram | Persistence, Birth, CriticalType, PairType | — |
TTKPathCompression | Scalars__MorseSmaleManifold | Region label integers |
Task Execution
When given $ARGUMENTS:
- Parse the task from the arguments
- Write a self-contained Python script following the template above
- Execute it with
pvpython script.py (or $PARAVIEW_HOME/bin/pvpython script.py)
- Read the output image with the Read tool to verify correctness
- If the result needs adjustment, iterate (max 5 rounds)
- Report the result to the user