.ipynb

Load SPHARM Coefficients#

ktch can read spherical harmonic coefficients from SPHARM-PDM .coef files, the output of the ParaToSPHARMMesh step of SPHARM-PDM. This guide reads a sample .coef file and reconstructs the 3D shape encoded by its coefficients.

Read SPHARM-PDM coefficients#

from ktch.datasets import fetch
from ktch.io import read_spharmpdm_coef

coef_path = fetch("danshaku_08_allSegments_SPHARM.coef")
data = read_spharmpdm_coef(coef_path)

print(f"specimen={data.specimen_name}, l_max={data.l_max}, "
      f"shape={data.to_numpy().shape}")
specimen=danshaku_08_allSegments_SPHARM, l_max=25, shape=(676, 3)

data.coeffs[l] holds the complex coefficients of degree l with shape (2*l+1, 3). See Harmonic-based Morphometrics for the convention.

Reconstruct the surface#

Convert the SPHARM-PDM coefficients to the real basis used by SphericalHarmonicAnalysis, then call inverse_transform to evaluate the reconstructed surface on a (theta, phi) grid.

from ktch.harmonic import SphericalHarmonicAnalysis
from ktch.io import spharmpdm_to_sha_coeffs

coeffs = spharmpdm_to_sha_coeffs(data)

sha = SphericalHarmonicAnalysis(n_harmonics=data.l_max)
X_coords = sha.inverse_transform(coeffs)
print(f"surface grid shape: {X_coords.shape}")  # (1, n_theta, n_phi, 3)
surface grid shape: (1, 90, 180, 3)

To use a different angular resolution, pass theta_range and phi_range to inverse_transform.

Register precomputed coefficients#

SPHARM-PDM coefficients might include each specimen’s position, orientation, and size. Before shape comparison, register them with SphericalHarmonicRegistration, which removes that information (if still present) from the coefficients without recomputing them from the surface. method="first_order" uses the degree-1 ellipsoid to align orientation and the parameter sphere; scale=False keeps size.

from ktch.harmonic import SphericalHarmonicRegistration

reg = SphericalHarmonicRegistration(method="first_order", scale=False)
registered = reg.fit_transform(coeffs)
print(f"registered coefficients shape: {registered.shape}")
registered coefficients shape: (1, 2028)

Because it maps coefficients to coefficients, it composes in a scikit-learn Pipeline.

Plot the 3D shape#

import plotly.graph_objects as go
x, y, z = X_coords[0].T

fig = go.Figure(
    data=[
        go.Surface(x=x, y=y, z=z, opacity=0.8, showscale=False),
    ]
)

fig.update_layout(
    width=700,
    height=700,
    autosize=False,
    scene=dict(
        camera=dict(
            up=dict(x=0, y=0, z=1),
            eye=dict(x=1.1, y=1.1, z=1.1),
        ),
        aspectmode="data",
    ),
)

fig.show()

See also