geometry.py

5.8 kB · python · 151 lines

1import numpy as np2from copy import deepcopy3from typing import List, Tuple, TYPE_CHECKING4from mrlypy.core.errors import MrlyError56if TYPE_CHECKING:7    from .models import Cell3d89# PUBLIC - IMMUTABLE1011def merge_3d(cells: List["Cell3d"], width: int, height: int, depth: int) -> "Cell3d":12    from .models import Cell3d13    if not cells:14        raise MrlyError("Cannot merge an empty list of cells.")15    if len(cells) != width * height * depth:16        raise MrlyError(f"len(cells) != width * height * depth: {len(cells)} != {width} * {height} * {depth}")17    first_cell = cells[0]18    cell_width, cell_height, cell_depth = first_cell.width, first_cell.height, first_cell.depth19    total_width = width * cell_width20    total_height = height * cell_height21    total_depth = depth * cell_depth22    new_cell = Cell3d(width=total_width, height=total_height, depth=total_depth)23    for i, cell in enumerate(cells):24        if cell.width != cell_width or cell.height != cell_height or cell.depth != cell_depth:25            raise MrlyError("All cells in a merge operation must have the same dimensions.")26        x = i % width27        y = (i // width) % height28        z = i // (width * height)29        start_x = x * cell_width30        end_x = start_x + cell_width31        start_y = y * cell_height32        end_y = start_y + cell_height33        start_z = z * cell_depth34        end_z = start_z + cell_depth35        if cell._types is not None:36            new_cell.types[start_z:end_z, start_y:end_y, start_x:end_x] = cell.types37        if cell._colors is not None:38            new_cell.colors[start_z:end_z, start_y:end_y, start_x:end_x] = cell.colors39        if cell._tags is not None:40            new_cell.tags[start_z:end_z, start_y:end_y, start_x:end_x] = cell.tags41    return new_cell4243def combine_3d(cell_1: "Cell3d", cell_2: "Cell3d") -> "Cell3d":44    from .models import Cell3d45    new_types = np.kron(cell_1.types, cell_2.types).astype(np.uint8)46    return Cell3d(types=new_types)4748def magic_3d(cells: List["Cell3d"]) -> "Cell3d":49    if len(cells) < 2:50        raise MrlyError("Magic composition requires at least two cells.")51    new_cell = combine_3d(cells[0], cells[1])52    for i in range(2, len(cells)):53        new_cell = combine_3d(new_cell, cells[i])54    return new_cell5556def special_3d(mask: np.ndarray, cell: "Cell3d") -> "Cell3d":57    depth, height, width = mask.shape58    new_cells = []59    for z in range(depth):60        for y in range(height):61            for x in range(width):62                rotation_index = mask[z, y, x]63                new_cell = deepcopy(cell).rotate(k=int(rotation_index))64                new_cells.append(new_cell)65    return merge_3d(new_cells, width, height, depth)6667def mosaic_3d(mask: np.ndarray, cells: List["Cell3d"]) -> "Cell3d":68    depth, height, width = mask.shape69    new_cells = []70    for z in range(depth):71        for y in range(height):72            for x in range(width):73                cell_index = mask[z, y, x]74                new_cell = deepcopy(cells[cell_index])75                new_cells.append(new_cell)76    return merge_3d(new_cells, width, height, depth)7778# PRIVATE - MUTABLE7980def invert_3d(cell: "Cell3d") -> "Cell3d":81    if cell._types is not None:82        cell.types = 1 - cell.types83    return cell8485def pad_3d(cell: "Cell3d", count: int = 1, value: int = 0) -> "Cell3d":86    if cell._types is not None:87        cell.types = np.pad(cell.types, count, mode="constant", constant_values=value)88    if cell._colors is not None:89        cell.colors = np.pad(cell.colors, count, mode="constant", constant_values=value)90    if cell._tags is not None:91        cell.tags = np.pad(cell.tags, count, mode="constant", constant_values=value)92    return cell9394def rotate_3d(cell: "Cell3d", k: int = 1, axes: Tuple[int, int] = (1, 2)) -> "Cell3d":95    if cell._types is not None:96        cell.types = np.rot90(cell.types, k=k, axes=axes)97    if cell._colors is not None:98        cell.colors = np.rot90(cell.colors, k=k, axes=axes)99    if cell._tags is not None:100        cell.tags = np.rot90(cell.tags, k=k, axes=axes)101    return cell102103def fractal_3d(cell: "Cell3d", level: int = 1) -> "Cell3d":104    if level < 1:105        raise MrlyError("Fractal level must be at least 1.")106    if level == 1:107        return cell108    new_types = cell.types109    for _ in range(1, level):110        new_types = np.kron(new_types, cell.types)111    cell.types = new_types.astype(np.uint8)112    cell.colors = None113    cell.tags = None114    return cell115116def tile_3d(cell: "Cell3d", width: int, height: int, depth: int) -> "Cell3d":117    if cell._types is not None:118        cell.types = np.tile(cell.types, (depth, height, width))119    if cell._colors is not None:120        cell.colors = np.tile(cell.colors, (depth, height, width, 1))121    if cell._tags is not None:122        cell.tags = np.tile(cell.tags, (depth, height, width))123    return cell124125def layers_3d(cell: "Cell3d", dtype: np.dtype = np.dtype(np.uint8)) -> "Cell3d":126    depth, height, width = cell.depth, cell.height, cell.width127    z_indices, y_indices, x_indices = np.indices((depth, height, width))128    center_z = (depth - 1) / 2129    center_y = (height - 1) / 2130    center_x = (width - 1) / 2131    distance_z = np.abs(z_indices - center_z)132    distance_y = np.abs(y_indices - center_y)133    distance_x = np.abs(x_indices - center_x)134    tags = np.floor(distance_x + distance_y + distance_z)135    cell.tags = tags.astype(dtype)136    return cell137138def neighbors_3d(cell: "Cell3d", mode: str = "constant") -> "Cell3d":139    kernel = np.ones((3, 3, 3), dtype=np.uint8)140    kernel[1, 1, 1] = 0141    padded = np.pad(cell.types, pad_width=1, mode=mode)142    result = np.zeros_like(cell.types, dtype=np.uint8)143    d, h, w = cell.types.shape144    for z in range(d):145        for y in range(h):146            for x in range(w):147                window = padded[z:z+3, y:y+3, x:x+3]148                count = np.sum(window * kernel)149                result[z, y, x] = count150    cell.tags = result151    return cell