def test_box_contains_box(self): box = Bounds(Point(5, -2, 0), Point(11, 4, 7)) BoundsResult = namedtuple("BoundsResult", ["min", "max", "result"]) bounds_results = [ BoundsResult(Point(5, -2, 0), Point(11, 4, 7), True), BoundsResult(Point(6, -1, 1), Point(10, 3, 6), True), BoundsResult(Point(4, -3, -1), Point(10, 3, 6), False), BoundsResult(Point(6, -1, 1), Point(12, 5, 8), False) ] for bounds_result in bounds_results: box2 = Bounds(bounds_result.min, bounds_result.max) self.assertEqual(box.box_contains_box(box2), bounds_result.result)
def test_tranforming_bounding_box(self): box = Bounds(Point(-1, -1, -1), Point(1, 1, 1)) matrix = Transformations.rotation_x(math.pi / 4).dot( Transformations.rotation_y(math.pi / 4)) box2 = box.transform(matrix) self.assertEqual(box2.min, Point(-1.4142, -1.7071, -1.7071)) self.assertEqual(box2.max, Point(1.4142, 1.7071, 1.7071))
def test_splitting_z_wide_box(self): box = Bounds(Point(-1, -2, -3), Point(5, 3, 7)) (left, right) = Bounds.split_bounds(box) self.assertEqual(left.min, Point(-1, -2, -3)) self.assertEqual(left.max, Point(5, 3, 2)) self.assertEqual(right.min, Point(-1, -2, 2)) self.assertEqual(right.max, Point(5, 3, 7))
def test_splitting_perfect_cube(self): box = Bounds(Point(-1, -4, -5), Point(9, 6, 5)) (left, right) = Bounds.split_bounds(box) self.assertEqual(left.min, Point(-1, -4, -5)) self.assertEqual(left.max, Point(4, 6, 5)) self.assertEqual(right.min, Point(4, -4, -5)) self.assertEqual(right.max, Point(9, 6, 5))
def bounds_of(self) -> Bounds: box = Bounds() for child in self.members: cbox = child.parent_space_bounds_of() box.add_box(cbox) return box
def bounds_of(self) -> Bounds: a = abs(self.minimum) b = abs(self.maximum) limit = max(a, b) return Bounds(Point(-limit, self.minimum, -limit), Point(limit, self.maximum, limit))
def test_add_points_to_empty_bounding_box(self): box = Bounds() p1 = Point(-5, 2, 0) p2 = Point(7, 0, -3) box.add_point(p1) box.add_point(p2) self.assertEqual(box.min, Point(-5, 0, -3)) self.assertEqual(box.max, Point(7, 2, 0))
def __init__(self, filename=None): ''' filename: string, absolute or relative filename of an SVG file ''' self.filename = filename self.paths = [] self.path_by_id = {} self.bounds = Bounds() self.batch = None
def __init__(self, tag=None): self.id = None self.loops = [] self.color = (0, 0, 0) self.bounds = Bounds() self.triangles = None if tag: self.parse(tag)
def bounds_of(self) -> Bounds: box = Bounds() left_cbox = self.left.parent_space_bounds_of() right_cbox = self.right.parent_space_bounds_of() box.add_box(left_cbox) box.add_box(right_cbox) return box
def get_bounds(self): max = np.max(self.geojson['coordinates'], axis=0) max_lon = max[0] max_lat = max[1] max_ele = max[2] min = np.min(self.geojson['coordinates'], axis=0) min_lon = min[0] min_lat = min[1] min_ele = min[2] self.bounds = Bounds(min_lon, max_lon, min_lat, max_lat)
def test_box_countains_point(self): box = Bounds(Point(5, -2, 0), Point(11, 4, 7)) PointResult = namedtuple("PointResult", ["point", "result"]) point_results = [ PointResult(Point(5, -2, 0), True), PointResult(Point(11, 4, 7), True), PointResult(Point(8, 1, 3), True), PointResult(Point(3, 0, 3), False), PointResult(Point(8, -4, 3), False), PointResult(Point(8, 1, -1), False), PointResult(Point(13, 1, 3), False), PointResult(Point(8, 5, 3), False), PointResult(Point(8, 1, 8), False) ] for point_result in point_results: p = point_result.point self.assertEqual(box.box_contains_point(p), point_result.result)
def Run(self): print("Analyzing problems") with open(self.options.currentProblemFile, 'rt') as problemsFile: problemIndex = 0 for line in problemsFile: problemIndex = problemIndex + 1 print("\n=> Problem #" + str(problemIndex) + " will be analyzed") builder = Builder(line, self.options) problem = self.LoadProblem(builder) problem.SetMeasure(Measure(problem)) problem.SetBounds(Bounds(problem)) problem.bounds.CalculateBounds() solver = Solver(problem) solver.Solve() problem.measure.Write() print("Finished analyzer run") # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
def get_rendering_bounds(self): center_lat = (self.bounds.se.lat + self.bounds.nw.lat) / 2.0 center_lon = (self.bounds.se.lon + self.bounds.nw.lon) / 2.0 center = Point(center_lon, center_lat) center.project(self.rendering_zoom) self.center = center top_left_x = center.x - (self.render_width / 2.0) top_left_y = center.y - (self.render_height / 2.0) top_left = Point.from_xy(top_left_x, top_left_y) top_left.unproject(self.rendering_zoom) bottom_x = center.x + (self.render_width / 2.0) bottom_y = center.y + (self.render_height / 2.0) bottom_right = Point.from_xy(bottom_x, bottom_y) bottom_right.unproject(self.rendering_zoom) self.rendering_bounds = Bounds(top_left.lon, bottom_right.lon, bottom_right.lat, top_left.lat) print self.rendering_bounds
def test_intersect_ray_with_noncubic_bouding_box(self): box = Bounds(Point(5, -2, 0), Point(11, 4, 7)) RayResult = namedtuple("RayResult", ["origin", "direction", "result"]) ray_results = [ RayResult(Point(15, 1, 2), Vector(-1, 0, 0), True), RayResult(Point(-5, -1, 4), Vector(1, 0, 0), True), RayResult(Point(7, 6, 5), Vector(0, -1, 0), True), RayResult(Point(9, -5, 6), Vector(0, 1, 0), True), RayResult(Point(8, 2, 12), Vector(0, 0, -1), True), RayResult(Point(6, 0, -5), Vector(0, 0, 1), True), RayResult(Point(8, 1, 3.5), Vector(0, 0, 1), True), RayResult(Point(9, -1, -8), Vector(2, 4, 6), False), RayResult(Point(8, 3, -4), Vector(6, 2, 4), False), RayResult(Point(9, -1, -2), Vector(4, 6, 2), False), RayResult(Point(4, 0, 9), Vector(0, 0, -1), False), RayResult(Point(8, 6, -1), Vector(0, -1, 0), False), RayResult(Point(12, 5, 4), Vector(-1, 0, 0), False) ] for ray_result in ray_results: direction = Vector.normalize(ray_result.direction) r = Ray(ray_result.origin, direction) self.assertEqual(box.intersects(r), ray_result.result)
def test_intersect_ray_with_bounding_box_at_origin(self): box = Bounds(Point(-1, -1, -1), Point(1, 1, 1)) RayResult = namedtuple("RayResult", ["origin", "direction", "result"]) ray_results = [ RayResult(Point(5, 0.5, 0), Vector(-1, 0, 0), True), RayResult(Point(-5, 0.5, 0), Vector(1, 0, 0), True), RayResult(Point(0.5, 5, 0), Vector(0, -1, 0), True), RayResult(Point(0.5, -5, 0), Vector(0, 1, 0), True), RayResult(Point(0.5, 0, 5), Vector(0, 0, -1), True), RayResult(Point(0.5, 0, -5), Vector(0, 0, 1), True), RayResult(Point(0, 0.5, 0), Vector(0, 0, 1), True), RayResult(Point(-2, 0, 0), Vector(2, 4, 6), False), RayResult(Point(0, -2, 0), Vector(6, 2, 4), False), RayResult(Point(0, 0, -2), Vector(4, 6, 2), False), RayResult(Point(2, 0, 2), Vector(0, 0, -1), False), RayResult(Point(0, 2, 2), Vector(0, -1, 0), False), RayResult(Point(2, 2, 0), Vector(-1, 0, 0), False) ] for ray_result in ray_results: direction = Vector.normalize(ray_result.direction) r = Ray(ray_result.origin, direction) self.assertEqual(box.intersects(r), ray_result.result)
def __init__(self): ## Cria os tipos que estão disponíveis self.bounds = Bounds() self.action = Action() self.loop = Functions() self.math = Math() self.variables = Variables() ## Adicona os tipos acima em uma lista self.buttons = [ self.bounds, self.action, self.loop, self.math, self.variables ] ##Variavel que armazena o valor do botão que está ativo. Em caso de False, nenhum está self.buttonActive = False ## Define o Botão de Compilar self.sizeButtonComp = [75, 50] self.buttonComp = pygame.image.load( os.path.join("img", "buttonCompilar.png")).convert_alpha() self.buttonComp = pygame.transform.scale(self.buttonComp, self.sizeButtonComp) self.posButtonComp = [10, 480]
def __init__(self, quantization=1e4, id_key='id', property_transform=property_transform, system=False, simplify=False, stitch_poles=False): self.ln = Line(quantization) self.id_func = lambda x: x[id_key] self.quantization = quantization self.system = system self.property_transform = property_transform self.bounds = Bounds() if simplify: self.simplify = Simplify(simplify) else: self.simplify = False if stitch_poles: self.stitch_poles = Stitch() else: self.stitch_poles = False self.feature_dir = mkdtemp() self.feature_path = [] self.feature_db = {} self.feature_length = 0 class Coincidences(Types): def __init__(self, ln): self.ln = ln def line(self, line): for point in line: lines = self.ln.arcs.coincidence_lines(point) if not line in lines: lines.append(line) self.coincidences = Coincidences(self.ln)
def main(): RESOURCE_DIR = join(os.getcwd(), "resources") SETS = join(RESOURCE_DIR, "card_data", "sets.json") TEST_DIR = "test_images" MPLANTIN = join(RESOURCE_DIR, "fonts", "MPlantin.ttf") BELEREN_SC = join(RESOURCE_DIR, "fonts", "Beleren_small_caps.ttf") BELEREN = join(RESOURCE_DIR, "fonts", "Jace_Beleren_bold.ttf") MPLANTIN_BOLD = join(RESOURCE_DIR, "fonts", "MPlantin_bold.ttf") MPLANTIN_ITAL = join(RESOURCE_DIR, "fonts", "MPlantin_italic.ttf") RELAY = join(RESOURCE_DIR, "fonts", "Relay_medium.ttf") FC = "White" if os.path.isfile(SETS): with open(SETS, "r") as f: sets = json.load(f) else: raise ValueError("sets.json not found.") myset = "GRN" JSON = join(RESOURCE_DIR, "card_data", f"{myset}.json") """make directory in art""" if not os.path.isdir(join(RESOURCE_DIR, "art", myset)): # dir does not exist os.mkdir(join(RESOURCE_DIR, "art", myset)) """make directory in render dir""" if not os.path.isdir(join("test_images", "all_render", myset)): # dir does not exist os.mkdir(join("test_images", "all_render", myset)) """download set data""" if os.path.isfile(JSON): # load cards if card data exists with open(JSON, "r") as f: cards = json.load(f) else: # raise ValueError("No json found.") print(f"Downloading cards for {myset}") cards = [c.__dict__ for c in Card.where(set=myset).all()] with open(JSON, "w") as f: json.dump(cards, f, sort_keys=True, indent=4) """Add set count to sets.json if it does not exist""" if myset not in [s["code"] for s in sets]: sets.append({ "code": myset, "count": len(set(card['id'] for card in cards)) }) with open(SETS, "w") as f: json.dump(sets, f, indent=4) """Nice to haves""" # TODO adaptive_sharpen for ImageLayers # TODO left = l, bottom = b, top = t, right = r # TODO Justify rules text (MTG stretches text so that it fits better) # TODO Image overlay # TODO ImageLayers (move ColorLayers into new file with Image layers) # TODO \u0106 does not have a character in BELEREN_SMALL_CAPS # TODO """The evaluate method on FunctionAttribute does not know when to return evaluated_value and when not. A function to clear them (bounds not neccessary but can be treated the same for extra efficiency)""" # TODO """Need to do""" # TODO test to see if pre_render function is worth the hassle # pre_render is necessary, especially for update_bounds() # TODO remove pre_render probably because some layers' render depend variables other than content # TODO What I could do is wrap some variables eg. font, color, size (PTL) with decorator that will set layer to dirty # TODO If layer is dirty at render, re-render else return pre_render if it exists. # TODO Don't pre_render at content set, call render when updating_bounds # TODO render is a means to get width+/height and pre_render was implemented to make the process less expensive # TODO pre_render doesn't actually work with TextLayers due to the nature of asc/descender # TODO Ascender + Descender option for PointTextLayers # TODO layer.dirty_bounds would be necessary for render_shadow + color/gradient/image overlay # TODO layer.dirty_bounds and layer.dirty_content # TODO How will dirty work with templates + parents # TODO Each Layer.render() method is responsible for deciding whether or # not to return pre_render. """ Managing pre_render was challenging with just render(), but now with - render_boundary - shadow - color_overlay - and image_overlay and gradient_overlay to come it's too difficult. For now, render is called only in render(), render-like-methods (overlays etc) and update_bounds(). """ """Can't replicate""" # TODO Infinite while loop when SA references layer that doesn't exist # TODO Template.update_bounds() infinite loop layer.x.is_bounded error """ AddAttribute Weird behaviour here, with other FunctionAttributes setting ev should take place outside of try.except, but not AddAttribute.Pytest passes either way, so should write some more tests. """ # TODO possibly better land images # https://www.passionmagic.com/illustrations-exclusives-officielles-fournies-par-wizard/ # TODO Match lands to EXP # TODO # gap = 20 + 75 # i = 0 # cards = cards[:10] # cards = [c for c in cards if c["name"] == "Evolving Wilds"] # cards = [c for c in cards if c["name"] == "Dust Stalker"] # cards = [c for c in cards if c["name"] == "Blighted Gorge"] cards = [c for c in cards if c["name"] == "Doom Whisperer"] BORDER = 45 RULES_BORDER = 60 HEIGHT = 1050 WIDTH = 750 SET_DOT_LANG_WIDTH = 5 INFO_SIZE = 18 FONT_SIZE = 40 RULES_TEXT_SIZE = 25 la = AA(MA(SA("language.right"), SA("number.right")), NA(3)) lmiddle = AA( NA(WIDTH / 2), DivA(AA(SA("artist_brush.width"), NA(3), SA("artist.width")), NA(2), negative=True)) art_layers = { "bg": ColorBackgroundLayer("bg", content=Color("Black")), "art": FillIL("art", order=-5, XP50=NA(WIDTH / 2), top=NA(0), width=NA(WIDTH), height=NA(HEIGHT)), "shadow1": GradL("shadow1", start="#0000007F", end="Transparent", left=NA(0), width=NA(WIDTH), top=SA("name.bottom"), height=NA(200)), "shadow2": ColorLayer("shadow2", content="#0000007F", left=NA(0), width=NA(WIDTH), top=NA(0), bottom=SA("shadow1.top")), # "shadow3": ColorLayer("shadow3", content="Black", left=NA(0), # width=NA(WIDTH), bottom=NA(HEIGHT), top=SA("art.YP100")), # "shadow4": GradL("shadow4", start="Transparent", end="Black", left=NA(0), # width=NA(WIDTH), bottom=SA("art.YP100"), height=NA(200)) } no_content_reset = { "dot": PTL("dot", RELAY, 25, FC, content=".", left=AA(SA("set.right"), NA(SET_DOT_LANG_WIDTH)), ycenter=SA("set.ycenter")), "language": PTL("language", RELAY, INFO_SIZE, FC, content="EN", left=AA(SA("dot.right"), NA(SET_DOT_LANG_WIDTH)), base=NA(HEIGHT - BORDER)), "artist_brush": ResizeIL("artist_brush", content=join(RESOURCE_DIR, "artist_brush_white.png"), width=NA(20), left=lmiddle, height=SA("set.height"), bottom=NA(HEIGHT - BORDER)), "copyright": PTL("copyright", MPLANTIN, INFO_SIZE - 5, FC, right=NA(WIDTH - BORDER), bottom=SA("set.bottom")), } layers = { "name": PTL("name", BELEREN, FONT_SIZE, FC, left=NA(BORDER), base=NA(95)), "type": PTL("type", BELEREN, FONT_SIZE, FC, left=NA(BORDER), base=AA(SA("rules.top"), NA(-5))), "PT": PTL("PT", BELEREN, FONT_SIZE, FC, right=NA(WIDTH - BORDER), base=SA("set.base")), "loyalty": PTL("loyalty", BELEREN, FONT_SIZE, FC, right=NA(WIDTH - BORDER), base=SA("set.base")), "set": PTL("set", RELAY, INFO_SIZE, FC, left=NA(BORDER), base=NA(HEIGHT - BORDER)), "number": PTL("number", RELAY, INFO_SIZE, FC, left=NA(BORDER), base=AA(SA("set.cap"), NA(-3))), "rarity": PTL("rarity", RELAY, INFO_SIZE, FC, left=SA("artist_brush.left"), base=SA("number.base")), "artist": PTL("artist", BELEREN_SC, INFO_SIZE, FC, left=AA(SA("artist_brush.right"), NA(3)), base=SA("set.base")), "mana_cost": ManaCost("mana_cost", right=NA(WIDTH - BORDER), bottom=SA("name.base")), "rules": RulesText("rules", MPLANTIN, MPLANTIN_ITAL, RULES_TEXT_SIZE, FC, RULES_TEXT_SIZE - 4, left=NA(RULES_BORDER), right=NA(WIDTH - RULES_BORDER), bottom=AA(SA("PT.bottom"), NA(-FONT_SIZE), NA(-5))), } text_template = Template("text_temp", *layers.values(), *no_content_reset.values(), left=NA(0), width=NA(WIDTH), top=NA(0), height=NA(HEIGHT)) art_template = Template("art_temp", *art_layers.values(), order=-5, left=NA(0), width=NA(WIDTH), top=NA(0), height=NA(HEIGHT)) name = text_template.get_layer("name") # no_content_reset["copyright"].content = f"™ & © {datetime.now().year} Wizards of the Coast" # no_content_reset["copyright"].content = f"™ & © {datetime.now().year} WOTC" loga = math.ceil(math.log10(len(cards))) temp = Template("template", text_template, art_template, left=NA(0), width=NA(WIDTH), top=NA(0), height=NA(HEIGHT)) temp.mana_image_format = "svg" temp.resource_dir = RESOURCE_DIR max_card_length = max(len(c['name']) for c in cards) row = f"| {{:0{loga}}}/{{:0{loga}}} | {{}} | {{}} | {{:07.3f}} |" total = 0 for i, card in enumerate(cards): start_time = time.time() # thread = ElapsedTimeThread(i, len(cards) - 1, card['name'], row) # thread.start() # for layer in temp.layers: # layer.unset_content_and_pre_render() reset_layers = list(layers) + ["art"] for name in reset_layers: layer = temp.get_layer(name) layer.unset_content_and_pre_render() # layer.content = None # layer.pre_render = None if layer.name in card: layer.content = card[layer.name] temp.get_layer("artist_brush").pre_shadow = None if "Creature" in card["types"]: layers["PT"].content = f"{card['power']}/{card['toughness']}" count = 999 sset = [s for s in sets if s['code'] == card['set']] if len(sset) == 1: count = sset[0]["count"] number = card['number'].upper().zfill(math.ceil(math.log10(count))) layers["number"].content = f"{number}/{count}" rarity_colors = { "M": "#D15003", "R": "#DFBD6C", "U": "#C8C8C8", "C": FC, "L": FC, } rarity = card["rarity"][0].upper() layers["rarity"].color = rarity_colors[rarity] layers["rarity"].content = rarity rules = "" text_to_use = "text" if card[text_to_use] is not None: rules = card[text_to_use] if card["flavor"] is not None: rules += "".join( [f"\n<i>{f}</i>" for f in card['flavor'].split('\n')]) temp.get_layer("rules").content = rules art_path = join(RESOURCE_DIR, "art", card["set"], f"{card['name']}_{card['id']}.jpg") temp.get_layer("art").content = art_path if os.path.isfile( art_path) else None temp.update_bounds() # for render_bg = temp.get_layer("art_temp").render() # render_text_shadow = temp.get_layer("text_temp").shadow(-4, 4, sigma=2, # radius=0, color="Black") render_text = temp.get_layer("text_temp").render() image = render_bg.clone() xb = Bounds(start=BORDER - 10, end=WIDTH - BORDER + 10) yb = Bounds(start=temp.get_layer("type")["top"] - 10, end=temp.get_layer("rules")['bottom'] + 10) exp = render_bg.export_pixels(x=int(xb['start']), y=int(yb['start']), width=int(xb['full']), height=int(yb['full'])) with Image(width=int(xb['full']), height=int(yb['full']), background=Color("Transparent")) as blur_image: blur_image.import_pixels(width=int(xb['full']), height=int(yb['full']), channel_map="RGBA", storage="char", data=exp) with Image(width=blur_image.width, height=blur_image.height, background=Color("RGBA(0,0,0,0.2)")) as dark: blur_image.composite(dark, 0, 0) blur_image.blur(radius=10, sigma=5) with Image(width=blur_image.width, height=blur_image.height, background=Color("Black")) as mask: with Drawing() as ctx: ctx.fill_color = Color("White") ctx.rectangle(0, 0, width=mask.width, height=mask.height, radius=15) ctx(mask) apply_mask(blur_image, mask) image.composite(blur_image, left=int(xb['start']), top=int(yb['start'])) # image.composite(render_text_shadow, left=0, top=0) image.composite(render_text, left=0, top=0) # image = reduce(lambda x, y: x.composite(y, left=0, top=0), # (render_bg, render_text_shadow, render_text)) # image = temp.render(fresh=False) image.save(filename=join("test_images", "all_render", card['set'], f"{card['name']}_{card['id']}.bmp")) temp.unset_bounds_and_attributes() # thread.stop() # thread.join() delta = time.time() - start_time total += delta if delta < .250: color = "green" elif delta < .500: color = "yellow" else: color = "red" print(f"\r{row}".format( i, len(cards) - 1, colored(f"{card['name']: <{max_card_length}}", color), colored(f"{delta:03.3f}", color), total))
def bounds_of(self) -> Bounds: box = Bounds() box.add_point(self.p1) box.add_point(self.p2) box.add_point(self.p3) return box
from load_map import LoadMap from bounds import Bounds # from navigation import Navigation map = LoadMap('map.csv', start_row=4, start_col=0) current_loc = map.start_point bounds = Bounds(current_loc).interpret() print(bounds)
def __init__(self): self.loops = [] self.bounds = Bounds()
def test_creating_empty_bounding_box(self): box = Bounds() self.assertEqual(box.min, Point(math.inf, math.inf, math.inf)) self.assertEqual(box.max, Point(-math.inf, -math.inf, -math.inf))
def test_creating_bounding_box_with_volume(self): box = Bounds(Point(-1, -2, -3), Point(3, 2, 1)) self.assertEqual(box.min, Point(-1, -2, -3)) self.assertEqual(box.max, Point(3, 2, 1))
import numpy as np import math from bounds import Bounds from src.particleSwarmOptimization.pso import PSO from src.particleSwarmOptimization.structure.particle import Particle np.set_printoptions(suppress=True) errors = [] bounds = Bounds(-10, 10) # Create the pso with the nn weights num_particles = 7 inertia_weight = 0.729 cognitiveConstant = 1.49 socialConstant = 1.49 num_dimensions = 50 # Configure PSO pso = PSO(bounds, num_particles, inertia_weight, cognitiveConstant, socialConstant) def error(position): err = 0.0 for i in range(len(position)): xi = position[i] err += (xi * xi) - (10 * math.cos(2 * math.pi * xi)) + 10 return err
def bounds_of(self) -> Bounds: min = Point(-1, self.minimum, -1) max = Point(1, self.maximum, 1) return Bounds(min, max)
def bounds_of(self) -> Bounds: min = Point(-1, -1, -1) max = Point(1, 1, 1) return Bounds(min, max)
def test_add_bounding_box_to_another(self): box1 = Bounds(Point(-5, -2, 0), Point(7, 4, 4)) box2 = Bounds(Point(8, -7, -2), Point(14, 2, 8)) box1.add_box(box2) self.assertEqual(box1.min, Point(-5, -7, -2)) self.assertEqual(box1.max, Point(14, 4, 8))
def __init__(self, dimension): self.bounds = Bounds([Bound([0, 0], "continuous")] * dimension) self.global_optimum = [0] * dimension
def bounds_of(self) -> Bounds: min = Point(-self.radius, -self.radius, -self.radius) max = Point(self.radius, self.radius, self.radius) return Bounds(min, max)