census.py

4.5 kB · python · 151 lines

1import numpy as np2from typing import Dict, TYPE_CHECKING3from .errors import MrlyError45if TYPE_CHECKING:6    from mrlypy.two.models import Cell2d7    from mrlypy.three.models import Cell3d89# 2D PRIMITIVES1011def _occ_2d(grid: np.ndarray) -> np.ndarray:12    return (grid != 0)1314def cells_2d(grid: np.ndarray) -> int:15    return int(_occ_2d(grid).sum())1617def vertices_2d(grid: np.ndarray) -> int:18    occ = _occ_2d(grid)19    h, w = occ.shape20    corners = np.zeros((h + 1, w + 1), dtype=bool)21    corners[:-1, :-1] |= occ22    corners[:-1, 1:] |= occ23    corners[1:, :-1] |= occ24    corners[1:, 1:] |= occ25    return int(corners.sum())2627def edges_2d(grid: np.ndarray) -> int:28    occ = _occ_2d(grid)29    h, w = occ.shape30    horiz = np.zeros((h + 1, w), dtype=bool)31    horiz[:-1, :] |= occ32    horiz[1:, :] |= occ33    vert = np.zeros((h, w + 1), dtype=bool)34    vert[:, :-1] |= occ35    vert[:, 1:] |= occ36    return int(horiz.sum() + vert.sum())3738def perimeter_2d(grid: np.ndarray) -> int:39    occ = _occ_2d(grid).astype(np.int64)40    h, w = occ.shape41    top = np.pad(occ, ((1, 0), (0, 0)))[:-1]42    bottom = np.pad(occ, ((0, 1), (0, 0)))[1:]43    left = np.pad(occ, ((0, 0), (1, 0)))[:, :-1]44    right = np.pad(occ, ((0, 0), (0, 1)))[:, 1:]45    exposed = (occ - top) + (occ - bottom) + (occ - left) + (occ - right)46    return int(np.clip(exposed, 0, None)[occ.astype(bool)].sum())4748def faces_2d(grid: np.ndarray) -> int:49    return cells_2d(grid)5051def euler_2d(grid: np.ndarray) -> int:52    return vertices_2d(grid) - edges_2d(grid) + faces_2d(grid)5354def census_2d(grid: np.ndarray) -> Dict[str, int]:55    return {56        "cells": cells_2d(grid),57        "vertices": vertices_2d(grid),58        "edges": edges_2d(grid),59        "perimeter": perimeter_2d(grid),60        "faces": faces_2d(grid),61        "euler": euler_2d(grid),62    }6364# 3D PRIMITIVES6566def _occ_3d(grid: np.ndarray) -> np.ndarray:67    return (grid != 0)6869def cells_3d(grid: np.ndarray) -> int:70    return int(_occ_3d(grid).sum())7172def vertices_3d(grid: np.ndarray) -> int:73    occ = _occ_3d(grid)74    d, h, w = occ.shape75    corners = np.zeros((d + 1, h + 1, w + 1), dtype=bool)76    for dz in (0, 1):77        for dy in (0, 1):78            for dx in (0, 1):79                corners[dz:dz + d, dy:dy + h, dx:dx + w] |= occ80    return int(corners.sum())8182def edges_3d(grid: np.ndarray) -> int:83    occ = _occ_3d(grid)84    d, h, w = occ.shape85    ex = np.zeros((d + 1, h + 1, w), dtype=bool)86    for dz in (0, 1):87        for dy in (0, 1):88            ex[dz:dz + d, dy:dy + h, :] |= occ89    ey = np.zeros((d + 1, h, w + 1), dtype=bool)90    for dz in (0, 1):91        for dx in (0, 1):92            ey[dz:dz + d, :, dx:dx + w] |= occ93    ez = np.zeros((d, h + 1, w + 1), dtype=bool)94    for dy in (0, 1):95        for dx in (0, 1):96            ez[:, dy:dy + h, dx:dx + w] |= occ97    return int(ex.sum() + ey.sum() + ez.sum())9899def faces_3d(grid: np.ndarray) -> int:100    occ = _occ_3d(grid)101    d, h, w = occ.shape102    fz = np.zeros((d + 1, h, w), dtype=bool)103    fz[:-1] |= occ104    fz[1:] |= occ105    fy = np.zeros((d, h + 1, w), dtype=bool)106    fy[:, :-1] |= occ107    fy[:, 1:] |= occ108    fx = np.zeros((d, h, w + 1), dtype=bool)109    fx[:, :, :-1] |= occ110    fx[:, :, 1:] |= occ111    return int(fz.sum() + fy.sum() + fx.sum())112113def surface_3d(grid: np.ndarray) -> int:114    occ = _occ_3d(grid).astype(np.int64)115    total = 0116    for axis in (0, 1, 2):117        a = np.pad(occ, [(1, 0) if i == axis else (0, 0) for i in range(3)])118        a = np.take(a, range(occ.shape[axis]), axis=axis)119        b = np.pad(occ, [(0, 1) if i == axis else (0, 0) for i in range(3)])120        b = np.take(b, range(1, occ.shape[axis] + 1), axis=axis)121        total += int(np.clip(occ - a, 0, None).sum())122        total += int(np.clip(occ - b, 0, None).sum())123    return total124125def euler_3d(grid: np.ndarray) -> int:126    return vertices_3d(grid) - edges_3d(grid) + faces_3d(grid) - cells_3d(grid)127128def census_3d(grid: np.ndarray) -> Dict[str, int]:129    return {130        "cells": cells_3d(grid),131        "vertices": vertices_3d(grid),132        "edges": edges_3d(grid),133        "faces": faces_3d(grid),134        "surface": surface_3d(grid),135        "euler": euler_3d(grid),136    }137138# PUBLIC139140def census(cell) -> Dict[str, int]:141    grid = getattr(cell, "_cell", cell)142    if hasattr(grid, "depth"):143        return census_3d(grid.types)144    if hasattr(grid, "height"):145        return census_2d(grid.types)146    arr = np.asarray(cell)147    if arr.ndim == 3:148        return census_3d(arr)149    if arr.ndim == 2:150        return census_2d(arr)151    raise MrlyError("census expects a Cell2d, Cell3d, or 2D/3D array.")