simetri.graphics.bbox

Bounding box class. Shape and Batch objects have a bounding box. Bounding box is axis-aligned. Provides reference edges and points.

  1"""Bounding box class. Shape and Batch objects have a bounding box.
  2Bounding box is axis-aligned. Provides reference edges and points.
  3"""
  4import warnings
  5
  6import numpy as np
  7from .common import Point, common_properties, defaults
  8from .all_enums import Side, Types, Anchor
  9from ..geometry.geometry import (
 10    distance,
 11    mid_point,
 12    offset_line,
 13    line_angle,
 14    intersect,
 15    positive_angle,
 16    polar_to_cartesian
 17)
 18
 19
 20class BoundingBox:
 21    """Rectangular bounding box.
 22    If the object is a Shape, it contains all points.
 23    If the object is a Batch, it contains all points of all Shapes.
 24
 25    Provides reference edges and points as shown in the Book page ???.
 26    """
 27
 28    def __init__(self, southwest: Point, northeast: Point):
 29        """
 30        Initialize a BoundingBox object.
 31
 32        Args:
 33            southwest (Point): The southwest corner of the bounding box.
 34            northeast (Point): The northeast corner of the bounding box.
 35        """
 36        # define the four corners
 37        self.__dict__["southwest"] = southwest
 38        self.__dict__["northeast"] = northeast
 39        self.__dict__["northwest"] = (southwest[0], northeast[1])
 40        self.__dict__["southeast"] = (northeast[0], southwest[1])
 41        self._aliases = {
 42            "s": "south",
 43            "n": "north",
 44            "w": "west",
 45            "e": "east",
 46            "sw": "southwest",
 47            "se": "southeast",
 48            "nw": "northwest",
 49            "ne": "northeast",
 50            "d1": "diagonal1",
 51            "d2": "diagonal2",
 52            "m": "midpoint",
 53            "vcl": "vert_centerline",
 54            "hcl": "horiz_centerline",
 55            "center": "midpoint",
 56        }
 57
 58        common_properties(self)
 59        self.type = Types.BOUNDING_BOX
 60        self.subtype = Types.BOUNDING_BOX
 61
 62    def __getattr__(self, name):
 63        """
 64        Get the attribute with the given name.
 65
 66        Args:
 67            name (str): The name of the attribute.
 68
 69        Returns:
 70            Any: The attribute with the given name.
 71        """
 72        if name in self._aliases:
 73            if name == "center":
 74                warnings.warn('"center" is deprecated use "midpoint" instead.', DeprecationWarning)
 75            res = getattr(self, self._aliases[name])
 76        else:
 77            res = self.__dict__[name]
 78        return res
 79
 80    def angle_point(self, angle: float) -> float:
 81        """
 82        Return the intersection point of the angled line starting
 83        from the midpoint and the bounding box. angle is in radians.
 84
 85        Args:
 86            angle (float): The angle in radians.
 87
 88        Returns:
 89            float: The intersection point.
 90        """
 91        angle = positive_angle(angle)
 92        line = ((0, 0), (np.cos(angle), np.sin(angle)))
 93
 94        angle1 = line_angle(self.midpoint, self.northeast)
 95        angle2 = -angle1  # midpoint, southeast
 96        angle3 = np.pi - angle1  # midpoint, northwest
 97        angle4 = -angle3  # midpoint, southwest
 98        if angle3 >= angle >= angle1:
 99            res = intersect(line, self.top)
100        elif angle4 <= angle <= angle2:
101            res = intersect(line, self.bottom)
102        elif angle1 <= angle <= angle2:
103            res = intersect(line, self.right)
104        else:
105            res = intersect(line, self.left)
106
107        return res
108
109    @property
110    def left(self):
111        """
112        Return the left edge.
113
114        Returns:
115            tuple: The left edge.
116        """
117        return (self.northwest, self.southwest)
118
119    @property
120    def right(self):
121        """
122        Return the right edge.
123
124        Returns:
125            tuple: The right edge.
126        """
127        return (self.northeast, self.southeast)
128
129    @property
130    def top(self):
131        """
132        Return the top edge.
133
134        Returns:
135            tuple: The top edge.
136        """
137        return (self.northwest, self.northeast)
138
139    @property
140    def bottom(self):
141        """
142        Return the bottom edge.
143
144        Returns:
145            tuple: The bottom edge.
146        """
147        return (self.southwest, self.southeast)
148
149    @property
150    def vert_centerline(self):
151        """
152        Return the vertical centerline.
153
154        Returns:
155            tuple: The vertical centerline.
156        """
157        return (self.north, self.south)
158
159    @property
160    def horiz_centerline(self):
161        """
162        Return the horizontal centerline.
163
164        Returns:
165            tuple: The horizontal centerline.
166        """
167        return (self.west, self.east)
168
169    @property
170    def midpoint(self):
171        """
172        Return the center of the bounding box.
173
174        Returns:
175            tuple: The center of the bounding box.
176        """
177        x1, y1 = self.southwest
178        x2, y2 = self.northeast
179
180        xc = (x1 + x2) / 2
181        yc = (y1 + y2) / 2
182
183        return (xc, yc)
184
185    @property
186    def corners(self):
187        """
188        Return the four corners of the bounding box.
189
190        Returns:
191            tuple: The four corners of the bounding box.
192        """
193        return (self.northwest, self.southwest, self.southeast, self.northeast)
194
195    @property
196    def diamond(self):
197        """
198        Return the four center points of the bounding box in a diamond shape.
199
200        Returns:
201            tuple: The four center points of the bounding box in a diamond shape.
202        """
203        return (self.north, self.west, self.south, self.east)
204
205    @property
206    def all_anchors(self):
207        """
208        Return all anchors of the bounding box.
209
210        Returns:
211            tuple: All anchors of the bounding box.
212        """
213        return (
214            self.northwest,
215            self.west,
216            self.southwest,
217            self.south,
218            self.northeast,
219            self.east,
220            self.northeast,
221            self.north,
222            self.midpoint,
223        )
224
225    @property
226    def width(self):
227        """
228        Return the width of the bounding box.
229
230        Returns:
231            float: The width of the bounding box.
232        """
233        return distance(self.northwest, self.northeast)
234
235    @property
236    def height(self):
237        """
238        Return the height of the bounding box.
239
240        Returns:
241            float: The height of the bounding box.
242        """
243        return distance(self.northwest, self.southwest)
244
245    @property
246    def size(self):
247        """
248        Return the size of the bounding box.
249
250        Returns:
251            tuple: The size of the bounding box.
252        """
253        return (self.width, self.height)
254
255    @property
256    def west(self):
257        """
258        Return the left edge midpoint.
259
260        Returns:
261            tuple: The left edge midpoint.
262        """
263        return mid_point(*self.left)
264
265    @property
266    def south(self):
267        """
268        Return the bottom edge midpoint.
269
270        Returns:
271            tuple: The bottom edge midpoint.
272        """
273        return mid_point(*self.bottom)
274
275    @property
276    def east(self):
277        """
278        Return the right edge midpoint.
279
280        Returns:
281            tuple: The right edge midpoint.
282        """
283        return mid_point(*self.right)
284
285    @property
286    def north(self):
287        """
288        Return the top edge midpoint.
289
290        Returns:
291            tuple: The top edge midpoint.
292        """
293        return mid_point(*self.top)
294
295    @property
296    def northwest(self):
297        """
298        Return the top left corner.
299
300        Returns:
301            tuple: The top left corner.
302        """
303        return self.__dict__["northwest"]
304
305    @property
306    def northeast(self):
307        """
308        Return the top right corner.
309
310        Returns:
311            tuple: The top right corner.
312        """
313        return self.__dict__["northeast"]
314
315    @property
316    def southwest(self):
317        """
318        Return the bottom left corner.
319
320        Returns:
321            tuple: The bottom left corner.
322        """
323        return self.__dict__["southwest"]
324
325    @property
326    def southeast(self):
327        """
328        Return the bottom right corner.
329
330        Returns:
331            tuple: The bottom right corner.
332        """
333        return self.__dict__["southeast"]
334
335    @property
336    def diagonal1(self):
337        """
338        Return the first diagonal. From the top left to the bottom right.
339
340        Returns:
341            tuple: The first diagonal.
342        """
343        return (self.southwest, self.northeast)
344
345    @property
346    def diagonal2(self):
347        """
348        Return the second diagonal. From the top right to the bottom left.
349
350        Returns:
351            tuple: The second diagonal.
352        """
353        return (self.southeast, self.northwest)
354
355    def get_inflated_b_box(
356        self, left_margin=None, bottom_margin=None, right_margin=None, top_margin=None
357    ):
358        """
359        Return a bounding box with offset edges.
360
361        Args:
362            left_margin (float, optional): The left margin.
363            bottom_margin (float, optional): The bottom margin.
364            right_margin (float, optional): The right margin.
365            top_margin (float, optional): The top margin.
366
367        Returns:
368            BoundingBox: The inflated bounding box.
369        """
370
371        if bottom_margin is None:
372            bottom_margin = left_margin
373        if right_margin is None:
374            right_margin = left_margin
375        if top_margin is None:
376            top_margin = bottom_margin
377
378        x, y = self.southwest[:2]
379        southwest = (x - left_margin, y - bottom_margin)
380
381        x, y = self.northeast[:2]
382        northeast = (x + right_margin, y + top_margin)
383
384        return BoundingBox(southwest, northeast)
385
386    def offset_line(self, side, offset):
387        """
388        Offset is applied outwards. Use negative values for inward offset.
389
390        Args:
391            side (Side): The side to offset.
392            offset (float): The offset distance.
393
394        Returns:
395            tuple: The offset line.
396        """
397        if isinstance(side, str):
398            side = Side[side.upper()]
399
400        if side == Side.RIGHT:
401            x1, y1 = self.southeast
402            x2, y2 = self.northeast
403            res = ((x1 + offset, y1), (x2 + offset, y2))
404        elif side == Side.LEFT:
405            x1, y1 = self.southwest
406            x2, y2 = self.northwest
407            res = ((x1 - offset, y1), (x2 - offset, y2))
408        elif side == Side.TOP:
409            x1, y1 = self.northwest
410            x2, y2 = self.northeast
411            res = ((x1, y1 + offset), (x2, y2 + offset))
412        elif side == Side.BOTTOM:
413            x1, y1 = self.southwest
414            x2, y2 = self.southeast
415            res = ((x1, y1 - offset), (x2, y2 - offset))
416        elif side == Side.DIAGONAL1:
417            res = offset_line(self.diagonal1, offset)
418        elif side == Side.DIAGONAL2:
419            res = offset_line(self.diagonal2, offset)
420        elif side == Side.H_CENTERLINE:
421            res = offset_line(self.horiz_center_line, offset)
422        elif side == Side.V_CENTERLINE:
423            res = offset_line(self.vert_center_line, offset)
424        else:
425            raise ValueError(f"Unknown side: {side}")
426            res = None
427
428        return res
429
430    def offset_point(self, anchor, dx, dy):
431        """
432        Return an offset point from the given corner.
433
434        Args:
435            anchor (Anchor): The anchor point.
436            dx (float): The x offset.
437            dy (float): The y offset.
438
439        Returns:
440            list: The offset point.
441        """
442        if isinstance(anchor, str):
443            anchor = Anchor[anchor.upper()]
444            x, y = getattr(self, anchor.value)[:2]
445        elif isinstance(anchor, Anchor):
446            x, y = anchor.value[:2]
447        else:
448            raise ValueError(f"Unknown anchor: {anchor}")
449        return [x + dx, y + dy]
450
451
452    def centered(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
453        """
454        Get the center of the reference item.
455
456        Args:
457            item (object): The reference item. Shape or Batch.
458            dx (float): The x offset.
459            dy (float): The y offset.
460
461        Returns:
462            Point: The item.midpoint of the reference item's bounding-box.
463        """
464
465        x, y = item.midpoint
466        x += dx
467        y += dy
468        return x, y
469
470    def left_of(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
471        """
472        Get the item.west of the reference item.
473
474        Args:
475            item (object): The reference item. Shape or Batch.
476            dx (float): The x offset.
477            dy (float): The y offset.
478
479        Returns:
480            Point: The item.west of the reference item's bounding-box.
481        """
482        x, y = item.west
483        w2 = self.width / 2
484        x += (dx - w2)
485        y += dy
486        return x, y
487
488    def right_of(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
489        """
490        Get the item.east of the reference item.
491
492        Args:
493            item (object): The reference item. Shape or Batch.
494            dx (float): The x offset.
495            dy (float): The y offset.
496
497        Returns:
498            Point: The item.east of the reference item's bounding-box.
499        """
500        x, y = item.east
501        w2 = self.width / 2
502        x += (dx + w2)
503        y += dy
504        return x, y
505
506    def above(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
507        """
508        Get the item.north of the reference item.
509
510        Args:
511            item (object): The reference item. Shape or Batch.
512            dx (float): The x offset.
513            dy (float): The y offset.
514
515        Returns:
516            Point: The item.north of the reference item's bounding-box.
517        """
518        x, y = item.north
519        h2 = self.height / 2
520        x += dx
521        y += (dy + h2)
522        return x, y
523
524    def below(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
525        """
526        Get the item.south of the reference item.
527
528        Args:
529            item (object): The reference item. Shape or Batch.
530            dx (float): The x offset.
531            dy (float): The y offset.
532
533        Returns:
534            Point: The item.south of the reference item's bounding-box.
535        """
536        x, y = item.south
537        h2 = self.height / 2
538        x += dx
539        y += (dy - h2)
540        return x, y
541
542    def above_left(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
543        """
544        Get the item.northwest of the reference item.
545
546        Args:
547            item (object): The reference item. Shape or Batch.
548            dx (float): The x offset.
549            dy (float): The y offset.
550
551        Returns:
552            Point: The item.northwest of the reference item's bounding-box.
553        """
554        x, y = item.northwest
555        w2 = self.width / 2
556        h2 = self.height / 2
557        x += (dx - w2)
558        y += (dy + h2)
559
560        return x, y
561
562
563    def above_right(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
564        """
565        Get the item.northeast of the reference item.
566
567        Args:
568            item (object): The reference item. Shape or Batch.
569            dx (float): The x offset.
570            dy (float): The y offset.
571
572        Returns:
573            Point: The item.northeast of the reference item's bounding-box.
574        """
575        x, y = item.northeast
576        w2 = self.width / 2
577        h2 = self.height / 2
578        x += (dx + w2)
579        y += (dy + h2)
580
581        return x, y
582
583
584    def below_left(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
585        """
586        Get the item.southwest of the reference item.
587
588        Args:
589            item (object): The reference item. Shape or Batch.
590            dx (float): The x offset.
591            dy (float): The y offset.
592
593        Returns:
594            Point: The item.southwest of the reference item's bounding-box.
595        """
596        x, y = item.southwest
597        w2 = self.width / 2
598        h2 = self.height / 2
599        x += (dx - w2)
600        y += (dy - h2)
601
602        return x, y
603
604
605    def below_right(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
606        """
607        Get the item.southeast of the reference item.
608
609        Args:
610            item (object): The reference item. Shape or Batch.
611            dx (float): The x offset.
612            dy (float): The y offset.
613
614        Returns:
615            Point: The item.southeast of the reference item's bounding-box.
616        """
617        x, y = item.southeast
618        w2 = self.width / 2
619        h2 = self.height / 2
620        x += (dx + w2)
621        y += (dy - h2)
622
623        return x, y
624
625
626    def polar_pos(self, item:'Union[Shape, Batch]', theta:float, radius:float)->Point:
627        """
628        Get the polar position of the reference item.
629
630        Args:
631            item (object): The reference item. Shape or Batch.
632            theta (float): The angle in radians.
633            radius (float): The radius.
634
635        Returns:
636            Point: The polar position of the reference item.
637        """
638
639        x, y = item.midpoint
640
641        x1, y1 = polar_to_cartesian(radius, theta)
642        x += x1
643        y += y1
644
645        return x, y
646
647
648def bounding_box(points):
649    """
650    Given a list of (x, y) points return the corresponding BoundingBox object.
651
652    Args:
653        points (list): The list of points.
654
655    Returns:
656        BoundingBox: The corresponding BoundingBox object.
657
658    Raises:
659        ValueError: If the list of points is empty.
660    """
661    if isinstance(points, np.ndarray):
662        points = points[:, :2]
663    else:
664        points = np.array(points)  # numpy array of points
665    n_points = len(points)
666    BB_EPSILON = defaults["BB_EPSILON"]
667    if n_points == 0:  # empty list of points
668        raise ValueError("Empty list of points")
669
670    if len(points.shape) == 1:
671        # single point
672        min_x, min_y = points
673        max_x = min_x + BB_EPSILON
674        max_y = min_y + BB_EPSILON
675    else:
676        # find minimum and maximum coordinates
677        min_x, min_y = points.min(axis=0)
678        max_x, max_y = points.max(axis=0)
679        if min_x == max_x:  # this could be a vertical line or degenerate points
680            max_x += BB_EPSILON
681        if min_y == max_y:  # this could be a horizontal line or degenerate points
682            max_y += BB_EPSILON
683    # bounding box corners
684    bottom_left = (min_x, min_y)
685    top_right = (max_x, max_y)
686    return BoundingBox(southwest=bottom_left, northeast=top_right)
class BoundingBox:
 21class BoundingBox:
 22    """Rectangular bounding box.
 23    If the object is a Shape, it contains all points.
 24    If the object is a Batch, it contains all points of all Shapes.
 25
 26    Provides reference edges and points as shown in the Book page ???.
 27    """
 28
 29    def __init__(self, southwest: Point, northeast: Point):
 30        """
 31        Initialize a BoundingBox object.
 32
 33        Args:
 34            southwest (Point): The southwest corner of the bounding box.
 35            northeast (Point): The northeast corner of the bounding box.
 36        """
 37        # define the four corners
 38        self.__dict__["southwest"] = southwest
 39        self.__dict__["northeast"] = northeast
 40        self.__dict__["northwest"] = (southwest[0], northeast[1])
 41        self.__dict__["southeast"] = (northeast[0], southwest[1])
 42        self._aliases = {
 43            "s": "south",
 44            "n": "north",
 45            "w": "west",
 46            "e": "east",
 47            "sw": "southwest",
 48            "se": "southeast",
 49            "nw": "northwest",
 50            "ne": "northeast",
 51            "d1": "diagonal1",
 52            "d2": "diagonal2",
 53            "m": "midpoint",
 54            "vcl": "vert_centerline",
 55            "hcl": "horiz_centerline",
 56            "center": "midpoint",
 57        }
 58
 59        common_properties(self)
 60        self.type = Types.BOUNDING_BOX
 61        self.subtype = Types.BOUNDING_BOX
 62
 63    def __getattr__(self, name):
 64        """
 65        Get the attribute with the given name.
 66
 67        Args:
 68            name (str): The name of the attribute.
 69
 70        Returns:
 71            Any: The attribute with the given name.
 72        """
 73        if name in self._aliases:
 74            if name == "center":
 75                warnings.warn('"center" is deprecated use "midpoint" instead.', DeprecationWarning)
 76            res = getattr(self, self._aliases[name])
 77        else:
 78            res = self.__dict__[name]
 79        return res
 80
 81    def angle_point(self, angle: float) -> float:
 82        """
 83        Return the intersection point of the angled line starting
 84        from the midpoint and the bounding box. angle is in radians.
 85
 86        Args:
 87            angle (float): The angle in radians.
 88
 89        Returns:
 90            float: The intersection point.
 91        """
 92        angle = positive_angle(angle)
 93        line = ((0, 0), (np.cos(angle), np.sin(angle)))
 94
 95        angle1 = line_angle(self.midpoint, self.northeast)
 96        angle2 = -angle1  # midpoint, southeast
 97        angle3 = np.pi - angle1  # midpoint, northwest
 98        angle4 = -angle3  # midpoint, southwest
 99        if angle3 >= angle >= angle1:
100            res = intersect(line, self.top)
101        elif angle4 <= angle <= angle2:
102            res = intersect(line, self.bottom)
103        elif angle1 <= angle <= angle2:
104            res = intersect(line, self.right)
105        else:
106            res = intersect(line, self.left)
107
108        return res
109
110    @property
111    def left(self):
112        """
113        Return the left edge.
114
115        Returns:
116            tuple: The left edge.
117        """
118        return (self.northwest, self.southwest)
119
120    @property
121    def right(self):
122        """
123        Return the right edge.
124
125        Returns:
126            tuple: The right edge.
127        """
128        return (self.northeast, self.southeast)
129
130    @property
131    def top(self):
132        """
133        Return the top edge.
134
135        Returns:
136            tuple: The top edge.
137        """
138        return (self.northwest, self.northeast)
139
140    @property
141    def bottom(self):
142        """
143        Return the bottom edge.
144
145        Returns:
146            tuple: The bottom edge.
147        """
148        return (self.southwest, self.southeast)
149
150    @property
151    def vert_centerline(self):
152        """
153        Return the vertical centerline.
154
155        Returns:
156            tuple: The vertical centerline.
157        """
158        return (self.north, self.south)
159
160    @property
161    def horiz_centerline(self):
162        """
163        Return the horizontal centerline.
164
165        Returns:
166            tuple: The horizontal centerline.
167        """
168        return (self.west, self.east)
169
170    @property
171    def midpoint(self):
172        """
173        Return the center of the bounding box.
174
175        Returns:
176            tuple: The center of the bounding box.
177        """
178        x1, y1 = self.southwest
179        x2, y2 = self.northeast
180
181        xc = (x1 + x2) / 2
182        yc = (y1 + y2) / 2
183
184        return (xc, yc)
185
186    @property
187    def corners(self):
188        """
189        Return the four corners of the bounding box.
190
191        Returns:
192            tuple: The four corners of the bounding box.
193        """
194        return (self.northwest, self.southwest, self.southeast, self.northeast)
195
196    @property
197    def diamond(self):
198        """
199        Return the four center points of the bounding box in a diamond shape.
200
201        Returns:
202            tuple: The four center points of the bounding box in a diamond shape.
203        """
204        return (self.north, self.west, self.south, self.east)
205
206    @property
207    def all_anchors(self):
208        """
209        Return all anchors of the bounding box.
210
211        Returns:
212            tuple: All anchors of the bounding box.
213        """
214        return (
215            self.northwest,
216            self.west,
217            self.southwest,
218            self.south,
219            self.northeast,
220            self.east,
221            self.northeast,
222            self.north,
223            self.midpoint,
224        )
225
226    @property
227    def width(self):
228        """
229        Return the width of the bounding box.
230
231        Returns:
232            float: The width of the bounding box.
233        """
234        return distance(self.northwest, self.northeast)
235
236    @property
237    def height(self):
238        """
239        Return the height of the bounding box.
240
241        Returns:
242            float: The height of the bounding box.
243        """
244        return distance(self.northwest, self.southwest)
245
246    @property
247    def size(self):
248        """
249        Return the size of the bounding box.
250
251        Returns:
252            tuple: The size of the bounding box.
253        """
254        return (self.width, self.height)
255
256    @property
257    def west(self):
258        """
259        Return the left edge midpoint.
260
261        Returns:
262            tuple: The left edge midpoint.
263        """
264        return mid_point(*self.left)
265
266    @property
267    def south(self):
268        """
269        Return the bottom edge midpoint.
270
271        Returns:
272            tuple: The bottom edge midpoint.
273        """
274        return mid_point(*self.bottom)
275
276    @property
277    def east(self):
278        """
279        Return the right edge midpoint.
280
281        Returns:
282            tuple: The right edge midpoint.
283        """
284        return mid_point(*self.right)
285
286    @property
287    def north(self):
288        """
289        Return the top edge midpoint.
290
291        Returns:
292            tuple: The top edge midpoint.
293        """
294        return mid_point(*self.top)
295
296    @property
297    def northwest(self):
298        """
299        Return the top left corner.
300
301        Returns:
302            tuple: The top left corner.
303        """
304        return self.__dict__["northwest"]
305
306    @property
307    def northeast(self):
308        """
309        Return the top right corner.
310
311        Returns:
312            tuple: The top right corner.
313        """
314        return self.__dict__["northeast"]
315
316    @property
317    def southwest(self):
318        """
319        Return the bottom left corner.
320
321        Returns:
322            tuple: The bottom left corner.
323        """
324        return self.__dict__["southwest"]
325
326    @property
327    def southeast(self):
328        """
329        Return the bottom right corner.
330
331        Returns:
332            tuple: The bottom right corner.
333        """
334        return self.__dict__["southeast"]
335
336    @property
337    def diagonal1(self):
338        """
339        Return the first diagonal. From the top left to the bottom right.
340
341        Returns:
342            tuple: The first diagonal.
343        """
344        return (self.southwest, self.northeast)
345
346    @property
347    def diagonal2(self):
348        """
349        Return the second diagonal. From the top right to the bottom left.
350
351        Returns:
352            tuple: The second diagonal.
353        """
354        return (self.southeast, self.northwest)
355
356    def get_inflated_b_box(
357        self, left_margin=None, bottom_margin=None, right_margin=None, top_margin=None
358    ):
359        """
360        Return a bounding box with offset edges.
361
362        Args:
363            left_margin (float, optional): The left margin.
364            bottom_margin (float, optional): The bottom margin.
365            right_margin (float, optional): The right margin.
366            top_margin (float, optional): The top margin.
367
368        Returns:
369            BoundingBox: The inflated bounding box.
370        """
371
372        if bottom_margin is None:
373            bottom_margin = left_margin
374        if right_margin is None:
375            right_margin = left_margin
376        if top_margin is None:
377            top_margin = bottom_margin
378
379        x, y = self.southwest[:2]
380        southwest = (x - left_margin, y - bottom_margin)
381
382        x, y = self.northeast[:2]
383        northeast = (x + right_margin, y + top_margin)
384
385        return BoundingBox(southwest, northeast)
386
387    def offset_line(self, side, offset):
388        """
389        Offset is applied outwards. Use negative values for inward offset.
390
391        Args:
392            side (Side): The side to offset.
393            offset (float): The offset distance.
394
395        Returns:
396            tuple: The offset line.
397        """
398        if isinstance(side, str):
399            side = Side[side.upper()]
400
401        if side == Side.RIGHT:
402            x1, y1 = self.southeast
403            x2, y2 = self.northeast
404            res = ((x1 + offset, y1), (x2 + offset, y2))
405        elif side == Side.LEFT:
406            x1, y1 = self.southwest
407            x2, y2 = self.northwest
408            res = ((x1 - offset, y1), (x2 - offset, y2))
409        elif side == Side.TOP:
410            x1, y1 = self.northwest
411            x2, y2 = self.northeast
412            res = ((x1, y1 + offset), (x2, y2 + offset))
413        elif side == Side.BOTTOM:
414            x1, y1 = self.southwest
415            x2, y2 = self.southeast
416            res = ((x1, y1 - offset), (x2, y2 - offset))
417        elif side == Side.DIAGONAL1:
418            res = offset_line(self.diagonal1, offset)
419        elif side == Side.DIAGONAL2:
420            res = offset_line(self.diagonal2, offset)
421        elif side == Side.H_CENTERLINE:
422            res = offset_line(self.horiz_center_line, offset)
423        elif side == Side.V_CENTERLINE:
424            res = offset_line(self.vert_center_line, offset)
425        else:
426            raise ValueError(f"Unknown side: {side}")
427            res = None
428
429        return res
430
431    def offset_point(self, anchor, dx, dy):
432        """
433        Return an offset point from the given corner.
434
435        Args:
436            anchor (Anchor): The anchor point.
437            dx (float): The x offset.
438            dy (float): The y offset.
439
440        Returns:
441            list: The offset point.
442        """
443        if isinstance(anchor, str):
444            anchor = Anchor[anchor.upper()]
445            x, y = getattr(self, anchor.value)[:2]
446        elif isinstance(anchor, Anchor):
447            x, y = anchor.value[:2]
448        else:
449            raise ValueError(f"Unknown anchor: {anchor}")
450        return [x + dx, y + dy]
451
452
453    def centered(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
454        """
455        Get the center of the reference item.
456
457        Args:
458            item (object): The reference item. Shape or Batch.
459            dx (float): The x offset.
460            dy (float): The y offset.
461
462        Returns:
463            Point: The item.midpoint of the reference item's bounding-box.
464        """
465
466        x, y = item.midpoint
467        x += dx
468        y += dy
469        return x, y
470
471    def left_of(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
472        """
473        Get the item.west of the reference item.
474
475        Args:
476            item (object): The reference item. Shape or Batch.
477            dx (float): The x offset.
478            dy (float): The y offset.
479
480        Returns:
481            Point: The item.west of the reference item's bounding-box.
482        """
483        x, y = item.west
484        w2 = self.width / 2
485        x += (dx - w2)
486        y += dy
487        return x, y
488
489    def right_of(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
490        """
491        Get the item.east of the reference item.
492
493        Args:
494            item (object): The reference item. Shape or Batch.
495            dx (float): The x offset.
496            dy (float): The y offset.
497
498        Returns:
499            Point: The item.east of the reference item's bounding-box.
500        """
501        x, y = item.east
502        w2 = self.width / 2
503        x += (dx + w2)
504        y += dy
505        return x, y
506
507    def above(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
508        """
509        Get the item.north of the reference item.
510
511        Args:
512            item (object): The reference item. Shape or Batch.
513            dx (float): The x offset.
514            dy (float): The y offset.
515
516        Returns:
517            Point: The item.north of the reference item's bounding-box.
518        """
519        x, y = item.north
520        h2 = self.height / 2
521        x += dx
522        y += (dy + h2)
523        return x, y
524
525    def below(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
526        """
527        Get the item.south of the reference item.
528
529        Args:
530            item (object): The reference item. Shape or Batch.
531            dx (float): The x offset.
532            dy (float): The y offset.
533
534        Returns:
535            Point: The item.south of the reference item's bounding-box.
536        """
537        x, y = item.south
538        h2 = self.height / 2
539        x += dx
540        y += (dy - h2)
541        return x, y
542
543    def above_left(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
544        """
545        Get the item.northwest of the reference item.
546
547        Args:
548            item (object): The reference item. Shape or Batch.
549            dx (float): The x offset.
550            dy (float): The y offset.
551
552        Returns:
553            Point: The item.northwest of the reference item's bounding-box.
554        """
555        x, y = item.northwest
556        w2 = self.width / 2
557        h2 = self.height / 2
558        x += (dx - w2)
559        y += (dy + h2)
560
561        return x, y
562
563
564    def above_right(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
565        """
566        Get the item.northeast of the reference item.
567
568        Args:
569            item (object): The reference item. Shape or Batch.
570            dx (float): The x offset.
571            dy (float): The y offset.
572
573        Returns:
574            Point: The item.northeast of the reference item's bounding-box.
575        """
576        x, y = item.northeast
577        w2 = self.width / 2
578        h2 = self.height / 2
579        x += (dx + w2)
580        y += (dy + h2)
581
582        return x, y
583
584
585    def below_left(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
586        """
587        Get the item.southwest of the reference item.
588
589        Args:
590            item (object): The reference item. Shape or Batch.
591            dx (float): The x offset.
592            dy (float): The y offset.
593
594        Returns:
595            Point: The item.southwest of the reference item's bounding-box.
596        """
597        x, y = item.southwest
598        w2 = self.width / 2
599        h2 = self.height / 2
600        x += (dx - w2)
601        y += (dy - h2)
602
603        return x, y
604
605
606    def below_right(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
607        """
608        Get the item.southeast of the reference item.
609
610        Args:
611            item (object): The reference item. Shape or Batch.
612            dx (float): The x offset.
613            dy (float): The y offset.
614
615        Returns:
616            Point: The item.southeast of the reference item's bounding-box.
617        """
618        x, y = item.southeast
619        w2 = self.width / 2
620        h2 = self.height / 2
621        x += (dx + w2)
622        y += (dy - h2)
623
624        return x, y
625
626
627    def polar_pos(self, item:'Union[Shape, Batch]', theta:float, radius:float)->Point:
628        """
629        Get the polar position of the reference item.
630
631        Args:
632            item (object): The reference item. Shape or Batch.
633            theta (float): The angle in radians.
634            radius (float): The radius.
635
636        Returns:
637            Point: The polar position of the reference item.
638        """
639
640        x, y = item.midpoint
641
642        x1, y1 = polar_to_cartesian(radius, theta)
643        x += x1
644        y += y1
645
646        return x, y

Rectangular bounding box. If the object is a Shape, it contains all points. If the object is a Batch, it contains all points of all Shapes.

Provides reference edges and points as shown in the Book page ???.

BoundingBox(southwest: Sequence[float], northeast: Sequence[float])
29    def __init__(self, southwest: Point, northeast: Point):
30        """
31        Initialize a BoundingBox object.
32
33        Args:
34            southwest (Point): The southwest corner of the bounding box.
35            northeast (Point): The northeast corner of the bounding box.
36        """
37        # define the four corners
38        self.__dict__["southwest"] = southwest
39        self.__dict__["northeast"] = northeast
40        self.__dict__["northwest"] = (southwest[0], northeast[1])
41        self.__dict__["southeast"] = (northeast[0], southwest[1])
42        self._aliases = {
43            "s": "south",
44            "n": "north",
45            "w": "west",
46            "e": "east",
47            "sw": "southwest",
48            "se": "southeast",
49            "nw": "northwest",
50            "ne": "northeast",
51            "d1": "diagonal1",
52            "d2": "diagonal2",
53            "m": "midpoint",
54            "vcl": "vert_centerline",
55            "hcl": "horiz_centerline",
56            "center": "midpoint",
57        }
58
59        common_properties(self)
60        self.type = Types.BOUNDING_BOX
61        self.subtype = Types.BOUNDING_BOX

Initialize a BoundingBox object.

Arguments:
  • southwest (Point): The southwest corner of the bounding box.
  • northeast (Point): The northeast corner of the bounding box.
type
subtype
def angle_point(self, angle: float) -> float:
 81    def angle_point(self, angle: float) -> float:
 82        """
 83        Return the intersection point of the angled line starting
 84        from the midpoint and the bounding box. angle is in radians.
 85
 86        Args:
 87            angle (float): The angle in radians.
 88
 89        Returns:
 90            float: The intersection point.
 91        """
 92        angle = positive_angle(angle)
 93        line = ((0, 0), (np.cos(angle), np.sin(angle)))
 94
 95        angle1 = line_angle(self.midpoint, self.northeast)
 96        angle2 = -angle1  # midpoint, southeast
 97        angle3 = np.pi - angle1  # midpoint, northwest
 98        angle4 = -angle3  # midpoint, southwest
 99        if angle3 >= angle >= angle1:
100            res = intersect(line, self.top)
101        elif angle4 <= angle <= angle2:
102            res = intersect(line, self.bottom)
103        elif angle1 <= angle <= angle2:
104            res = intersect(line, self.right)
105        else:
106            res = intersect(line, self.left)
107
108        return res

Return the intersection point of the angled line starting from the midpoint and the bounding box. angle is in radians.

Arguments:
  • angle (float): The angle in radians.
Returns:

float: The intersection point.

left
110    @property
111    def left(self):
112        """
113        Return the left edge.
114
115        Returns:
116            tuple: The left edge.
117        """
118        return (self.northwest, self.southwest)

Return the left edge.

Returns:

tuple: The left edge.

right
120    @property
121    def right(self):
122        """
123        Return the right edge.
124
125        Returns:
126            tuple: The right edge.
127        """
128        return (self.northeast, self.southeast)

Return the right edge.

Returns:

tuple: The right edge.

top
130    @property
131    def top(self):
132        """
133        Return the top edge.
134
135        Returns:
136            tuple: The top edge.
137        """
138        return (self.northwest, self.northeast)

Return the top edge.

Returns:

tuple: The top edge.

bottom
140    @property
141    def bottom(self):
142        """
143        Return the bottom edge.
144
145        Returns:
146            tuple: The bottom edge.
147        """
148        return (self.southwest, self.southeast)

Return the bottom edge.

Returns:

tuple: The bottom edge.

vert_centerline
150    @property
151    def vert_centerline(self):
152        """
153        Return the vertical centerline.
154
155        Returns:
156            tuple: The vertical centerline.
157        """
158        return (self.north, self.south)

Return the vertical centerline.

Returns:

tuple: The vertical centerline.

horiz_centerline
160    @property
161    def horiz_centerline(self):
162        """
163        Return the horizontal centerline.
164
165        Returns:
166            tuple: The horizontal centerline.
167        """
168        return (self.west, self.east)

Return the horizontal centerline.

Returns:

tuple: The horizontal centerline.

midpoint
170    @property
171    def midpoint(self):
172        """
173        Return the center of the bounding box.
174
175        Returns:
176            tuple: The center of the bounding box.
177        """
178        x1, y1 = self.southwest
179        x2, y2 = self.northeast
180
181        xc = (x1 + x2) / 2
182        yc = (y1 + y2) / 2
183
184        return (xc, yc)

Return the center of the bounding box.

Returns:

tuple: The center of the bounding box.

corners
186    @property
187    def corners(self):
188        """
189        Return the four corners of the bounding box.
190
191        Returns:
192            tuple: The four corners of the bounding box.
193        """
194        return (self.northwest, self.southwest, self.southeast, self.northeast)

Return the four corners of the bounding box.

Returns:

tuple: The four corners of the bounding box.

diamond
196    @property
197    def diamond(self):
198        """
199        Return the four center points of the bounding box in a diamond shape.
200
201        Returns:
202            tuple: The four center points of the bounding box in a diamond shape.
203        """
204        return (self.north, self.west, self.south, self.east)

Return the four center points of the bounding box in a diamond shape.

Returns:

tuple: The four center points of the bounding box in a diamond shape.

all_anchors
206    @property
207    def all_anchors(self):
208        """
209        Return all anchors of the bounding box.
210
211        Returns:
212            tuple: All anchors of the bounding box.
213        """
214        return (
215            self.northwest,
216            self.west,
217            self.southwest,
218            self.south,
219            self.northeast,
220            self.east,
221            self.northeast,
222            self.north,
223            self.midpoint,
224        )

Return all anchors of the bounding box.

Returns:

tuple: All anchors of the bounding box.

width
226    @property
227    def width(self):
228        """
229        Return the width of the bounding box.
230
231        Returns:
232            float: The width of the bounding box.
233        """
234        return distance(self.northwest, self.northeast)

Return the width of the bounding box.

Returns:

float: The width of the bounding box.

height
236    @property
237    def height(self):
238        """
239        Return the height of the bounding box.
240
241        Returns:
242            float: The height of the bounding box.
243        """
244        return distance(self.northwest, self.southwest)

Return the height of the bounding box.

Returns:

float: The height of the bounding box.

size
246    @property
247    def size(self):
248        """
249        Return the size of the bounding box.
250
251        Returns:
252            tuple: The size of the bounding box.
253        """
254        return (self.width, self.height)

Return the size of the bounding box.

Returns:

tuple: The size of the bounding box.

west
256    @property
257    def west(self):
258        """
259        Return the left edge midpoint.
260
261        Returns:
262            tuple: The left edge midpoint.
263        """
264        return mid_point(*self.left)

Return the left edge midpoint.

Returns:

tuple: The left edge midpoint.

south
266    @property
267    def south(self):
268        """
269        Return the bottom edge midpoint.
270
271        Returns:
272            tuple: The bottom edge midpoint.
273        """
274        return mid_point(*self.bottom)

Return the bottom edge midpoint.

Returns:

tuple: The bottom edge midpoint.

east
276    @property
277    def east(self):
278        """
279        Return the right edge midpoint.
280
281        Returns:
282            tuple: The right edge midpoint.
283        """
284        return mid_point(*self.right)

Return the right edge midpoint.

Returns:

tuple: The right edge midpoint.

north
286    @property
287    def north(self):
288        """
289        Return the top edge midpoint.
290
291        Returns:
292            tuple: The top edge midpoint.
293        """
294        return mid_point(*self.top)

Return the top edge midpoint.

Returns:

tuple: The top edge midpoint.

northwest
296    @property
297    def northwest(self):
298        """
299        Return the top left corner.
300
301        Returns:
302            tuple: The top left corner.
303        """
304        return self.__dict__["northwest"]

Return the top left corner.

Returns:

tuple: The top left corner.

northeast
306    @property
307    def northeast(self):
308        """
309        Return the top right corner.
310
311        Returns:
312            tuple: The top right corner.
313        """
314        return self.__dict__["northeast"]

Return the top right corner.

Returns:

tuple: The top right corner.

southwest
316    @property
317    def southwest(self):
318        """
319        Return the bottom left corner.
320
321        Returns:
322            tuple: The bottom left corner.
323        """
324        return self.__dict__["southwest"]

Return the bottom left corner.

Returns:

tuple: The bottom left corner.

southeast
326    @property
327    def southeast(self):
328        """
329        Return the bottom right corner.
330
331        Returns:
332            tuple: The bottom right corner.
333        """
334        return self.__dict__["southeast"]

Return the bottom right corner.

Returns:

tuple: The bottom right corner.

diagonal1
336    @property
337    def diagonal1(self):
338        """
339        Return the first diagonal. From the top left to the bottom right.
340
341        Returns:
342            tuple: The first diagonal.
343        """
344        return (self.southwest, self.northeast)

Return the first diagonal. From the top left to the bottom right.

Returns:

tuple: The first diagonal.

diagonal2
346    @property
347    def diagonal2(self):
348        """
349        Return the second diagonal. From the top right to the bottom left.
350
351        Returns:
352            tuple: The second diagonal.
353        """
354        return (self.southeast, self.northwest)

Return the second diagonal. From the top right to the bottom left.

Returns:

tuple: The second diagonal.

def get_inflated_b_box( self, left_margin=None, bottom_margin=None, right_margin=None, top_margin=None):
356    def get_inflated_b_box(
357        self, left_margin=None, bottom_margin=None, right_margin=None, top_margin=None
358    ):
359        """
360        Return a bounding box with offset edges.
361
362        Args:
363            left_margin (float, optional): The left margin.
364            bottom_margin (float, optional): The bottom margin.
365            right_margin (float, optional): The right margin.
366            top_margin (float, optional): The top margin.
367
368        Returns:
369            BoundingBox: The inflated bounding box.
370        """
371
372        if bottom_margin is None:
373            bottom_margin = left_margin
374        if right_margin is None:
375            right_margin = left_margin
376        if top_margin is None:
377            top_margin = bottom_margin
378
379        x, y = self.southwest[:2]
380        southwest = (x - left_margin, y - bottom_margin)
381
382        x, y = self.northeast[:2]
383        northeast = (x + right_margin, y + top_margin)
384
385        return BoundingBox(southwest, northeast)

Return a bounding box with offset edges.

Arguments:
  • left_margin (float, optional): The left margin.
  • bottom_margin (float, optional): The bottom margin.
  • right_margin (float, optional): The right margin.
  • top_margin (float, optional): The top margin.
Returns:

BoundingBox: The inflated bounding box.

def offset_line(self, side, offset):
387    def offset_line(self, side, offset):
388        """
389        Offset is applied outwards. Use negative values for inward offset.
390
391        Args:
392            side (Side): The side to offset.
393            offset (float): The offset distance.
394
395        Returns:
396            tuple: The offset line.
397        """
398        if isinstance(side, str):
399            side = Side[side.upper()]
400
401        if side == Side.RIGHT:
402            x1, y1 = self.southeast
403            x2, y2 = self.northeast
404            res = ((x1 + offset, y1), (x2 + offset, y2))
405        elif side == Side.LEFT:
406            x1, y1 = self.southwest
407            x2, y2 = self.northwest
408            res = ((x1 - offset, y1), (x2 - offset, y2))
409        elif side == Side.TOP:
410            x1, y1 = self.northwest
411            x2, y2 = self.northeast
412            res = ((x1, y1 + offset), (x2, y2 + offset))
413        elif side == Side.BOTTOM:
414            x1, y1 = self.southwest
415            x2, y2 = self.southeast
416            res = ((x1, y1 - offset), (x2, y2 - offset))
417        elif side == Side.DIAGONAL1:
418            res = offset_line(self.diagonal1, offset)
419        elif side == Side.DIAGONAL2:
420            res = offset_line(self.diagonal2, offset)
421        elif side == Side.H_CENTERLINE:
422            res = offset_line(self.horiz_center_line, offset)
423        elif side == Side.V_CENTERLINE:
424            res = offset_line(self.vert_center_line, offset)
425        else:
426            raise ValueError(f"Unknown side: {side}")
427            res = None
428
429        return res

Offset is applied outwards. Use negative values for inward offset.

Arguments:
  • side (Side): The side to offset.
  • offset (float): The offset distance.
Returns:

tuple: The offset line.

def offset_point(self, anchor, dx, dy):
431    def offset_point(self, anchor, dx, dy):
432        """
433        Return an offset point from the given corner.
434
435        Args:
436            anchor (Anchor): The anchor point.
437            dx (float): The x offset.
438            dy (float): The y offset.
439
440        Returns:
441            list: The offset point.
442        """
443        if isinstance(anchor, str):
444            anchor = Anchor[anchor.upper()]
445            x, y = getattr(self, anchor.value)[:2]
446        elif isinstance(anchor, Anchor):
447            x, y = anchor.value[:2]
448        else:
449            raise ValueError(f"Unknown anchor: {anchor}")
450        return [x + dx, y + dy]

Return an offset point from the given corner.

Arguments:
  • anchor (Anchor): The anchor point.
  • dx (float): The x offset.
  • dy (float): The y offset.
Returns:

list: The offset point.

def centered( self, item: 'Union[Shape, Batch]', dx: float = 0, dy: float = 0) -> Sequence[float]:
453    def centered(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
454        """
455        Get the center of the reference item.
456
457        Args:
458            item (object): The reference item. Shape or Batch.
459            dx (float): The x offset.
460            dy (float): The y offset.
461
462        Returns:
463            Point: The item.midpoint of the reference item's bounding-box.
464        """
465
466        x, y = item.midpoint
467        x += dx
468        y += dy
469        return x, y

Get the center of the reference item.

Arguments:
  • item (object): The reference item. Shape or Batch.
  • dx (float): The x offset.
  • dy (float): The y offset.
Returns:

Point: The item.midpoint of the reference item's bounding-box.

def left_of( self, item: 'Union[Shape, Batch]', dx: float = 0, dy: float = 0) -> Sequence[float]:
471    def left_of(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
472        """
473        Get the item.west of the reference item.
474
475        Args:
476            item (object): The reference item. Shape or Batch.
477            dx (float): The x offset.
478            dy (float): The y offset.
479
480        Returns:
481            Point: The item.west of the reference item's bounding-box.
482        """
483        x, y = item.west
484        w2 = self.width / 2
485        x += (dx - w2)
486        y += dy
487        return x, y

Get the item.west of the reference item.

Arguments:
  • item (object): The reference item. Shape or Batch.
  • dx (float): The x offset.
  • dy (float): The y offset.
Returns:

Point: The item.west of the reference item's bounding-box.

def right_of( self, item: 'Union[Shape, Batch]', dx: float = 0, dy: float = 0) -> Sequence[float]:
489    def right_of(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
490        """
491        Get the item.east of the reference item.
492
493        Args:
494            item (object): The reference item. Shape or Batch.
495            dx (float): The x offset.
496            dy (float): The y offset.
497
498        Returns:
499            Point: The item.east of the reference item's bounding-box.
500        """
501        x, y = item.east
502        w2 = self.width / 2
503        x += (dx + w2)
504        y += dy
505        return x, y

Get the item.east of the reference item.

Arguments:
  • item (object): The reference item. Shape or Batch.
  • dx (float): The x offset.
  • dy (float): The y offset.
Returns:

Point: The item.east of the reference item's bounding-box.

def above( self, item: 'Union[Shape, Batch]', dx: float = 0, dy: float = 0) -> Sequence[float]:
507    def above(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
508        """
509        Get the item.north of the reference item.
510
511        Args:
512            item (object): The reference item. Shape or Batch.
513            dx (float): The x offset.
514            dy (float): The y offset.
515
516        Returns:
517            Point: The item.north of the reference item's bounding-box.
518        """
519        x, y = item.north
520        h2 = self.height / 2
521        x += dx
522        y += (dy + h2)
523        return x, y

Get the item.north of the reference item.

Arguments:
  • item (object): The reference item. Shape or Batch.
  • dx (float): The x offset.
  • dy (float): The y offset.
Returns:

Point: The item.north of the reference item's bounding-box.

def below( self, item: 'Union[Shape, Batch]', dx: float = 0, dy: float = 0) -> Sequence[float]:
525    def below(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
526        """
527        Get the item.south of the reference item.
528
529        Args:
530            item (object): The reference item. Shape or Batch.
531            dx (float): The x offset.
532            dy (float): The y offset.
533
534        Returns:
535            Point: The item.south of the reference item's bounding-box.
536        """
537        x, y = item.south
538        h2 = self.height / 2
539        x += dx
540        y += (dy - h2)
541        return x, y

Get the item.south of the reference item.

Arguments:
  • item (object): The reference item. Shape or Batch.
  • dx (float): The x offset.
  • dy (float): The y offset.
Returns:

Point: The item.south of the reference item's bounding-box.

def above_left( self, item: 'Union[Shape, Batch]', dx: float = 0, dy: float = 0) -> Sequence[float]:
543    def above_left(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
544        """
545        Get the item.northwest of the reference item.
546
547        Args:
548            item (object): The reference item. Shape or Batch.
549            dx (float): The x offset.
550            dy (float): The y offset.
551
552        Returns:
553            Point: The item.northwest of the reference item's bounding-box.
554        """
555        x, y = item.northwest
556        w2 = self.width / 2
557        h2 = self.height / 2
558        x += (dx - w2)
559        y += (dy + h2)
560
561        return x, y

Get the item.northwest of the reference item.

Arguments:
  • item (object): The reference item. Shape or Batch.
  • dx (float): The x offset.
  • dy (float): The y offset.
Returns:

Point: The item.northwest of the reference item's bounding-box.

def above_right( self, item: 'Union[Shape, Batch]', dx: float = 0, dy: float = 0) -> Sequence[float]:
564    def above_right(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
565        """
566        Get the item.northeast of the reference item.
567
568        Args:
569            item (object): The reference item. Shape or Batch.
570            dx (float): The x offset.
571            dy (float): The y offset.
572
573        Returns:
574            Point: The item.northeast of the reference item's bounding-box.
575        """
576        x, y = item.northeast
577        w2 = self.width / 2
578        h2 = self.height / 2
579        x += (dx + w2)
580        y += (dy + h2)
581
582        return x, y

Get the item.northeast of the reference item.

Arguments:
  • item (object): The reference item. Shape or Batch.
  • dx (float): The x offset.
  • dy (float): The y offset.
Returns:

Point: The item.northeast of the reference item's bounding-box.

def below_left( self, item: 'Union[Shape, Batch]', dx: float = 0, dy: float = 0) -> Sequence[float]:
585    def below_left(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
586        """
587        Get the item.southwest of the reference item.
588
589        Args:
590            item (object): The reference item. Shape or Batch.
591            dx (float): The x offset.
592            dy (float): The y offset.
593
594        Returns:
595            Point: The item.southwest of the reference item's bounding-box.
596        """
597        x, y = item.southwest
598        w2 = self.width / 2
599        h2 = self.height / 2
600        x += (dx - w2)
601        y += (dy - h2)
602
603        return x, y

Get the item.southwest of the reference item.

Arguments:
  • item (object): The reference item. Shape or Batch.
  • dx (float): The x offset.
  • dy (float): The y offset.
Returns:

Point: The item.southwest of the reference item's bounding-box.

def below_right( self, item: 'Union[Shape, Batch]', dx: float = 0, dy: float = 0) -> Sequence[float]:
606    def below_right(self, item:'Union[Shape, Batch]', dx:float = 0, dy:float = 0)->Point:
607        """
608        Get the item.southeast of the reference item.
609
610        Args:
611            item (object): The reference item. Shape or Batch.
612            dx (float): The x offset.
613            dy (float): The y offset.
614
615        Returns:
616            Point: The item.southeast of the reference item's bounding-box.
617        """
618        x, y = item.southeast
619        w2 = self.width / 2
620        h2 = self.height / 2
621        x += (dx + w2)
622        y += (dy - h2)
623
624        return x, y

Get the item.southeast of the reference item.

Arguments:
  • item (object): The reference item. Shape or Batch.
  • dx (float): The x offset.
  • dy (float): The y offset.
Returns:

Point: The item.southeast of the reference item's bounding-box.

def polar_pos( self, item: 'Union[Shape, Batch]', theta: float, radius: float) -> Sequence[float]:
627    def polar_pos(self, item:'Union[Shape, Batch]', theta:float, radius:float)->Point:
628        """
629        Get the polar position of the reference item.
630
631        Args:
632            item (object): The reference item. Shape or Batch.
633            theta (float): The angle in radians.
634            radius (float): The radius.
635
636        Returns:
637            Point: The polar position of the reference item.
638        """
639
640        x, y = item.midpoint
641
642        x1, y1 = polar_to_cartesian(radius, theta)
643        x += x1
644        y += y1
645
646        return x, y

Get the polar position of the reference item.

Arguments:
  • item (object): The reference item. Shape or Batch.
  • theta (float): The angle in radians.
  • radius (float): The radius.
Returns:

Point: The polar position of the reference item.

def bounding_box(points):
649def bounding_box(points):
650    """
651    Given a list of (x, y) points return the corresponding BoundingBox object.
652
653    Args:
654        points (list): The list of points.
655
656    Returns:
657        BoundingBox: The corresponding BoundingBox object.
658
659    Raises:
660        ValueError: If the list of points is empty.
661    """
662    if isinstance(points, np.ndarray):
663        points = points[:, :2]
664    else:
665        points = np.array(points)  # numpy array of points
666    n_points = len(points)
667    BB_EPSILON = defaults["BB_EPSILON"]
668    if n_points == 0:  # empty list of points
669        raise ValueError("Empty list of points")
670
671    if len(points.shape) == 1:
672        # single point
673        min_x, min_y = points
674        max_x = min_x + BB_EPSILON
675        max_y = min_y + BB_EPSILON
676    else:
677        # find minimum and maximum coordinates
678        min_x, min_y = points.min(axis=0)
679        max_x, max_y = points.max(axis=0)
680        if min_x == max_x:  # this could be a vertical line or degenerate points
681            max_x += BB_EPSILON
682        if min_y == max_y:  # this could be a horizontal line or degenerate points
683            max_y += BB_EPSILON
684    # bounding box corners
685    bottom_left = (min_x, min_y)
686    top_right = (max_x, max_y)
687    return BoundingBox(southwest=bottom_left, northeast=top_right)

Given a list of (x, y) points return the corresponding BoundingBox object.

Arguments:
  • points (list): The list of points.
Returns:

BoundingBox: The corresponding BoundingBox object.

Raises:
  • ValueError: If the list of points is empty.