Skip to content

Commit 5465eae

Browse files
sbryngelsonclaude
andcommitted
Fix PyVista isosurface: use _compute_isomesh() instead of grid.contour()
The grid.contour() approach produced flat, incorrect-looking isosurfaces that didn't match the Plotly rendering. Now both paths use the same marching cubes algorithm (_compute_isomesh) for identical geometry. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 19a5d8d commit 5465eae

1 file changed

Lines changed: 61 additions & 49 deletions

File tree

toolchain/mfc/viz/interactive.py

Lines changed: 61 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -695,38 +695,44 @@ def _pv_render_on_thread(gen, raw, x_cc, y_cc, z_cc, mode, cmap, log_fn, # pyli
695695
ds_raw, ds_x, ds_y, ds_z = _downsample_3d(
696696
raw, x_cc, y_cc, z_cc, 500_000)
697697

698-
nx, ny, nz = ds_raw.shape
699-
grid = pv.ImageData()
700-
grid.dimensions = (nx, ny, nz)
701-
grid.origin = (float(ds_x[0]), float(ds_y[0]), float(ds_z[0]))
702-
if nx > 1:
703-
grid.spacing = (
704-
float(ds_x[-1] - ds_x[0]) / max(nx - 1, 1),
705-
float(ds_y[-1] - ds_y[0]) / max(ny - 1, 1),
706-
float(ds_z[-1] - ds_z[0]) / max(nz - 1, 1),
707-
)
708-
scalar = log_fn(ds_raw).astype(np.float64)
709-
scalar = np.where(np.isfinite(scalar), scalar, np.nan)
710-
grid.point_data['scalar'] = scalar.flatten(order='F')
711-
712698
if mode == 'isosurface':
713699
ilo = cmin + rng * iso_min_frac
714700
ihi = cmin + rng * max(iso_max_frac, iso_min_frac + 0.01)
715-
levels = np.linspace(ilo, ihi, max(int(iso_n), 1)).tolist()
716-
try:
717-
iso = grid.contour(levels, scalars='scalar')
718-
if iso.n_points > 0:
719-
pl.add_mesh(iso, scalars='scalar', cmap=cmap,
720-
clim=[ilo, ihi],
721-
smooth_shading=True, opacity=1.0,
722-
show_scalar_bar=True,
723-
scalar_bar_args=dict(
724-
title=varname, color='#cdd6f4',
725-
label_font_size=10, title_font_size=12))
726-
except Exception: # pylint: disable=broad-except
727-
pass
701+
vx, vy, vz, fi, fj, fk, intens = _compute_isomesh(
702+
ds_raw, ds_x, ds_y, ds_z, log_fn, ilo, ihi, iso_n)
703+
if len(vx) > 3:
704+
points = np.column_stack([vx, vy, vz])
705+
n_faces = len(fi)
706+
faces = np.empty(n_faces * 4, dtype=np.int32)
707+
faces[0::4] = 3
708+
faces[1::4] = fi
709+
faces[2::4] = fj
710+
faces[3::4] = fk
711+
mesh = pv.PolyData(points, faces)
712+
mesh.point_data['intensity'] = intens
713+
pl.add_mesh(mesh, scalars='intensity', cmap=cmap,
714+
clim=[ilo, ihi],
715+
smooth_shading=True, opacity=1.0,
716+
lighting=True, specular=0.3,
717+
show_scalar_bar=True,
718+
scalar_bar_args=dict(
719+
title=varname, color='#cdd6f4',
720+
label_font_size=10, title_font_size=12))
728721

729722
elif mode == 'volume':
723+
nx, ny, nz = ds_raw.shape
724+
grid = pv.ImageData()
725+
grid.dimensions = (nx, ny, nz)
726+
grid.origin = (float(ds_x[0]), float(ds_y[0]), float(ds_z[0]))
727+
if nx > 1:
728+
grid.spacing = (
729+
float(ds_x[-1] - ds_x[0]) / max(nx - 1, 1),
730+
float(ds_y[-1] - ds_y[0]) / max(ny - 1, 1),
731+
float(ds_z[-1] - ds_z[0]) / max(nz - 1, 1),
732+
)
733+
scalar = log_fn(ds_raw).astype(np.float64)
734+
scalar = np.where(np.isfinite(scalar), scalar, np.nan)
735+
grid.point_data['scalar'] = scalar.flatten(order='F')
730736
vlo = cmin + rng * vol_min_frac
731737
vhi = cmin + rng * max(vol_max_frac, vol_min_frac + 0.01)
732738
grid_vol = grid.threshold([vlo, vhi], scalars='scalar')
@@ -742,19 +748,8 @@ def _pv_render_on_thread(gen, raw, x_cc, y_cc, z_cc, mode, cmap, log_fn, # pyli
742748
if overlay_raw is not None:
743749
ov_ds, ov_x, ov_y, ov_z = _downsample_3d(
744750
overlay_raw, overlay_x, overlay_y, overlay_z, 500_000)
745-
o_nx, o_ny, o_nz = ov_ds.shape
746-
ov_grid = pv.ImageData()
747-
ov_grid.dimensions = (o_nx, o_ny, o_nz)
748-
ov_grid.origin = (float(ov_x[0]), float(ov_y[0]), float(ov_z[0]))
749-
if o_nx > 1:
750-
ov_grid.spacing = (
751-
float(ov_x[-1] - ov_x[0]) / max(o_nx - 1, 1),
752-
float(ov_y[-1] - ov_y[0]) / max(o_ny - 1, 1),
753-
float(ov_z[-1] - ov_z[0]) / max(o_nz - 1, 1),
754-
)
755751
ov_scalar = log_fn(ov_ds).astype(np.float64)
756752
ov_scalar = np.where(np.isfinite(ov_scalar), ov_scalar, np.nan)
757-
ov_grid.point_data['scalar'] = ov_scalar.flatten(order='F')
758753
ov_vmin = float(np.nanmin(ov_scalar))
759754
ov_vmax = float(np.nanmax(ov_scalar))
760755
ov_rng = ov_vmax - ov_vmin if ov_vmax > ov_vmin else 1.0
@@ -763,18 +758,35 @@ def _pv_render_on_thread(gen, raw, x_cc, y_cc, z_cc, mode, cmap, log_fn, # pyli
763758
ov_lo = ov_vmin + ov_rng * overlay_iso_min
764759
ov_hi = ov_vmin + ov_rng * max(overlay_iso_max,
765760
overlay_iso_min + 0.01)
766-
ov_lvls = np.linspace(ov_lo, ov_hi,
767-
max(int(overlay_nlevels), 1)).tolist()
768-
try:
769-
ov_iso = ov_grid.contour(ov_lvls, scalars='scalar')
770-
if ov_iso.n_points > 0:
771-
pl.add_mesh(ov_iso, color=overlay_color,
772-
opacity=float(overlay_opacity),
773-
smooth_shading=True,
774-
show_scalar_bar=False)
775-
except Exception: # pylint: disable=broad-except
776-
pass
761+
ovx, ovy, ovz, ofi, ofj, ofk, _ = _compute_isomesh(
762+
ov_ds, ov_x, ov_y, ov_z, log_fn,
763+
ov_lo, ov_hi, overlay_nlevels)
764+
if len(ovx) > 3:
765+
ov_pts = np.column_stack([ovx, ovy, ovz])
766+
n_ov = len(ofi)
767+
ov_faces = np.empty(n_ov * 4, dtype=np.int32)
768+
ov_faces[0::4] = 3
769+
ov_faces[1::4] = ofi
770+
ov_faces[2::4] = ofj
771+
ov_faces[3::4] = ofk
772+
ov_mesh = pv.PolyData(ov_pts, ov_faces)
773+
pl.add_mesh(ov_mesh, color=overlay_color,
774+
opacity=float(overlay_opacity),
775+
smooth_shading=True,
776+
show_scalar_bar=False)
777777
else:
778+
o_nx, o_ny, o_nz = ov_ds.shape
779+
ov_grid = pv.ImageData()
780+
ov_grid.dimensions = (o_nx, o_ny, o_nz)
781+
ov_grid.origin = (float(ov_x[0]), float(ov_y[0]),
782+
float(ov_z[0]))
783+
if o_nx > 1:
784+
ov_grid.spacing = (
785+
float(ov_x[-1] - ov_x[0]) / max(o_nx - 1, 1),
786+
float(ov_y[-1] - ov_y[0]) / max(o_ny - 1, 1),
787+
float(ov_z[-1] - ov_z[0]) / max(o_nz - 1, 1),
788+
)
789+
ov_grid.point_data['scalar'] = ov_scalar.flatten(order='F')
778790
ov_vlo = ov_vmin + ov_rng * overlay_vol_min
779791
ov_vhi = ov_vmin + ov_rng * max(overlay_vol_max,
780792
overlay_vol_min + 0.01)

0 commit comments

Comments
 (0)