From 8e5785b645c014a4c361e17aa642be7c4883a0ce Mon Sep 17 00:00:00 2001 From: amal Date: Fri, 18 Aug 2023 21:54:38 +0800 Subject: [PATCH 1/3] Reducing rotations based on bounding box constraint --- python/cubes.py | 29 +++++++++++++++++++--- python/libraries/resizing.py | 48 +++++++++++++++++++++++++++++++++++- python/libraries/rotation.py | 46 +++++++++++++++++++++++++++++++++- 3 files changed, 117 insertions(+), 6 deletions(-) diff --git a/python/cubes.py b/python/cubes.py index 1b5647a..df62b38 100644 --- a/python/cubes.py +++ b/python/cubes.py @@ -5,7 +5,7 @@ from libraries.resizing import expand_cube from libraries.packing import pack, unpack from libraries.renderer import render_shapes -from libraries.rotation import all_rotations +from libraries.rotation import all_rotations, one_diff_rot, all_diff_rot def log_if_needed(n, total_n): @@ -87,11 +87,32 @@ def get_canonical_packing(polycube: np.ndarray, """ max_id = b'\x00' - for cube_rotation in all_rotations(polycube): + shape = polycube.shape + + if shape[0] == shape[1] == shape[2]: + for cube_rotation in all_rotations(polycube): + this_id = pack(cube_rotation) + if this_id in known_ids: + return this_id + if this_id > max_id: + max_id = this_id + return max_id + + for idx in range(0, 3): + if shape[idx] == shape[(idx + 1) % 3]: + for cube_rotation in one_diff_rot(polycube, (idx + 2) % 3, [idx, (idx + 1) % 3]): + this_id = pack(cube_rotation) + if this_id in known_ids: + return this_id + if this_id > max_id: + max_id = this_id + return max_id + + for cube_rotation in all_diff_rot(polycube): this_id = pack(cube_rotation) - if (this_id in known_ids): + if this_id in known_ids: return this_id - if (this_id > max_id): + if this_id > max_id: max_id = this_id return max_id diff --git a/python/libraries/resizing.py b/python/libraries/resizing.py index 517f5f4..d2937de 100644 --- a/python/libraries/resizing.py +++ b/python/libraries/resizing.py @@ -23,6 +23,52 @@ def crop_cube(cube: np.ndarray) -> np.ndarray: return cube +def fix_axis(cube): + """ + Rotate cube to make boundary sizes in sorted order. + + Ex : if input cube.shape = (4, 1, 2) this method rotate it to be (1, 2, 4) + + Parameters: + cube (np.array): 3D Numpy byte array where 1 values indicate polycube positions + + Returns: + np.array: Cropped 3D Numpy byte array equivalent to cube, but with no zero padding + + """ + if cube.shape == tuple(sorted(cube.shape)): + return cube + + if cube.shape == tuple(sorted(cube.shape, reverse=True)): + return np.rot90(cube, 1, (0, 2)) + + if cube.shape[0] == cube.shape[1] or cube.shape[1] == cube.shape[2]: + if cube.shape[0] < cube.shape[2]: + return cube + else: + return np.rot90(cube, 1, (0, 2)) + + if cube.shape[0] == cube.shape[2]: + if cube.shape[0] < cube.shape[1]: + return np.rot90(cube, 1, (1, 2)) + else: + return np.rot90(cube, 1, (0, 1)) + + if cube.shape[0] < cube.shape[2] < cube.shape[1]: + return np.rot90(cube, 1, (1, 2)) + + if cube.shape[1] < cube.shape[2] < cube.shape[0]: + return np.rot90(np.rot90(cube, 1, (0, 1)), 1, (1, 2)) + + if cube.shape[1] < cube.shape[0] < cube.shape[2]: + return np.rot90(cube, 1, (0, 1)) + + if cube.shape[2] < cube.shape[0] < cube.shape[1]: + return np.rot90(np.rot90(cube, 1, (1, 2)), 1, (0, 1)) + + print("error", cube.shape) + exit(2) + def expand_cube(cube: np.ndarray) -> Generator[np.ndarray, None, None]: """ Expands a polycube by adding single blocks at all valid locations. @@ -53,4 +99,4 @@ def expand_cube(cube: np.ndarray) -> Generator[np.ndarray, None, None]: for (x, y, z) in zip(exp[0], exp[1], exp[2]): new_cube = np.array(cube) new_cube[x, y, z] = 1 - yield crop_cube(new_cube) + yield fix_axis(crop_cube(new_cube)) diff --git a/python/libraries/rotation.py b/python/libraries/rotation.py index e20edb6..51aa5c2 100644 --- a/python/libraries/rotation.py +++ b/python/libraries/rotation.py @@ -4,7 +4,7 @@ def all_rotations(polycube: np.ndarray) -> Generator[np.ndarray, None, None]: """ - Calculates all rotations of a polycube. + Calculates rotations of a polycube when bounding box size in all axes are equal(Ex (3, 3, 3)). Adapted from https://stackoverflow.com/questions/33190042/how-to-calculate-all-24-rotations-of-3d-array. This function computes all 24 rotations around each of the axis x,y,z. It uses numpy operations to do this, to avoid unecessary copies. @@ -36,3 +36,47 @@ def single_axis_rotation(polycube, axes): # rotate about axis 2, 8 rotations about axis 1 yield from single_axis_rotation(np.rot90(polycube, axes=(0, 1)), (0, 2)) yield from single_axis_rotation(np.rot90(polycube, -1, axes=(0, 1)), (0, 2)) + + +def one_diff_rot(cube, diff_axis, equal_axes): + """ + Calculates rotations of a polycube when bounding box size in two axes are equal, but one is different. (Ex (2, 2, 3)). + Only 8 rotations can be done without breaking the bounding box. + + Parameters: + polycube (np.array): 3D Numpy byte array where 1 values indicate polycube positions + diff_axis (integer): Axis which has different size + equal_axes (integer array): Axes which have same size + + Returns: + generator(np.array): Yields new rotations of this cube about all axes + + """ + for _ in range(0, 4): + yield cube + cube = np.rot90(cube, 1, equal_axes) + + cube = np.rot90(cube, 2, (diff_axis, equal_axes[0])) + + for _ in range(0, 4): + yield cube + cube = np.rot90(cube, 1, equal_axes) + + +def all_diff_rot(cube): + """ + Calculates rotations of a polycube when bounding box size in all axes are different. (Ex (1, 4, 6)). + Only 4 rotations can be done without breaking the bounding box. + + Parameters: + polycube (np.array): 3D Numpy byte array where 1 values indicate polycube positions + + Returns: + generator(np.array): Yields new rotations of this cube about all axes + + """ + yield cube + yield np.rot90(cube, 2, (0, 1)) + yield np.rot90(cube, 2, (0, 2)) + yield np.rot90(cube, 2, (1, 2)) + From aaf49b530538c25ae8f7201fa2a7a6c520f58b6d Mon Sep 17 00:00:00 2001 From: amal Date: Fri, 18 Aug 2023 21:54:38 +0800 Subject: [PATCH 2/3] Reducing rotations based on bounding box constraint --- python/cubes.py | 29 +++++++++++++--- python/libraries/resizing.py | 64 +++++++++++++++++++++++++++++++----- python/libraries/rotation.py | 46 +++++++++++++++++++++++++- 3 files changed, 126 insertions(+), 13 deletions(-) diff --git a/python/cubes.py b/python/cubes.py index 1b5647a..df62b38 100644 --- a/python/cubes.py +++ b/python/cubes.py @@ -5,7 +5,7 @@ from libraries.resizing import expand_cube from libraries.packing import pack, unpack from libraries.renderer import render_shapes -from libraries.rotation import all_rotations +from libraries.rotation import all_rotations, one_diff_rot, all_diff_rot def log_if_needed(n, total_n): @@ -87,11 +87,32 @@ def get_canonical_packing(polycube: np.ndarray, """ max_id = b'\x00' - for cube_rotation in all_rotations(polycube): + shape = polycube.shape + + if shape[0] == shape[1] == shape[2]: + for cube_rotation in all_rotations(polycube): + this_id = pack(cube_rotation) + if this_id in known_ids: + return this_id + if this_id > max_id: + max_id = this_id + return max_id + + for idx in range(0, 3): + if shape[idx] == shape[(idx + 1) % 3]: + for cube_rotation in one_diff_rot(polycube, (idx + 2) % 3, [idx, (idx + 1) % 3]): + this_id = pack(cube_rotation) + if this_id in known_ids: + return this_id + if this_id > max_id: + max_id = this_id + return max_id + + for cube_rotation in all_diff_rot(polycube): this_id = pack(cube_rotation) - if (this_id in known_ids): + if this_id in known_ids: return this_id - if (this_id > max_id): + if this_id > max_id: max_id = this_id return max_id diff --git a/python/libraries/resizing.py b/python/libraries/resizing.py index 517f5f4..64d0bfc 100644 --- a/python/libraries/resizing.py +++ b/python/libraries/resizing.py @@ -1,6 +1,7 @@ -import numpy as np from typing import Generator +import numpy as np + def crop_cube(cube: np.ndarray) -> np.ndarray: """ @@ -23,6 +24,53 @@ def crop_cube(cube: np.ndarray) -> np.ndarray: return cube +def fix_axis(cube): + """ + Rotate cube to make boundary sizes in sorted order. + + Ex : if input cube.shape = (4, 1, 2) this method rotate it to be (1, 2, 4) + + Parameters: + cube (np.array): 3D Numpy byte array where 1 values indicate polycube positions + + Returns: + np.array: Cropped 3D Numpy byte array equivalent to cube, but with no zero padding + + """ + if cube.shape == tuple(sorted(cube.shape, reverse=True)): + return cube + + if cube.shape == tuple(sorted(cube.shape)): + return np.rot90(cube, 1, (0, 2)) + + if cube.shape[0] < cube.shape[2] < cube.shape[1]: + return np.rot90(np.rot90(cube, 1, (0, 2)), 1, (1, 2)) + + if cube.shape[0] == cube.shape[2]: + if cube.shape[0] < cube.shape[1]: + return np.rot90(cube, 1, (0, 1)) + else: + return np.rot90(cube, 1, (1, 2)) + + if cube.shape[0] == cube.shape[1] or cube.shape[1] == cube.shape[2]: + if cube.shape[0] < cube.shape[2]: + return cube + else: + return np.rot90(cube, 1, (0, 2)) + + if cube.shape[2] < cube.shape[0] < cube.shape[1]: + return np.rot90(cube, 1, (0, 1)) + + if cube.shape[1] < cube.shape[0] < cube.shape[2]: + return np.rot90(np.rot90(cube, 1, (0, 1)), 1, (1, 2)) + + if cube.shape[1] < cube.shape[2] < cube.shape[0]: + return np.rot90(cube, 1, (2, 1)) + + print("error", cube.shape) + exit(2) + + def expand_cube(cube: np.ndarray) -> Generator[np.ndarray, None, None]: """ Expands a polycube by adding single blocks at all valid locations. @@ -41,16 +89,16 @@ def expand_cube(cube: np.ndarray) -> Generator[np.ndarray, None, None]: output_cube = np.array(cube) xs, ys, zs = cube.nonzero() - output_cube[xs+1, ys, zs] = 1 - output_cube[xs-1, ys, zs] = 1 - output_cube[xs, ys+1, zs] = 1 - output_cube[xs, ys-1, zs] = 1 - output_cube[xs, ys, zs+1] = 1 - output_cube[xs, ys, zs-1] = 1 + output_cube[xs + 1, ys, zs] = 1 + output_cube[xs - 1, ys, zs] = 1 + output_cube[xs, ys + 1, zs] = 1 + output_cube[xs, ys - 1, zs] = 1 + output_cube[xs, ys, zs + 1] = 1 + output_cube[xs, ys, zs - 1] = 1 exp = (output_cube ^ cube).nonzero() for (x, y, z) in zip(exp[0], exp[1], exp[2]): new_cube = np.array(cube) new_cube[x, y, z] = 1 - yield crop_cube(new_cube) + yield fix_axis(crop_cube(new_cube)) diff --git a/python/libraries/rotation.py b/python/libraries/rotation.py index e20edb6..51aa5c2 100644 --- a/python/libraries/rotation.py +++ b/python/libraries/rotation.py @@ -4,7 +4,7 @@ def all_rotations(polycube: np.ndarray) -> Generator[np.ndarray, None, None]: """ - Calculates all rotations of a polycube. + Calculates rotations of a polycube when bounding box size in all axes are equal(Ex (3, 3, 3)). Adapted from https://stackoverflow.com/questions/33190042/how-to-calculate-all-24-rotations-of-3d-array. This function computes all 24 rotations around each of the axis x,y,z. It uses numpy operations to do this, to avoid unecessary copies. @@ -36,3 +36,47 @@ def single_axis_rotation(polycube, axes): # rotate about axis 2, 8 rotations about axis 1 yield from single_axis_rotation(np.rot90(polycube, axes=(0, 1)), (0, 2)) yield from single_axis_rotation(np.rot90(polycube, -1, axes=(0, 1)), (0, 2)) + + +def one_diff_rot(cube, diff_axis, equal_axes): + """ + Calculates rotations of a polycube when bounding box size in two axes are equal, but one is different. (Ex (2, 2, 3)). + Only 8 rotations can be done without breaking the bounding box. + + Parameters: + polycube (np.array): 3D Numpy byte array where 1 values indicate polycube positions + diff_axis (integer): Axis which has different size + equal_axes (integer array): Axes which have same size + + Returns: + generator(np.array): Yields new rotations of this cube about all axes + + """ + for _ in range(0, 4): + yield cube + cube = np.rot90(cube, 1, equal_axes) + + cube = np.rot90(cube, 2, (diff_axis, equal_axes[0])) + + for _ in range(0, 4): + yield cube + cube = np.rot90(cube, 1, equal_axes) + + +def all_diff_rot(cube): + """ + Calculates rotations of a polycube when bounding box size in all axes are different. (Ex (1, 4, 6)). + Only 4 rotations can be done without breaking the bounding box. + + Parameters: + polycube (np.array): 3D Numpy byte array where 1 values indicate polycube positions + + Returns: + generator(np.array): Yields new rotations of this cube about all axes + + """ + yield cube + yield np.rot90(cube, 2, (0, 1)) + yield np.rot90(cube, 2, (0, 2)) + yield np.rot90(cube, 2, (1, 2)) + From 0c547e3ae80974c1d03f877e6d933ed070af0465 Mon Sep 17 00:00:00 2001 From: amal Date: Wed, 23 Aug 2023 23:09:09 +0800 Subject: [PATCH 3/3] Fix fix-axis method comment --- python/libraries/resizing.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/libraries/resizing.py b/python/libraries/resizing.py index 652205e..d152b0b 100644 --- a/python/libraries/resizing.py +++ b/python/libraries/resizing.py @@ -25,15 +25,15 @@ def crop_cube(cube: np.ndarray) -> np.ndarray: def fix_axis(cube): """ - Rotate cube to make boundary sizes in sorted order. + Rotate cube to make boundary sizes in descending order. - Ex : if input cube.shape = (4, 1, 2) this method rotate it to be (1, 2, 4) + Ex : if input cube.shape = (4, 1, 2) this method rotate it to be (4, 2, 1) Parameters: cube (np.array): 3D Numpy byte array where 1 values indicate polycube positions Returns: - np.array: Cropped 3D Numpy byte array equivalent to cube, but with no zero padding + np.array: Rotated 3D Numpy byte array equivalent to cube, but with descending size orientation """ if cube.shape == tuple(sorted(cube.shape, reverse=True)):