Skip to content

c2f4dt.utils.io.importers

c2f4dt.utils.io.importers

ImportedObject dataclass

Container for imported datasets (point cloud or mesh).

Source code in src/c2f4dt/utils/io/importers.py
@dataclass
class ImportedObject:
    """Container for imported datasets (point cloud or mesh)."""

    kind: str  # "points" | "mesh"
    name: str
    points: Optional[np.ndarray] = None        # (N, 3) float64
    colors: Optional[np.ndarray] = None        # (N, 3) float32 in [0,1]
    intensity: Optional[np.ndarray] = None     # (N,) float32
    normals: Optional[np.ndarray] = None       # (N, 3) float32 unit vectors
    faces: Optional[np.ndarray] = None         # (M, 3) int32 (triangles)
    pv_mesh: Optional[object] = None           # pyvista.PolyData or similar
    meta: Dict[str, object] = field(default_factory=dict)

    def bounds(self) -> Optional[Tuple[float, float, float, float, float, float]]:
        """Return (xmin, xmax, ymin, ymax, zmin, zmax) if available."""
        if self.points is not None and len(self.points):
            mins = self.points.min(axis=0)
            maxs = self.points.max(axis=0)
            return (
                float(mins[0]), float(maxs[0]),
                float(mins[1]), float(maxs[1]),
                float(mins[2]), float(maxs[2])
            )
        if self.pv_mesh is not None and _HAS_PYVISTA:
            try:
                return tuple(self.pv_mesh.bounds)  # type: ignore[return-value]
            except Exception:
                return None
        return None

colors = None class-attribute instance-attribute

faces = None class-attribute instance-attribute

intensity = None class-attribute instance-attribute

kind instance-attribute

meta = field(default_factory=dict) class-attribute instance-attribute

name instance-attribute

normals = None class-attribute instance-attribute

points = None class-attribute instance-attribute

pv_mesh = None class-attribute instance-attribute

bounds()

Return (xmin, xmax, ymin, ymax, zmin, zmax) if available.

Source code in src/c2f4dt/utils/io/importers.py
def bounds(self) -> Optional[Tuple[float, float, float, float, float, float]]:
    """Return (xmin, xmax, ymin, ymax, zmin, zmax) if available."""
    if self.points is not None and len(self.points):
        mins = self.points.min(axis=0)
        maxs = self.points.max(axis=0)
        return (
            float(mins[0]), float(maxs[0]),
            float(mins[1]), float(maxs[1]),
            float(mins[2]), float(maxs[2])
        )
    if self.pv_mesh is not None and _HAS_PYVISTA:
        try:
            return tuple(self.pv_mesh.bounds)  # type: ignore[return-value]
        except Exception:
            return None
    return None

downsample_random(points, percent)

Randomly downsample points to the given percentage.

Parameters:

Name Type Description Default
points ndarray

(N, 3) float array.

required
percent float

Target percentage in [1, 100].

required

Returns:

Type Description
ndarray

Indices of selected points (1D int array).

Source code in src/c2f4dt/utils/io/importers.py
def downsample_random(points: np.ndarray, percent: float) -> np.ndarray:
    """Randomly downsample points to the given percentage.

    Args:
        points: (N, 3) float array.
        percent: Target percentage in [1, 100].

    Returns:
        Indices of selected points (1D int array).
    """
    if points.size == 0:
        return np.empty((0,), dtype=np.int64)
    p = np.clip(percent, 1.0, 100.0) / 100.0
    n = points.shape[0]
    k = max(1, int(round(n * p)))
    idx = np.random.default_rng().choice(n, size=k, replace=False)
    return np.sort(idx)

downsample_voxel_auto(points, target_percent)

Voxel-grid downsampling using a simple auto voxel-size heuristic.

Heuristic: scala la dimensione del voxel con l'estensione del bounding-box e il fattore di riduzione desiderato. La % risultante è approssimata.

Parameters:

Name Type Description Default
points ndarray

(N, 3) float array.

required
target_percent float

Target percentage in [1, 100].

required

Returns:

Type Description
ndarray

Indices of representative points (1D int array).

Source code in src/c2f4dt/utils/io/importers.py
def downsample_voxel_auto(points: np.ndarray, target_percent: float) -> np.ndarray:
    """Voxel-grid downsampling using a simple auto voxel-size heuristic.

    Heuristic: scala la dimensione del voxel con l'estensione del bounding-box e
    il fattore di riduzione desiderato. La % risultante è approssimata.

    Args:
        points: (N, 3) float array.
        target_percent: Target percentage in [1, 100].

    Returns:
        Indices of representative points (1D int array).
    """
    n = points.shape[0]
    if n == 0:
        return np.empty((0,), dtype=np.int64)
    p = np.clip(target_percent, 1.0, 100.0) / 100.0
    if p >= 0.999:
        return np.arange(n, dtype=np.int64)

    mins = points.min(axis=0)
    maxs = points.max(axis=0)
    extent = np.maximum(maxs - mins, 1e-9)

    reduction = max(1e-3, 1.0 - p)
    base = float(extent.max())
    voxel = base * reduction * 0.02  # 2% bbox @50% riduzione
    voxel = max(voxel, base * 1e-4)

    q = np.floor((points - mins) / voxel).astype(np.int64)
    key = (q[:, 0] * 73856093) ^ (q[:, 1] * 19349663) ^ (q[:, 2] * 83492791)
    order = np.argsort(key, kind="mergesort")
    key_sorted = key[order]
    mask = np.concatenate(([True], key_sorted[1:] != key_sorted[:-1]))
    kept_sorted = order[mask]
    return np.sort(kept_sorted)

import_file(path)

Import a geometry file into one or more ImportedObject.

Auto-detects reader by extension and available backends.

Parameters:

Name Type Description Default
path str

File path.

required

Returns:

Type Description
List[ImportedObject]

List of imported objects.

Raises:

Type Description
ValueError

On unsupported type or missing backend.

Source code in src/c2f4dt/utils/io/importers.py
def import_file(path: str) -> List[ImportedObject]:
    """Import a geometry file into one or more `ImportedObject`.

    Auto-detects reader by extension and available backends.

    Args:
        path: File path.

    Returns:
        List of imported objects.

    Raises:
        ValueError: On unsupported type or missing backend.
    """
    p = Path(path)
    ext = p.suffix.lower()

    if ext in {".ply", ".obj", ".vtp", ".stl", ".vtk", ".gltf", ".glb"}:
        return _import_with_pyvista(p)

    if ext in {".las", ".laz"}:
        return _import_las(p)

    if ext in {".e57"}:
        return _import_e57(p)

    raise ValueError(f"Unsupported file type: {ext}")