Skip to content

Data

Hakowan's internal data frame and adapters for arrays, tabular data, and common geometry containers.

Internal geometry-plus-attributes data frame representation.

DataFrameLike = object module-attribute

Runtime data accepted by :func:to_dataframe.

Supported values are mesh paths, Lagrange meshes, Hakowan data frames, numeric point arrays, pandas DataFrames, xarray Datasets, PyVista datasets, and Trimesh objects. Optional third-party packages are detected without importing them.

DataFrame dataclass

Store spatial geometry and element attributes on a SurfaceMesh.

Facet-free meshes represent point clouds; meshes with facets represent surfaces. Attributes act as typed columns defined on vertices, edges, facets, corners, or indexed values.

Attributes:

Name Type Description
mesh SurfaceMesh

Lagrange geometry and attribute storage.

roi_box ArrayLike | None

Optional axis-aligned region used for framing and normalization.

source Path | None

Original mesh path for canonical serialization, or None for in-memory data.

Source code in hakowan/grammar/dataframe/dataframe.py
@dataclass(slots=True)
class DataFrame:
    """Store spatial geometry and element attributes on a SurfaceMesh.

    Facet-free meshes represent point clouds; meshes with facets represent
    surfaces. Attributes act as typed columns defined on vertices, edges,
    facets, corners, or indexed values.

    Attributes:
        mesh: Lagrange geometry and attribute storage.
        roi_box: Optional axis-aligned region used for framing and normalization.
        source: Original mesh path for canonical serialization, or ``None`` for
            in-memory data.

    """

    mesh: lagrange.SurfaceMesh
    roi_box: npt.ArrayLike | None = None
    source: Path | None = None

Convert supported geometry or tabular data to a Hakowan DataFrame.

Paths and Lagrange meshes retain topology. Numeric arrays become 2D/3D point clouds. Pandas and xarray inputs use inferred or explicit position columns; numeric fields become vertex attributes. PyVista and Trimesh adapters preserve supported point/vertex and cell/facet attributes without importing optional libraries inside Hakowan.

Parameters:

Name Type Description Default
data object

Mesh path, Lagrange mesh, point array, table, PyVista dataset, Trimesh object, or existing Hakowan DataFrame.

required
positions PositionColumns

Two or three position column names for pandas/xarray input.

None
roi_box ArrayLike | None

Optional axis-aligned region of interest.

None

Returns:

Type Description
DataFrame

A DataFrame backed by a Lagrange SurfaceMesh.

Raises:

Type Description
TypeError

If the input type or option combination is unsupported.

ValueError

If positions, topology, or numeric values are invalid.

Source code in hakowan/grammar/dataframe/adapters.py
def to_dataframe(
    data: object,
    *,
    positions: PositionColumns = None,
    roi_box: npt.ArrayLike | None = None,
) -> DataFrame:
    """Convert supported geometry or tabular data to a Hakowan DataFrame.

    Paths and Lagrange meshes retain topology. Numeric arrays become 2D/3D
    point clouds. Pandas and xarray inputs use inferred or explicit position
    columns; numeric fields become vertex attributes. PyVista and Trimesh
    adapters preserve supported point/vertex and cell/facet attributes without
    importing optional libraries inside Hakowan.

    Args:
        data: Mesh path, Lagrange mesh, point array, table, PyVista dataset,
            Trimesh object, or existing Hakowan DataFrame.
        positions: Two or three position column names for pandas/xarray input.
        roi_box: Optional axis-aligned region of interest.

    Returns:
        A DataFrame backed by a Lagrange SurfaceMesh.

    Raises:
        TypeError: If the input type or option combination is unsupported.
        ValueError: If positions, topology, or numeric values are invalid.

    """
    if isinstance(data, DataFrame):
        if positions is not None:
            raise TypeError("positions cannot be used with an existing DataFrame")
        if roi_box is None:
            return data
        return DataFrame(mesh=data.mesh, roi_box=roi_box, source=data.source)
    if isinstance(data, (str, Path)):
        if positions is not None:
            raise TypeError("positions cannot be used with a mesh file")
        source = Path(data)
        return DataFrame(
            mesh=_mesh_file(source),
            roi_box=roi_box,
            source=source,
        )
    if isinstance(data, lagrange.SurfaceMesh):
        if positions is not None:
            raise TypeError("positions cannot be used with a SurfaceMesh")
        return DataFrame(mesh=data, roi_box=roi_box)
    if isinstance(data, np.ndarray) or (
        isinstance(data, Sequence) and not isinstance(data, (str, bytes, bytearray))
    ):
        if positions is not None:
            raise TypeError("positions cannot be used with a position array")
        return DataFrame(mesh=_point_mesh(data, {}), roi_box=roi_box)

    module = _module_name(data)
    if module == "pandas":
        return DataFrame(mesh=_table_mesh(data, positions), roi_box=roi_box)
    if module == "xarray":
        return DataFrame(mesh=_xarray_mesh(data, positions), roi_box=roi_box)
    if module == "pyvista" and hasattr(data, "points"):
        if positions is not None:
            raise TypeError("positions cannot be used with PyVista geometry")
        return DataFrame(mesh=_pyvista_mesh(data), roi_box=roi_box)
    if module == "trimesh" and hasattr(data, "vertices") and hasattr(data, "faces"):
        if positions is not None:
            raise TypeError("positions cannot be used with Trimesh geometry")
        return DataFrame(mesh=_trimesh_mesh(data), roi_box=roi_box)
    raise TypeError(f"Unsupported data type: {type(data)!r}")

Structured inspection

Inspect supported geometry or tabular data without changing it.

The returned dataclasses contain only JSON-safe metadata. Attribute statistics are computed from unique values for indexed attributes; element_count reports the index count while value_count reports the unique-value count.

Parameters:

Name Type Description Default
data DataFrameLike

Any input accepted by :func:hkw.dataframe.to_dataframe.

required
positions PositionColumns

Optional position column names for pandas and xarray inputs.

None

Returns:

Type Description
DataSummary

Geometry, topology, attribute-domain, and numeric-range metadata.

Source code in hakowan/workflow/inspection.py
def inspect(data: DataFrameLike, *, positions: PositionColumns = None) -> DataSummary:
    """Inspect supported geometry or tabular data without changing it.

    The returned dataclasses contain only JSON-safe metadata. Attribute statistics
    are computed from unique values for indexed attributes; ``element_count``
    reports the index count while ``value_count`` reports the unique-value count.

    Args:
        data: Any input accepted by :func:`hkw.dataframe.to_dataframe`.
        positions: Optional position column names for pandas and xarray inputs.

    Returns:
        Geometry, topology, attribute-domain, and numeric-range metadata.

    """
    mesh, source = _load_data(data, positions)
    if mesh.num_vertices:
        vertices = np.asarray(mesh.vertices, dtype=np.float64)
        finite_vertices = vertices[np.all(np.isfinite(vertices), axis=1)]
        bounds = (
            [
                np.min(finite_vertices, axis=0).tolist(),
                np.max(finite_vertices, axis=0).tolist(),
            ]
            if finite_vertices.size
            else None
        )
    else:
        bounds = None

    names = sorted(
        mesh.get_attribute_name(attribute_id)
        for attribute_id in mesh.get_matching_attribute_ids()
    )
    attributes = tuple(_summarize_attribute(mesh, name) for name in names)
    return DataSummary(
        source=source,
        dimension=int(mesh.dimension),
        vertex_count=int(mesh.num_vertices),
        facet_count=int(mesh.num_facets),
        corner_count=int(mesh.num_corners),
        edge_count=int(mesh.num_edges) if mesh.has_edges else None,
        edges_initialized=bool(mesh.has_edges),
        bounds=bounds,
        attributes=attributes,
    )

Machine-readable geometry, topology, and attribute summary.

Source code in hakowan/workflow/inspection.py
@dataclass(frozen=True, slots=True)
class DataSummary:
    """Machine-readable geometry, topology, and attribute summary."""

    source: str | None
    dimension: int
    vertex_count: int
    facet_count: int
    corner_count: int
    edge_count: int | None
    edges_initialized: bool
    bounds: list[list[float]] | None
    attributes: tuple[AttributeSummary, ...]

    def to_dict(self) -> dict[str, JsonValue]:
        """Return a representation accepted by :func:`json.dumps`."""
        result = asdict(self)
        result["attributes"] = [attribute.to_dict() for attribute in self.attributes]
        return result  # type: ignore[return-value]

    def attribute(self, name: str) -> AttributeSummary:
        """Look up an attribute by name.

        Raises:
            KeyError: If the input has no attribute named ``name``.

        """
        for attribute in self.attributes:
            if attribute.name == name:
                return attribute
        raise KeyError(name)

attribute(name)

Look up an attribute by name.

Raises:

Type Description
KeyError

If the input has no attribute named name.

Source code in hakowan/workflow/inspection.py
def attribute(self, name: str) -> AttributeSummary:
    """Look up an attribute by name.

    Raises:
        KeyError: If the input has no attribute named ``name``.

    """
    for attribute in self.attributes:
        if attribute.name == name:
            return attribute
    raise KeyError(name)

to_dict()

Return a representation accepted by :func:json.dumps.

Source code in hakowan/workflow/inspection.py
def to_dict(self) -> dict[str, JsonValue]:
    """Return a representation accepted by :func:`json.dumps`."""
    result = asdict(self)
    result["attributes"] = [attribute.to_dict() for attribute in self.attributes]
    return result  # type: ignore[return-value]

Machine-readable metadata and statistics for one mesh attribute.

Source code in hakowan/workflow/inspection.py
@dataclass(frozen=True, slots=True)
class AttributeSummary:
    """Machine-readable metadata and statistics for one mesh attribute."""

    name: str
    element: str
    usage: str
    indexed: bool
    channels: int
    dtype: str
    element_count: int
    value_count: int
    finite_count: int
    nonfinite_count: int
    minimum: int | float | list[int | float | None] | None
    maximum: int | float | list[int | float | None] | None
    quantiles: dict[str, int | float | list[int | float | None] | None]

    def to_dict(self) -> dict[str, JsonValue]:
        """Return a representation accepted by :func:`json.dumps`."""
        return asdict(self)  # type: ignore[return-value]

to_dict()

Return a representation accepted by :func:json.dumps.

Source code in hakowan/workflow/inspection.py
def to_dict(self) -> dict[str, JsonValue]:
    """Return a representation accepted by :func:`json.dumps`."""
    return asdict(self)  # type: ignore[return-value]