Skip to content

Commit 5150860

Browse files
committed
fix: making allow bbox to do rescaling
1 parent af98144 commit 5150860

File tree

1 file changed

+111
-10
lines changed

1 file changed

+111
-10
lines changed

LoopStructural/datatypes/_bounding_box.py

Lines changed: 111 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ def __init__(
1010
self,
1111
origin: Optional[np.ndarray] = None,
1212
maximum: Optional[np.ndarray] = None,
13+
global_origin: Optional[np.ndarray] = None,
1314
nsteps: Optional[np.ndarray] = None,
1415
step_vector: Optional[np.ndarray] = None,
1516
dimensions: int = 3,
@@ -28,13 +29,25 @@ def __init__(
2829
nsteps : Optional[np.ndarray], optional
2930
_description_, by default None
3031
"""
32+
# reproject relative to the global origin, if origin is not provided.
33+
# we want the local coordinates to start at 0
34+
# otherwise uses provided origin. This is useful for having multiple bounding boxes rela
35+
if global_origin is not None and origin is None:
36+
origin = np.zeros(3)
37+
3138
if maximum is None and nsteps is not None and step_vector is not None:
3239
maximum = origin + nsteps * step_vector
40+
3341
self._origin = np.array(origin)
3442
self._maximum = np.array(maximum)
43+
if global_origin is None:
44+
global_origin = np.zeros(3)
45+
46+
self._global_origin = global_origin
3547
self.dimensions = dimensions
36-
if nsteps is None:
37-
self.nsteps = np.array([50, 50, 25])
48+
self.nsteps = np.array([50, 50, 25])
49+
if nsteps is not None:
50+
self.nsteps = np.array(nsteps)
3851
self.name_map = {
3952
"xmin": (0, 0),
4053
"ymin": (0, 1),
@@ -54,11 +67,15 @@ def __init__(
5467

5568
@property
5669
def global_origin(self):
57-
return self._origin
70+
return self._global_origin
71+
72+
@global_origin.setter
73+
def global_origin(self, global_origin):
74+
self._global_origin = global_origin
5875

5976
@property
6077
def global_maximum(self):
61-
return self._maximum
78+
return self.maximum - self.origin + self._global_origin
6279

6380
@property
6481
def valid(self):
@@ -108,7 +125,7 @@ def nelements(self, nelements):
108125
self.nsteps = nsteps
109126

110127
@property
111-
def corners(self):
128+
def corners(self) -> np.ndarray:
112129
"""Returns the corners of the bounding box
113130
114131
@@ -130,6 +147,29 @@ def corners(self):
130147
]
131148
)
132149

150+
@property
151+
def corners_global(self) -> np.ndarray:
152+
"""Returns the corners of the bounding box
153+
154+
155+
Returns
156+
-------
157+
np.ndarray
158+
corners of the bounding box
159+
"""
160+
return np.array(
161+
[
162+
self.global_origin.tolist(),
163+
[self.global_maximum[0], self.global_origin[1], self.global_origin[2]],
164+
[self.global_maximum[0], self.global_maximum[1], self.global_origin[2]],
165+
[self.global_origin[0], self.global_maximum[1], self.global_origin[2]],
166+
[self.global_origin[0], self.global_origin[1], self.global_maximum[2]],
167+
[self.global_maximum[0], self.global_origin[1], self.global_maximum[2]],
168+
self.global_maximum.tolist(),
169+
[self.global_origin[0], self.global_maximum[1], self.global_maximum[2]],
170+
]
171+
)
172+
133173
@property
134174
def step_vector(self):
135175
return (self.maximum - self.origin) / self.nsteps
@@ -138,21 +178,67 @@ def step_vector(self):
138178
def length(self):
139179
return self.maximum - self.origin
140180

141-
def fit(self, locations: np.ndarray):
181+
def fit(self, locations: np.ndarray, local_coordinate: bool = False) -> BoundingBox:
182+
"""Initialise the bounding box from a set of points.
183+
184+
Parameters
185+
----------
186+
locations : np.ndarray
187+
xyz locations of the points to fit the bbox
188+
local_coordinate : bool, optional
189+
whether to set the origin to [0,0,0], by default False
190+
191+
Returns
192+
-------
193+
BoundingBox
194+
A reference to the bounding box object, note this is not a new bounding box
195+
it updates the current one in place.
196+
197+
Raises
198+
------
199+
LoopValueError
200+
_description_
201+
"""
142202
if locations.shape[1] != self.dimensions:
143203
raise LoopValueError(
144204
f"locations array is {locations.shape[1]}D but bounding box is {self.dimensions}"
145205
)
146-
self.origin = locations.min(axis=0)
147-
self.maximum = locations.max(axis=0)
206+
origin = locations.min(axis=0)
207+
maximum = locations.max(axis=0)
208+
if local_coordinate:
209+
self.global_origin = origin
210+
self.origin = np.zeros(3)
211+
self.maximum = maximum - origin
212+
else:
213+
self.origin = origin
214+
self.maximum = maximum
215+
self.global_origin = np.zeros(3)
148216
return self
149217

150218
def with_buffer(self, buffer: float = 0.2) -> BoundingBox:
219+
"""Create a new bounding box with a buffer around the existing bounding box
220+
221+
Parameters
222+
----------
223+
buffer : float, optional
224+
percentage to expand the dimensions by, by default 0.2
225+
226+
Returns
227+
-------
228+
BoundingBox
229+
The new bounding box object.
230+
231+
Raises
232+
------
233+
LoopValueError
234+
if the current bounding box is invalid
235+
"""
151236
if self.origin is None or self.maximum is None:
152237
raise LoopValueError("Cannot create bounding box with buffer, no origin or maximum")
238+
# local coordinates, rescale into the original bounding boxes global coordinates
153239
origin = self.origin - buffer * (self.maximum - self.origin)
154240
maximum = self.maximum + buffer * (self.maximum - self.origin)
155-
return BoundingBox(origin=origin, maximum=maximum)
241+
return BoundingBox(origin=origin, maximum=maximum, global_origin=self.global_origin)
156242

157243
def get_value(self, name):
158244
ix, iy = self.name_map.get(name, (-1, -1))
@@ -187,12 +273,16 @@ def is_inside(self, xyz):
187273
inside = np.logical_and(inside, xyz[:, 2] < self.maximum[2])
188274
return inside
189275

190-
def regular_grid(self, nsteps=None, shuffle=False, order="C"):
276+
def regular_grid(self, nsteps=None, shuffle=False, order="C", local=True):
191277
if nsteps is None:
192278
nsteps = self.nsteps
193279
x = np.linspace(self.origin[0], self.maximum[0], nsteps[0])
194280
y = np.linspace(self.origin[1], self.maximum[1], nsteps[1])
195281
z = np.linspace(self.origin[2], self.maximum[2], nsteps[2])
282+
if not local:
283+
x = np.linspace(self.global_origin[0], self.global_maximum[0], nsteps[0])
284+
y = np.linspace(self.global_origin[1], self.global_maximum[1], nsteps[1])
285+
z = np.linspace(self.global_origin[2], self.global_maximum[2], nsteps[2])
196286
xx, yy, zz = np.meshgrid(x, y, z, indexing="ij")
197287
locs = np.array(
198288
[xx.flatten(order=order), yy.flatten(order=order), zz.flatten(order=order)]
@@ -202,6 +292,13 @@ def regular_grid(self, nsteps=None, shuffle=False, order="C"):
202292
rng.shuffle(locs)
203293
return locs
204294

295+
def to_dict(self):
296+
return {
297+
"origin": self.origin.tolist(),
298+
"maximum": self.maximum.tolist(),
299+
"nsteps": self.nsteps.tolist(),
300+
}
301+
205302
@property
206303
def vtk(self):
207304

@@ -217,3 +314,7 @@ def vtk(self):
217314
y,
218315
z,
219316
)
317+
318+
@property
319+
def structured_grid(self):
320+
pass

0 commit comments

Comments
 (0)