Skip to content

Typing Overhaul #3

Merged
merged 5 commits into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions AUVSim/auv.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
from Raytrace.TriangleMesh import TriangleMesh, Ray
from Raytrace import TriangleMesh
from Raytrace.SideScan import SideScan
import numpy as np
from typing import Any
from CustomTypes import Ray, Vector3, Vector3Array

class AUV:
ideal_distance:float # Currently unused
start_pos:np.ndarray
start_facing:np.ndarray
def __init__(self, position:np.ndarray, facing:np.ndarray):
start_pos:Vector3
start_facing:Vector3
def __init__(self, position:Vector3, facing:Vector3):
self.start_pos = position
self.start_facing = facing
class AUVPath:
positions:np.ndarray[float, Any]
facings:np.ndarray[float, Any]
positions:Vector3Array
facings:Vector3Array

def __init__(self, positions:np.ndarray, facings:np.ndarray):
def __init__(self, positions:Vector3Array, facings:Vector3Array):
self.positions = positions
self.facings = facings
def as_rays(self):
return [Ray(i, j) for i, j in zip(self.facings, self.positions)]
@property
def rays(self) -> list[Ray]:
return list(map(Ray, self.facings, self.positions))

class AUVPathGen:
mesh:TriangleMesh
Expand Down Expand Up @@ -50,10 +53,8 @@ def get_path(self, travel_distance:float, samples:int) -> AUVPath:
# point in the direction that the hull is sloping
rise = (ray_dist - prev_dist) * side_ray.direction
if not (np.isfinite(ray_dist) and np.isfinite(prev_dist)):
rise = np.zeros(3)
rise = Vector3(0, 0, 0)
new_facing = normalize(facings[-1] + rise)
facings.append(new_facing)

np_positions = np.array(positions)
np_facings = np.array(facings)
return AUVPath(np_positions, np_facings)
return AUVPath(Vector3Array(positions), Vector3Array(facings))
1 change: 1 addition & 0 deletions CustomTypes/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__/
11 changes: 11 additions & 0 deletions CustomTypes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from .custom_np import CustomNP as CustomNP
from .custom_types import (
Vector3 as Vector3,
Triangle as Triangle,
Ray as Ray,
)
from .custom_arrays import (
FloatArray as FloatArray,
TriangleArray as TriangleArray,
Vector3Array as Vector3Array,
)
54 changes: 54 additions & 0 deletions CustomTypes/custom_arrays.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from typing import Any, Iterable, Literal, overload
import numpy as np

from . import CustomNP, Triangle, Vector3

class FloatArray(CustomNP):
def __new__(cls,
content: Iterable[float]|np.ndarray|None = None,
):
if isinstance(content, np.ndarray):
if content.ndim != 1: raise ValueError(content.shape) # must be 1d
return content.view(cls)
if content is None:
return np.ndarray((0,), float).view(cls)
return np.array(content).view(cls)
def __getitem__(self, index):
if isinstance(index, slice): return super().__getitem__(index).view(type(self))
return super().__getitem__(index)

class Vector3Array(CustomNP):
def __new__(cls,
content: Iterable[Vector3]|np.ndarray|None = None,
):
if isinstance(content, np.ndarray):
if content.ndim == 1: content = content.reshape((1,) + content.shape)
if content.ndim != 2: raise ValueError(content.shape) # must be 1d or 2d
if content.shape[1] != 3: raise ValueError(content.shape) # must be shape (n, 3)
return content.view(cls)
if content is None:
return np.ndarray((0, 3), float).view(cls)
return np.array(content).view(cls)
def __getitem__(self, index):
if isinstance(index, slice): return super().__getitem__(index).view(type(self))
return super().__getitem__(index)

class TriangleArray(CustomNP):
def __new__(cls,
content: Iterable[Triangle]|np.ndarray|None = None,
):
if isinstance(content, np.ndarray):
if content.ndim == 2: content = content.reshape((1,) + content.shape)
if content.ndim != 3: raise ValueError(content.shape) # must be 2d or 3d
if content.shape[1] != 3 or content.shape[2] != 3: raise ValueError(content.shape) # must be shape (n, 3, 3)
return content.view(cls)
if content is None:
return np.ndarray((0, 3, 3), float).view(cls)
return np.array(content).view(cls)
def __getitem__(self, index):
if isinstance(index, slice): return super().__getitem__(index).view(type(self))
return super().__getitem__(index)
@property
def verticies(self) -> tuple[Vector3Array, Vector3Array, Vector3Array]:
v0, v1, v2 = self.swapaxes(0, 1)
return (v0.view(Vector3Array), v1.view(Vector3Array), v2.view(Vector3Array))
51 changes: 51 additions & 0 deletions CustomTypes/custom_arrays.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import numpy as np
from typing import Any, Iterable, Literal, Self, Type, TypeVar, overload

from . import CustomNP, Triangle, Vector3

class FloatArray(CustomNP[tuple[int, Literal[3]], np.dtype[np.float_]]):
@overload
def __new__(cls) -> FloatArray:...
@overload
def __new__(cls, content: Iterable[float]) -> FloatArray:...
@overload
def __new__(cls, content: np.ndarray[tuple[int], np.dtype[np.float_]]) -> FloatArray:...

@overload # type: ignore
def __getitem__(self, index:int) -> float: ...
@overload
def __getitem__(self, index:slice) -> Self: ...

class Vector3Array(CustomNP[tuple[int, Literal[3]], np.dtype[np.float_]]):
@overload
def __new__(cls) -> Vector3Array:...
@overload
def __new__(cls, content: Iterable[Vector3]) -> Vector3Array:...
@overload
def __new__(cls, content: np.ndarray[tuple[int, Literal[3]], np.dtype[np.float_]]) -> Vector3Array:...

@overload # type: ignore
def __getitem__(self, index:int) -> Vector3: ...
@overload
def __getitem__(self, index:tuple[int, Literal[0, 1, 2]]) -> float: ...
@overload
def __getitem__(self, index:slice) -> Self: ...

class TriangleArray(CustomNP[tuple[int, Literal[3], Literal[3]], np.dtype[np.float_]]):
@overload
def __new__(cls) -> TriangleArray:...
@overload
def __new__(cls, content: Iterable[Triangle]) -> TriangleArray:...
@overload
def __new__(cls, content: np.ndarray[tuple[int, Literal[3], Literal[3]], np.dtype[np.float_]]) -> TriangleArray:...

@overload # type: ignore
def __getitem__(self, index:tuple[int, Literal[0, 1, 2]]) -> Vector3: ...
@overload
def __getitem__(self, index:tuple[int, Literal[0, 1, 2], Literal[0, 1, 2]]) -> float: ...
@overload
def __getitem__(self, index:slice) -> Self: ...

@property
def verticies(self) -> tuple[Vector3Array, Vector3Array, Vector3Array]: ...

21 changes: 21 additions & 0 deletions CustomTypes/custom_np.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

from typing import Generic, TypeVar, Self
import numpy as np


_S = TypeVar('_S', bound=tuple)
_T = TypeVar('_T', bound=np.dtype)
class CustomNP(np.ndarray[_S, _T]):
def __add__(self, other):
return super().__add__(other).view(type(self))
def __sub__(self, other):
return super().__sub__(other).view(type(self))
def __mul__(self, other):
return super().__mul__(other).view(type(self))
def __truediv__(self, other):
return super().__truediv__(other).view(type(self))
def __rtruediv__(self, other):
return super().__rtruediv__(other).view(type(self))
@property
def nd(self):
return self.view(np.ndarray)
15 changes: 15 additions & 0 deletions CustomTypes/custom_np.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from typing import TypeVar, Self
import numpy as np


_S = TypeVar('_S', bound=tuple)
_T = TypeVar('_T', bound=np.dtype)
class CustomNP(np.ndarray[_S, _T]):
def __add__(self, other:Self) -> Self: ... # type: ignore
def __sub__(self, other:Self) -> Self: ... # type: ignore
def __mul__(self, other:Self|float) -> Self: ... # type: ignore
def __truediv__(self, other:Self|float) -> Self: ... # type: ignore
def __rtruediv__(self, other:float) -> Self: ... # type: ignore

@property
def nd(self) -> np.ndarray: ...
42 changes: 42 additions & 0 deletions CustomTypes/custom_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from typing import Any, Iterable, Literal, Self, overload
import numpy as np
from . import CustomNP

class Vector3(CustomNP):
def __new__(cls,
x: float | tuple[float, float, float] | Self, y = 0, z = 0):
if isinstance(x, tuple):
x, y, z = x
elif isinstance(x, Vector3):
x, y, z = x
assert isinstance(x, (float, int))
return np.array([x, y, z]).view(cls)
def __str__(self):
return f"({self[0]}, {self[1]}, {self[2]})"
def __repr__(self):
return f"Vector3({self[0]}, {self[1]}, {self[2]})"

class Triangle(CustomNP):
def __new__(cls,
v0: Vector3 | tuple[Vector3, Vector3, Vector3],
v1: Vector3 = Vector3(0, 0, 0),
v2: Vector3 = Vector3(0, 0, 0),
) -> 'Triangle':
if isinstance(v0, tuple):
v0, v1, v2 = v0
return np.array([v0, v1, v2]).view(cls)

def __str__(self) -> str:
return f"({self[0]}, {self[1]}, {self[2]})"
def __repr__(self) -> str:
return f"Vector3({self[0]}, {self[1]}, {self[2]})"

class Ray:
direction:Vector3
origin:Vector3
def __init__(self,
direction:Vector3,
origin:Vector3,
) -> None:
self.direction = direction
self.origin = origin
40 changes: 40 additions & 0 deletions CustomTypes/custom_types.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from typing import Any, Iterable, Iterator, Literal, TypeAlias, TypeVar, overload
import numpy as np
from . import CustomNP

class Vector3(CustomNP[tuple[Literal[3]], np.dtype[np.float_]]):
@overload
def __new__(cls, x: float, y: float, z: float) -> Vector3: ...
@overload
def __new__(cls, x: tuple[float, float, float]) -> Vector3: ...
@overload
def __new__(cls, x: Vector3) -> Vector3: ...

def __getitem__(self, index:Literal[0, 1, 2]) -> float: ... # type: ignore

class Triangle(CustomNP[tuple[Literal[3], Literal[3]], np.dtype[np.float_]], Iterable[Vector3]):
@overload
def __new__(cls, v0: Vector3, v1: Vector3, v2: Vector3) -> Triangle: ...
@overload
def __new__(cls, v0: tuple[Vector3, Vector3, Vector3]) -> Triangle: ...
@overload
def __new__(cls, v0: Triangle) -> Triangle: ...

@overload # type: ignore
def __getitem__(self, index:Literal[0, 1, 2]) -> Vector3: ...
@overload
def __getitem__(self, index:tuple[Literal[0, 1, 2], Literal[0, 1, 2]]) -> float: ...

def __add__(self, other:Triangle) -> Triangle: ... # type: ignore
def __sub__(self, other:Triangle) -> Triangle: ... # type: ignore

def __iter__(self) -> Iterator[Vector3]: ...
class Ray:
direction:Vector3
origin:Vector3
def __init__(self,
direction:Vector3,
origin:Vector3,
) -> None:
self.direction = direction
self.origin = origin
16 changes: 8 additions & 8 deletions Raytrace/Examples/ExampleMultipleSonarReadings.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import sys; sys.path.append('../..')\n",
"from Raytrace.TriangleMesh import Ray\n",
"from Raytrace.BVHMesh import BVHMesh\n",
"from CustomTypes import Ray, Vector3, Vector3Array\n",
"from Raytrace import BVHMesh\n",
"from Raytrace.SideScan import SideScan, ScanReading\n",
"import numpy as np\n",
"import time"
Expand All @@ -34,18 +34,18 @@
"metadata": {},
"outputs": [],
"source": [
"facing = np.array([1, 0, 0])\n",
"facing = Vector3(1, 0, 0)\n",
"min_angle = 0\n",
"max_angle = -np.pi/2\n",
"sample_ray_count = 1000\n",
"\n",
"origin_start = np.array([5, -1, 1])\n",
"origin_end = np.array([9, -1, 1])\n",
"origin_start = Vector3(5, -1, 1)\n",
"origin_end = Vector3(9, -1, 1)\n",
"readings_count = 100\n",
"# liniar interpolation between the two for this simple demo\n",
"origins = [origin_start * (1-i) + origin_end * i for i in np.arange(0, 1+1/(readings_count-1)/2, 1/(readings_count-1))]\n",
"origins = Vector3Array([origin_start * (1-i) + origin_end * i for i in np.arange(0, 1+1/(readings_count-1)/2, 1/(readings_count-1))])\n",
"\n",
"orientations = [Ray(facing, origin) for origin in origins]\n",
"orientations = list(map(Ray, [facing] * readings_count, origins))\n",
"\n",
"rays_list = [SideScan.generate_rays(orientation, min_angle, max_angle, sample_ray_count) for orientation in orientations]\n",
"\n",
Expand Down
15 changes: 8 additions & 7 deletions Raytrace/Examples/ExampleSingleSonarReading.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@
"cells": [
{
"cell_type": "code",
"execution_count": 2,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import sys; sys.path.append('../..')\n",
"from Raytrace.TriangleMesh import Ray\n",
"from Raytrace.BVHMesh import BVHMesh\n",
"from CustomTypes import Ray\n",
"from Raytrace import BVHMesh\n",
"from Raytrace.SideScan import SideScan, ScanReading\n",
"import numpy as np\n",
"import time"
"import time\n",
"from CustomTypes import Vector3"
]
},
{
Expand All @@ -34,8 +35,8 @@
"metadata": {},
"outputs": [],
"source": [
"origin = np.array([5, -1, 1])\n",
"facing = np.array([1, 0, 0])\n",
"origin = Vector3(5, -1, 1)\n",
"facing = Vector3(1, 0, 0)\n",
"min_angle = 0\n",
"max_angle = -np.pi/2\n",
"sample_ray_count = 1000\n",
Expand All @@ -47,7 +48,7 @@
"raw_reading = SideScan(mesh).scan_rays(rays)\n",
"reading = ScanReading(raw_reading)\n",
"\n",
"print('Triangles:', mesh.triangles.shape[0])\n",
"print('Triangles:', len(mesh.triangles))\n",
"raw_reading.print_summary()"
]
},
Expand Down
3 changes: 3 additions & 0 deletions Raytrace/Meshs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .triangle_mesh import TriangleMesh as TriangleMesh
from .bvh_mesh import BVHMesh as BVHMesh
from .composite_mesh import CompositeMesh as CompositeMesh
Loading