Skip to content

Commit

Permalink
PR fixes.
Browse files Browse the repository at this point in the history
  • Loading branch information
Eric-Vin committed Jan 11, 2024
1 parent 84071ae commit 7738224
Show file tree
Hide file tree
Showing 7 changed files with 40 additions and 69 deletions.
49 changes: 18 additions & 31 deletions src/scenic/core/object_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1257,12 +1257,6 @@ def boundingBox(self):
def inradius(self):
"""A lower bound on the inradius of this object"""

# Define a helper function that computes the actual inradius
def inradiusActual(width, length, height, shape):
return MeshVolumeRegion(
mesh=shape.mesh, dimensions=(width, length, height)
).inradius

# Define a helper function that computes the support of the inradius,
# given the sub supports.
def inradiusSupport(width_s, length_s, height_s, shape_s):
Expand Down Expand Up @@ -1310,14 +1304,15 @@ def inradiusSupport(width_s, length_s, height_s, shape_s):

return distance_range

# Return either the inradius or a FunctionDistribution using the above helpers
args = toDistribution((self.width, self.length, self.height, self.shape))
if not isLazy(args):
return inradiusActual(*args)
else:
return FunctionDistribution(
args=args, kwargs={}, func=inradiusActual, support=inradiusSupport
)
# Define a helper function that computes the actual inradius
@distributionFunction(support=inradiusSupport)
def inradiusActual(width, length, height, shape):
return MeshVolumeRegion(
mesh=shape.mesh, dimensions=(width, length, height)
).inradius

# Return the inradius (possibly a distribution) with proper support information
return inradiusActual(self.width, self.length, self.height, self.shape)

@cached_property
def planarInradius(self):
Expand All @@ -1328,12 +1323,6 @@ def planarInradius(self):
roll are both 0.
"""

# Define a helper function that computes the actual planarInradius
def planarInradiusActual(width, length, shape):
return MeshVolumeRegion(
mesh=shape.mesh, dimensions=(width, length, 1)
).boundingPolygon.inradius

# Define a helper function that computes the support of the inradius,
# given the sub supports.
def planarInradiusSupport(width_s, length_s, shape_s):
Expand Down Expand Up @@ -1377,17 +1366,15 @@ def planarInradiusSupport(width_s, length_s, shape_s):

return distance_range

# Return either the inradius or a FunctionDistribution using the above helpers
args = toDistribution((self.width, self.length, self.shape))
if not isLazy(args):
return planarInradiusActual(*args)
else:
return FunctionDistribution(
args=args,
kwargs={},
func=planarInradiusActual,
support=planarInradiusSupport,
)
# Define a helper function that computes the actual planarInradius
@distributionFunction(support=planarInradiusSupport)
def planarInradiusActual(width, length, shape):
return MeshVolumeRegion(
mesh=shape.mesh, dimensions=(width, length, 1)
).boundingPolygon.inradius

# Return the planar inradius (possibly a distribution) with proper support information
return planarInradiusActual(self.width, self.length, self.shape)

@cached_property
def surface(self):
Expand Down
2 changes: 2 additions & 0 deletions src/scenic/core/pruning.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ def matchPolygonalField(heading, position):


### Pruning procedures


def prune(scenario, verbosity=1):
"""Prune a `Scenario`, removing infeasible parts of the space.
Expand Down
39 changes: 12 additions & 27 deletions src/scenic/core/regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1418,7 +1418,6 @@ def intersect(self, other, triedReversed=False):
# Other region is a mesh volume. We can extract the mesh to perform boolean operations on it
other_mesh = other.mesh

# If dimensions are prov
# Compute intersection using Trimesh
try:
new_mesh = self.mesh.intersection(other_mesh, engine=self.engine)
Expand Down Expand Up @@ -2066,14 +2065,7 @@ class VoxelRegion(Region):
"""Region represented by a voxel grid in 3D space.
Args:
encoding: A numpy array encoding the voxel grid.
dimensions: An optional 3-tuple, with the values representing width, length, height
respectively. The voxel region will be scaled to have these dimensions. Default
value (1,1,1).
position: A position, which determines where the center of the region will be. Default
value is the origin, (0,0,0).
voxelGrid: Optionally, just pass in the final trimesh voxelGrid to be used. Passing this
parameter will result in encoding, dimensions, and position being ignored.
voxelGrid: The Trimesh voxelGrid to be used.
orientation: An optional vector field describing the preferred orientation at every point in
the region.
name: An optional name to help with debugging.
Expand All @@ -2085,27 +2077,21 @@ def __init__(self, voxelGrid, orientation=None, name=None, lazy=False):
# Initialize superclass
super().__init__(name, orientation=orientation)

# Work around Trimesh caching bug
self._voxelGrid = trimesh.voxel.VoxelGrid(
voxelGrid.encoding, transform=voxelGrid.transform.copy()
)

# Check that the encoding isn't empty. In that case, raise an error.
if self._voxelGrid.encoding.is_empty:
if voxelGrid.encoding.is_empty:
raise ValueError("Tried to create an empty VoxelRegion.")

# Transform voxel grid points and extract scale
self.voxel_points = self._voxelGrid.points
self.scale = self._voxelGrid.scale
# Store voxel grid and extract points and scale
self.voxelGrid = trimesh.voxel.VoxelGrid(
voxelGrid.encoding, transform=voxelGrid.transform.copy()
)
self.voxel_points = self.voxelGrid.points
self.scale = self.voxelGrid.scale

# Initialize KD-Tree for containment checking if not lazy
if not lazy:
self.kdTree

@cached_property
def voxelGrid(self):
return self._voxelGrid

@cached_property
def kdTree(self):
return scipy.spatial.KDTree(self.voxel_points)
Expand All @@ -2114,16 +2100,14 @@ def containsPoint(self, point):
point = toVector(point)

# Find closest voxel point
_, index = self.kdTree.query([point])
_, index = self.kdTree.query(point)
closest_point = self.voxel_points[index]

# Check voxel containment
voxel_low = closest_point - self.scale / 2
voxel_high = closest_point + self.scale / 2

return numpy.all(voxel_low <= point, axis=1) & numpy.all(
point <= voxel_high, axis=1
)
return numpy.all(voxel_low <= point) & numpy.all(point <= voxel_high)

def containsObject(self, obj):
raise NotImplementedError
Expand All @@ -2142,10 +2126,11 @@ def uniformPointInner(self):
# equal to scale, centered at the origin.
base_pt = numpy.random.random_sample(3) - 0.5
scaled_pt = base_pt * self.scale

# Pick a random voxel point and add it to base_pt.
voxel_base = self.voxel_points[random.randrange(len(self.voxel_points))]
offset_pt = voxel_base + scaled_pt

# Then pick a random voxel point and add the base point to that point.
return Vector(*offset_pt)

def dilation(self, iterations, structure=None):
Expand Down
1 change: 0 additions & 1 deletion src/scenic/core/scenarios.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,6 @@ def _generateInner(self, maxIterations, verbosity, feedback):
numpy.random.set_state(np_state)

if rejection is not None:
# breakpoint()
optionallyDebugRejection()

# obtained a valid sample; assemble a scene from it
Expand Down
3 changes: 1 addition & 2 deletions tests/core/test_regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,8 +574,7 @@ def test_voxel_region():

def test_mesh_voxelization(getAssetPath):
plane_region = MeshVolumeRegion.fromFile(getAssetPath("meshes/classic_plane.obj.bz2"))
voxel_grid = plane_region.mesh.voxelized(max(plane_region.mesh.extents) / 100).fill()
vr = VoxelRegion(voxelGrid=voxel_grid)
vr = plane_region.voxelized(max(plane_region.mesh.extents) / 100)

for sampled_pt in trimesh.sample.volume_mesh(plane_region.mesh, 100):
assert vr.containsPoint(sampled_pt)
Expand Down
1 change: 0 additions & 1 deletion tests/syntax/test_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,6 @@ def test_object_inradius():
"""
)
ego = sampleEgo(scenario)
assert isinstance(scenario.objects[0].inradius, Distribution)
assert supportInterval(scenario.objects[0].inradius) == (0, 0)
assert ego.inradius == 0

Expand Down
14 changes: 7 additions & 7 deletions tests/syntax/test_pruning.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class TestObject:
baseOffset: (0.1, 0, self.height/2)
workspace = Workspace(PolygonalRegion([0@0, 2@0, 2@2, 0@2]))
ego = new Object in workspace, with height Range(0.1,0.5)
ego = new TestObject on workspace, with height Range(0.1,0.5)
"""
)
# Sampling should fail ~30.56% of the time, so
Expand Down Expand Up @@ -166,23 +166,23 @@ def test_visibility_pruning():
The following scenarios are equivalent except for how they specify that foo
must be visible from ego. The size of the workspace and the visibleDistance
of ego are chosen such that without pruning the chance of sampling a valid
scene over 100 tries is 1-(1-Decimal(3.14)/Decimal(10000000000**2))**100 = ~1e-18.
scene over 100 tries is 1-(1-Decimal(3.14)/Decimal(1e10**2))**100 = ~1e-18.
Assuming the approximately buffered volume of the viewRegion has a 50% chance of
ejecting (i.e. it is twice as large as the true buffered viewRegion, which testing
rejecting (i.e. it is twice as large as the true buffered viewRegion, which testing
indicates in this case has about a 10% increase in volume for this case), the chance
of not finding a sample in 100 iterations is 1e-31.
We also want to confirm that we aren't pruning too much, i.e. placing the position
in the viewRegion instead of at any point where the object intersects the view region.
Because of this, we want to see at least one sample where the position is outside
the viewRegion but the object intersects the viewRegion. The chance of this happening
is 1-((4/3)*math.pi*(1)**3)/((4/3)*math.pi*(1.1)**3) = ~25%, so by repeating the process
30 times we have a 1e-19 chance of not gettin a single point in this zone.
per sample is 1 - (1 / 1.1)**3 = ~25%, so by repeating the process 30 times we have
a 1e-19 chance of not getting a single point in this zone.
"""
# requireVisible
scenario = compileScenic(
"""
workspace = Workspace(RectangularRegion(0@0, 0, 10000000000, 10000000000))
workspace = Workspace(RectangularRegion(0@0, 0, 1e10, 1e10))
ego = new Object at (0,0,0), with visibleDistance 1
foo = new Object in workspace, with requireVisible True,
with shape SpheroidShape(dimensions=(0.2,0.2,0.2))
Expand All @@ -196,7 +196,7 @@ def test_visibility_pruning():
# visible
scenario = compileScenic(
"""
workspace = Workspace(RectangularRegion(0@0, 0, 10000000000, 10000000000))
workspace = Workspace(RectangularRegion(0@0, 0, 1e10, 1e10))
ego = new Object at (0,0,0), with visibleDistance 1
foo = new Object in workspace, visible,
with shape SpheroidShape(dimensions=(0.2,0.2,0.2))
Expand Down

0 comments on commit 7738224

Please sign in to comment.