upxo.geoEntities.mulpoint3d module

Multi-point 3D geometric entity module for UPXO.

Provides MPoint3d, a collection class for N 3-D points stored as a single (N, 3) NumPy array. Supports construction from coordinate arrays, separated x/y/z lists, regular grids, and other UPXO point collections; rigid-body operations (translation, rotation); spatial queries (kd-tree, nearest neighbours, distance computations); and surface-topology checks for voxel-based meshes.

Classes

MPoint3d

Collection of 3-D points backed by an (N, 3) NumPy array.

Usage

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d

@author: Dr. Sunil Anandatheertha

class upxo.geoEntities.mulpoint3d.MPoint3d(coords=None)[source]

Bases: object

Collection of N 3-D points stored as a single (N, 3) NumPy array.

Provides construction class-methods, rigid-body operations (translation, rotation), spatial-query helpers (kd-tree, neighbour search, distance calculations), and surface-topology checks for voxel-based meshes.

coords

Row-major array of 3-D coordinates: each row is [x, y, z].

Type:

numpy.ndarray, shape (N, 3)

tree

Spatial index, populated on demand by maketree().

Type:

scipy.spatial.cKDTree or None

pdist

Reference to scipy.spatial.distance.pdist for pairwise distances.

Type:

callable

Standard coordinate format
--------------------------
::
coords = np.array([[0, 0, 0],

[1, 1, 1], [2, 3, 3], [4, 5, 6]])

coords
pdist
add(toadd=None, operation='add')[source]

Add to or append coordinates in self.coords.

Parameters:
  • toadd (scalar, list, or numpy.ndarray, optional) –

    Value(s) to add or append. Accepted shapes / types:

    • scalar number — broadcast-added to every coordinate.

    • [x, y, z] — added to every row as a 3-element offset.

    • [[x, y, z]] — same as above, single-row list.

    • (N, 3) array — element-wise add; must match self.n.

    • (3, N) array (transposed) — transposed before adding.

    When operation='append', the same shapes are supported but the rows are appended instead of added.

  • operation ({'add', 'append'}, optional) – 'add' modifies coordinates in place; 'append' grows self.coords by the supplied rows. Default is 'add'.

Returns:

Modifies self.coords in place.

Return type:

None

Examples

Example 1 — scalar broadcast addition:

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
mulpoint3d = mp3d.from_coords(np.random.random((10, 3)))
mulpoint3d.add(toadd=10, operation='add')

Example 2 — 3-element offset vector:

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
mulpoint3d = mp3d.from_coords(np.random.random((10, 3)))
mulpoint3d.add(toadd=[-10, 20, 0], operation='add')

Example 3 — element-wise (N, 3) array addition:

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
mulpoint3d = mp3d.from_coords(np.random.random((10, 3)))
mulpoint3d.add(toadd=np.random.random((mulpoint3d.n, 3)), operation='add')

Example 4 — transposed (3, N) array addition:

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
mulpoint3d = mp3d.from_coords(np.random.random((10, 3)))
mulpoint3d.add(toadd=np.random.random((mulpoint3d.n, 3)).T, operation='add')

Example 5 — append rows:

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
mulpoint3d = mp3d.from_coords(np.random.random((10, 3)))
mulpoint3d.add(toadd=np.random.random((10, 3)), operation='append')
classmethod from_coords(point_coords)[source]

Instantiate from an (N, 3) array or list of coordinate triples.

Parameters:

point_coords (array-like, shape (N, 3)) – Each row is a 3-D coordinate [x, y, z].

Returns:

New instance with coords set from point_coords.

Return type:

MPoint3d

Examples

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
point_coords = np.array([[0, 0, 0], [1, 1, 1], [2, 3, 3], [4, 5, 6]])
MULPOINT3D = mp3d.from_coords(point_coords)
print(MULPOINT3D.coords)
classmethod from_x_y_z(x, y, z)[source]

Instantiate from separate x, y, and z coordinate arrays.

Parameters:
  • x (array-like, shape (N,)) – Coordinate components of the N points.

  • y (array-like, shape (N,)) – Coordinate components of the N points.

  • z (array-like, shape (N,)) – Coordinate components of the N points.

Returns:

New instance with coords of shape (N, 3).

Return type:

MPoint3d

Examples

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
x, y, z = np.array([[0, 0, 0], [1, 1, 1], [2, 3, 3], [4, 5, 6]]).T
MULPOINT3D = mp3d.from_x_y_z(x, y, z)
print(MULPOINT3D.coords)
classmethod from_xyz(xyz)[source]

Instantiate from a (3, N) coordinate matrix.

Parameters:

xyz (numpy.ndarray, shape (3, N)) – Row 0 is x-coords, row 1 is y-coords, row 2 is z-coords.

Returns:

New instance with coords of shape (N, 3) (transposed from xyz).

Return type:

MPoint3d

Examples

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
xyz = np.array([[0, 0, 0], [1, 1, 1], [2, 3, 3], [4, 5, 6]]).T
MULPOINT3D = mp3d.from_xyz(xyz)
print(MULPOINT3D.coords)
classmethod from_mulpoint2d(mp2d, zloc=0.0)[source]

Construct from a MPoint2d by appending a constant z-value. Not yet implemented.

classmethod from_mulpoint3d(mulpoint3d=None, dxyz=[0.0, 0.0, 0.0], translate_ref=[0.0, 0.0, 0.0], rot=[0.0, 0.0, 0.0], rot_ref=[0.0, 0.0, 0.0], degree=True)[source]

Instantiate by applying rotation and translation to an existing MPoint3d.

Parameters:
  • mulpoint3d (MPoint3d) – Source point collection to transform.

  • dxyz (list of float, optional) – Translation offsets [dx, dy, dz] applied after rotation. Default is [0.0, 0.0, 0.0].

  • translate_ref (list of float, optional) – Reference point for the translation step; the cloud is shifted so that translate_ref maps to the origin before rotation. Default is [0.0, 0.0, 0.0].

  • rot (list of float, optional) – Rotation angles [rx, ry, rz] about the x, y, and z axes (CCW positive about positive axes). Default is [0.0, 0.0, 0.0].

  • rot_ref (list of float, optional) – Centre of rotation in 3-D space. Default is [0.0, 0.0, 0.0].

  • degree (bool, optional) – If True, rot values are interpreted as degrees; if False, as radians. Default is True.

Returns:

New instance with transformed coordinates.

Return type:

MPoint3d

Notes

Rotation is applied as successive Rx → Ry → Rz matrix multiplication about rot_ref. Translation is applied last by centering on translate_ref and adding dxyz. Refer to the examples for a concrete demonstration of each degree of freedom.

Examples

Example 1 — no rotation, no translation (identity):

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
point_coords = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2], [3, 3, 3]])
mulpoint3d = mp3d.from_coords(point_coords)
MULPOINT3D = mp3d.from_mulpoint3d(mulpoint3d=mulpoint3d,
                                  dxyz=[0.0, 0.0, 0.0],
                                  translate_ref=mulpoint3d.centroid,
                                  rot=[0.0, 0.0, 0.0],
                                  rot_ref=[0.0, 0.0, 0.0],
                                  degree=True)
mulpoint3d.plot(MULPOINT3D.coords)

Example 2 — 45° rotation about x-axis, centred at origin:

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
point_coords = np.array([[0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3]])
mulpoint3d = mp3d.from_coords(point_coords)
MULPOINT3D = mp3d.from_mulpoint3d(mulpoint3d=mulpoint3d,
                                  dxyz=[0.0, 0.0, 0.0],
                                  translate_ref=mulpoint3d.centroid,
                                  rot=[45, 0.0, 0.0],
                                  rot_ref=[0.0, 0.0, 0.0],
                                  degree=True)
mulpoint3d.plot(MULPOINT3D.coords)

Example 3 — 45° rotation about x-axis, non-origin rotation centre:

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
point_coords = np.array([[0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3]])
mulpoint3d = mp3d.from_coords(point_coords)
MULPOINT3D = mp3d.from_mulpoint3d(mulpoint3d=mulpoint3d,
                                  dxyz=[0.0, 0.0, 0.0],
                                  translate_ref=[0.0, 0.0, 0.0],
                                  rot=[45, 0.0, 0.0],
                                  rot_ref=[2.0, 0.0, 0.0],
                                  degree=True)
mulpoint3d.plot(MULPOINT3D.coords)

Example 4 — rotation about x with centroid as both translate_ref and rot_ref:

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
point_coords = np.array([[0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3]])
mulpoint3d = mp3d.from_coords(point_coords)
MULPOINT3D = mp3d.from_mulpoint3d(mulpoint3d=mulpoint3d,
                                  dxyz=[0.0, 0.0, 0.0],
                                  translate_ref=mulpoint3d.centroid,
                                  rot=[45, 0.0, 0.0],
                                  rot_ref=[2.0, 0.0, 0.0],
                                  degree=True)
mulpoint3d.plot(MULPOINT3D.coords)

Example 5 — rotation with rot_ref at centroid:

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
point_coords = np.array([[0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3]])
mulpoint3d = mp3d.from_coords(point_coords)
MULPOINT3D = mp3d.from_mulpoint3d(mulpoint3d=mulpoint3d,
                                  dxyz=[0.0, 0.0, 0.0],
                                  translate_ref=mulpoint3d.centroid,
                                  rot=[45, 0.0, 0.0],
                                  rot_ref=mulpoint3d.centroid,
                                  degree=True)
mulpoint3d.plot(MULPOINT3D.coords)

Example 6 — combined rotation and x-translation:

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
point_coords = np.array([[0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3]])
mulpoint3d = mp3d.from_coords(point_coords)
MULPOINT3D = mp3d.from_mulpoint3d(mulpoint3d=mulpoint3d,
                                  dxyz=[1.0, 0.0, 0.0],
                                  translate_ref=mulpoint3d.centroid,
                                  rot=[45, 0.0, 0.0],
                                  rot_ref=mulpoint3d.centroid,
                                  degree=True)
mulpoint3d.plot(MULPOINT3D.coords)

Example 7 — 3-axis translation, no rotation:

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
point_coords = np.array([[0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3]])
mulpoint3d = mp3d.from_coords(point_coords)
MULPOINT3D = mp3d.from_mulpoint3d(mulpoint3d=mulpoint3d,
                                  dxyz=[1.0, 1.0, -0.5],
                                  translate_ref=mulpoint3d.centroid,
                                  rot=[0, 0.0, 0.0],
                                  rot_ref=mulpoint3d.centroid,
                                  degree=True)
mulpoint3d.plot(MULPOINT3D.coords)
classmethod from_mulsline3d(msline3d)[source]

Construct from a MSline3d endpoint collection. Not yet implemented.

classmethod from_xyz_grid(xspec=[0, 1, 0.25], yspec=[0, 1, 0.25], zspec=[0, 1, 0.25], dxyz=[0.0, 0.0, 0.0], translate_ref=[0.0, 0.0, 0.0], rot=[0.0, 0.0, 0.0], rot_ref=[0.0, 0.0, 0.0], degree=True)[source]

Instantiate from a regular 3-D Cartesian grid with optional rigid-body transform.

Builds a meshgrid from the three axis specifications, flattens it to an (N, 3) array, then delegates to from_mulpoint3d() to apply the requested rotation and translation.

Parameters:
  • xspec (list of float, optional) – [xstart, xend, xincrement] for the x-axis grid. Default is [0, 1, 0.25].

  • yspec (list of float, optional) – [ystart, yend, yincrement] for the y-axis grid. Default is [0, 1, 0.25].

  • zspec (list of float, optional) – [zstart, zend, zincrement] for the z-axis grid. Default is [0, 1, 0.25].

  • dxyz (list of float, optional) – Translation offsets [dx, dy, dz]. Default is [0.0, 0.0, 0.0].

  • translate_ref (list of float or str, optional) – Reference point for translation. Pass 'centroid' to use the grid centroid, or a [x, y, z] coordinate list. Default is [0.0, 0.0, 0.0].

  • rot (list of float, optional) – Rotation angles [rx, ry, rz] about x, y, z axes (CCW positive). Default is [0.0, 0.0, 0.0].

  • rot_ref (list of float, optional) – Centre of rotation. Default is [0.0, 0.0, 0.0].

  • degree (bool, optional) – If True, rot is in degrees; if False, in radians. Default is True.

Returns:

New instance containing the grid points after the rigid-body transform.

Return type:

MPoint3d

Examples

Example 1 — two grids, one base and one rotated by (5°, 5°, 5°):

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
xspec, yspec, zspec = [0, 1, 0.1], [0, 1, 0.1], [0, 1, 0.1]
dxyz, translate_ref = [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]
mulpoint3d = mp3d.from_xyz_grid(xspec=xspec, yspec=yspec, zspec=zspec,
                                dxyz=dxyz, translate_ref=translate_ref,
                                rot=[0.0, 0.0, 0.0],
                                rot_ref=[0.0, 0.0, 0.0],
                                degree=True)
MULPOINT3D = mp3d.from_xyz_grid(xspec=xspec, yspec=yspec, zspec=zspec,
                                dxyz=dxyz, translate_ref=translate_ref,
                                rot=[5.0, 5.0, 5.0],
                                rot_ref=[0.0, 0.0, 0.0],
                                degree=True)
MULPOINT3D.plot(mulpoint3d.coords, primary_ms=50, secondary_ms=5)
property n

Number of points in the collection.

property centroid

Mean 3D coordinate of all points as a (3,) array.

property points

Return a list of Point3d objects built from self.coords.

property x

x-coordinates of all points as a 1-D array.

property y

y-coordinates of all points as a 1-D array.

property z

z-coordinates of all points as a 1-D array.

property ckd_tree

Build and return a cKDTree for fast nearest-neighbour queries.

squared_distances_to_point(point)[source]

Return squared Euclidean distances from all points to point.

Parameters:

point (Point3d or array-like) – Target point. Validated via val_point_and_get_coord.

Returns:

Squared distance from each point in self.coords to point.

Return type:

numpy.ndarray, shape (N,)

distances_to_point(point)[source]

Return Euclidean distances from all points to point.

Parameters:

point (Point3d or array-like) – Target point.

Returns:

Euclidean distance from each point in self.coords to point.

Return type:

numpy.ndarray, shape (N,)

squared_distance_to_centroid(points, validate_points=True, points_type='numpy')[source]

Compute squared distances from self.centroid to a set of 3-D points.

Parameters:
  • points (list of Point3d or numpy.ndarray, shape (M, 3)) – Target points to measure from self.centroid.

  • validate_points (bool, optional) – When True the input is validated and converted automatically. When confident that points is an (M, 3) NumPy array, set to False to skip validation overhead. Default is True.

  • points_type ({'numpy', 'upxo', 'shapely', 'coord', 'coord_pair'}, optional) – Type hint used only when validate_points=False. Use 'numpy' for plain NumPy arrays. Default is 'numpy'.

Returns:

Squared Euclidean distances from each target point to self.centroid.

Return type:

numpy.ndarray, shape (M,)

Examples

Example 1 — validated UPXO point objects:

from upxo.geoEntities.mulpoint3d import MPoint3d
MULPOINT3D = MPoint3d.from_coords(np.random.random((10, 3)))
POINTS = make_p3d(2 + np.random.random((10, 3)), return_type='p3d')
MULPOINT3D.squared_distance_to_centroid(POINTS, validate_points=True)

Example 2 — raw NumPy array, validation skipped:

POINTS = 2 + np.random.random((10, 3))
MULPOINT3D.squared_distance_to_centroid(POINTS, validate_points=False,
                                        points_type='numpy')
distance_to_centroid(points, validate_points=True, points_type='numpy')[source]

Compute Euclidean distances from self.centroid to a set of 3-D points.

Parameters:
Returns:

Euclidean distances from each target point to self.centroid.

Return type:

numpy.ndarray, shape (M,)

Examples

from upxo.geoEntities.mulpoint3d import MPoint3d
MULPOINT3D = MPoint3d.from_coords(np.random.random((10, 3)))
POINTS = 2 + np.random.random((10, 3))
MULPOINT3D.distance_to_centroid(POINTS, validate_points=False,
                                points_type='numpy')
convex_hull()[source]

Compute the convex hull of the 3D point set. Not yet implemented.

maketree(treeType='ckdtree', saa=False, throw=False, balance=True)[source]

Build a spatial index tree over self.coords.

Parameters:
  • treeType ({'ckdtree', 'kdtree'}, optional) – Type of spatial index. Currently only 'ckdtree' is implemented. Default is 'ckdtree'.

  • saa (bool, optional) – If True, store the built tree on self.tree. Default is False.

  • throw (bool, optional) – If True, return the tree object. Default is False.

  • balance (bool, optional) – Passed as balanced_tree to cKDTree. Default is True.

Returns:

The built tree when throw=True; otherwise None.

Return type:

scipy.spatial.cKDTree or None

Examples

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
mulpoint3d = mp3d.from_coords(np.random.random((25, 3)))
tree = mulpoint3d.maketree(treeType='ckdtree', throw=True)
print(tree.data.shape)
get_self_distance_max()[source]

Return the maximum pairwise distance among all points in self.coords.

get_self_distance_min()[source]

Return the minimum pairwise distance among all points in self.coords.

find_first_order_neigh_CUBIC(coord, vox_size, return_indices=True, return_coords=True, return_input_coord=False, k=1.000001)[source]

Find first-order (face+edge+vertex) neighbours of a voxel on a cubic lattice.

A point p in self.coords is a first-order neighbour of coord if |p[d] - coord[d]| <= vox_size for all three dimensions d (i.e. it fits within a 3×3×3 cubic stencil centred at coord). The tolerance multiplier k avoids floating-point boundary misclassification.

Parameters:
  • coord (array-like, shape (3,)) – Centre voxel coordinate. Must be a member of self.coords.

  • vox_size (float) – Voxel edge length; defines the stencil half-width.

  • return_indices (bool, optional) – Include neighbour indices into self.coords in the output. Default is True.

  • return_coords (bool, optional) – Include neighbour coordinate arrays in the output. Default is True.

  • return_input_coord (bool, optional) – Append coord to the return tuple. Default is False.

  • k (float, optional) – Tolerance multiplier applied to vox_size to avoid floating-point boundary misses. Default is 1.000001.

Returns:

Contents depend on the flag combination:

  • (return_indices=True, return_coords=False)(indices,) or (indices, coord)

  • (return_indices=False, return_coords=True)(coords,) or (coords, coord)

  • (return_indices=True, return_coords=True)(indices, coords, coord) or (indices, coords)

Return type:

tuple

Notes

Designed for cubic lattices only. A voxel [x, y, z] is a first-order neighbour of [cx, cy, cz] when |x-cx| <= A, |y-cy| <= B, |z-cz| <= C where A = B = C = vox_size. This includes up to 26 neighbours in a full 3×3×3 grid.

Examples

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
vs = 0.1
xspec, yspec, zspec = [0, 1, vs], [0, 1, vs], [0, 1, vs]
X, Y, Z = np.meshgrid(np.arange(xspec[0], xspec[1], xspec[2]),
                      np.arange(yspec[0], yspec[1], yspec[2]),
                      np.arange(zspec[0], zspec[1], zspec[2]))
mp = mp3d.from_coords(np.vstack((X.ravel(), Y.ravel(), Z.ravel())).T)
mp.find_first_order_neigh_CUBIC((0.5, 0.5, 0.5), vs)
check_if_point_can_host_a_single_surface_CUBIC(coord, vs)[source]

Check whether a voxel can have a single non-self-intersecting surface through it.

Given that the 3×3×3 neighbourhood contains 27 voxels in various ON/OFF states, a single surface can pass through the centre voxel and all ON-state neighbours only when the number of ON-state neighbours is at most 4 (empirical threshold; equivalent to at most 5 points including the centre).

The CUBIC suffix indicates this method is designed for cubic lattices only.

Parameters:
  • coord (array-like, shape (3,)) – Coordinate of the voxel to assess. Must be a member of self.coords.

  • vs (float) – Voxel size used to define the 3×3×3 stencil.

Returns:

True if 2–4 same-state neighbours exist (a surface can be formed), False if outside that range, None if coord is not found in self.coords.

Return type:

bool or None

Examples

from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
vs = 0.1
xspec, yspec, zspec = [0, 1, vs], [0, 1, vs], [0, 1, vs]
X, Y, Z = np.meshgrid(np.arange(xspec[0], xspec[1], xspec[2]),
                      np.arange(yspec[0], yspec[1], yspec[2]),
                      np.arange(zspec[0], zspec[1], zspec[2]))
mp = mp3d.from_coords(np.vstack((X.ravel(), Y.ravel(), Z.ravel())).T)
coords = mp.find_first_order_neigh_CUBIC((0.5, 0.5, 0.5), vs,
                                         return_indices=False,
                                         return_coords=True,
                                         return_input_coord=False)[0]
coord = np.array([0.5, 0.5, 0.5])
coord_loc = np.argwhere(np.all(coords == coord, axis=1)).squeeze()
rand_4_locs = np.sort(np.random.choice(range(coords.shape[0]), 4, replace=False))
points_5_locs = np.unique(np.hstack((coord_loc, rand_4_locs)))
coords_ON_state = coords[points_5_locs]
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(coords[:, 0], coords[:, 1], coords[:, 2],
           c='c', marker='o', alpha=0.1, s=200, edgecolors='black')
ax.scatter(coords_ON_state[:, 0], coords_ON_state[:, 1], coords_ON_state[:, 2],
           c='b', marker='o', alpha=0.8, s=50, edgecolors='black')
result = mp.check_if_point_can_host_a_single_surface_CUBIC(coord, vs)
print("Can host single surface:", result)
get_local_tn(coord, k=5)[source]

Find the local tangent plane and normal vector at a coordinate.

Parameters:
  • coord (array-like, shape (3,)) – Query point. Must be a member of self.coords.

  • k (int, optional) – Number of nearest neighbours used to fit the tangent plane. Default is 5.

Returns:

Not yet implemented.

Return type:

None

find_intersection_voxels_with_line(sl3d, cod)[source]

Find all voxels in self.coords that intersect a 3-D line within a cut-off distance.

Parameters:
  • sl3d (Sline3d) – UPXO 3-D straight-line object to intersect against.

  • cod (float) – Cut-off distance; only voxels within this distance of sl3d are considered to intersect.

Returns:

Not yet implemented.

Return type:

None

tree