예제 #1
0
    def _compute_fill_task(
        self, task: FloodFillTask
    ) -> Optional[Tuple[Chunk[b8], List[FloodFillTask]]]:
        """
        Create a fill mask for a chunk with a source mask
        :param task: fill task
        :return: A tuple of the resulting fill mask for this chunk and tasks for the neighboring chunks to fill
        """

        # Do nothing when out of bounds
        if self.is_out_of_bounds(task.index):
            return None

        # Get mask
        mask_chunk: Chunk[b8] = self.mask.ensure_chunk_at_index(task.index,
                                                                insert=False)

        # Method cache (prevent lookup in loop)
        __face_flip = ChunkFace.flip
        __face_slice = ChunkFace.slice

        if mask_chunk.is_filled():
            if mask_chunk.value:
                return (Chunk(task.index, mask_chunk.size, b8,
                              b8_False).set_fill(b8_True), [
                                  FloodFillTask(i, face=__face_flip(f))
                                  for f, i in ChunkGrid.iter_neighbors_indices(
                                      mask_chunk.index) if f != task.face
                              ])
            else:
                return Chunk(task.index, mask_chunk.size, b8, b8_False), []

        task_image: np.ndarray = task.image(mask_chunk.array_shape)
        assert task_image.shape == mask_chunk.array_shape
        if not task_image.any():
            # return Chunk(task.index, chunk.size, dtype=np_bool8).set_fill(False), []
            return None
        else:
            mask = mask_chunk.to_array()
            # Enforce that only "free" fields in mask are filled
            img = task_image & mask
            result = ndimage.binary_propagation(img, mask=mask).astype(b8)

            # Create tasks where propagation is possible
            tasks = []
            for f, i in ChunkGrid.iter_neighbors_indices(mask_chunk.index):
                face_slice = result[__face_slice(f)]
                if face_slice.all():
                    if f != task.face:
                        tasks.append(FloodFillTask(i, face=__face_flip(f)))
                elif face_slice.any():
                    tmp = np.full(mask_chunk.shape, b8_False, dtype=b8)
                    tmp[__face_slice(__face_flip(f))] = face_slice
                    tasks.append(FloodFillTask(i, image=tmp))
            return Chunk(task.index, mask_chunk.size, b8,
                         b8_False).set_array(result).cleanup(), tasks
def dilate_no_mask(image: ChunkGrid[bool_t], steps=1, structure: Optional[np.ndarray] = None) -> ChunkGrid[bool_t]:
    if structure is not None:
        assert structure.ndim == 2 and structure.shape == (3, 3)

    __pad_slice = slice(1, -1)

    result = image.astype(np.bool8)
    for step in range(steps):
        # Temporary result between each step
        tmp = result.copy(empty=True)
        # Dilate inner chunk
        # result.pad_chunks(1)

        for index, r in result.chunks.items():
            if r.is_filled() and r.value:
                tmp.ensure_chunk_at_index(index).set_fill(r.value)
                for f, ni in ChunkGrid.iter_neighbors_indices(r.index):
                    ch = tmp.ensure_chunk_at_index(ni)
                    if not (ch.is_filled() and ch.value):
                        arr = ch.to_array()
                        arr[f.flip().slice()] = True
                        ch.set_array(arr)
                        ch.cleanup()
                continue

            padded = result.padding_at(index, 1, corners=False, edges=False)
            if (not np.any(padded)):  # Skip, nothing to do
                continue

            # Do dilation
            dilated = ndimage.binary_dilation(padded, structure=structure)

            # Copy result to tmp
            ch = tmp.ensure_chunk_at_index(index)
            ch.set_array(dilated[1:-1, 1:-1, 1:-1])
            ch.cleanup()

            # Propagate to the next chunks
            for f in ChunkFace:  # type: ChunkFace
                s = dilated[f.slice(other=__pad_slice)]
                if np.any(s):
                    ch: Chunk = tmp.ensure_chunk_at_index(f.direction() + index)
                    arr = ch.to_array()
                    arr[f.flip().slice()] |= s
                    ch.set_array(arr)

        # Set result
        result = tmp
    result.cleanup(remove=True)
    return result
def diffuse(model: ChunkGrid[bool], repeat=1):
    """
    Diffuse the voxels in model to their neighboring voxels
    :param model: the model to diffuse
    :param repeat: number of diffusion steps
    :return: diffused model
    """
    kernel = np.zeros((3, 3, 3), dtype=float)
    kernel[1] = 1
    kernel[:, 1] = 1
    kernel[:, :, 1] = 1
    kernel /= np.sum(kernel)

    result = ChunkGrid(model.chunk_size, dtype=float, fill_value=1.0)
    result[model] = 0.0
    result.pad_chunks(repeat // result.chunk_size + 1)

    for r in range(repeat):
        tmp = result.copy(empty=True)
        for chunk in result.chunks:
            padded = chunk.padding(result, 1)
            ndimage.convolve(padded,
                             kernel,
                             output=padded,
                             mode='constant',
                             cval=1.0)
            conv = padded[1:-1, 1:-1, 1:-1]
            m = model.ensure_chunk_at_index(chunk.index, insert=False)
            if m.is_filled():
                if m.value:
                    tmp.ensure_chunk_at_index(chunk.index).set_fill(0.0)
                    continue
            else:
                conv[m.to_array()] = 0.0
            tmp.ensure_chunk_at_index(chunk.index).set_array(conv)
            # Expand chunks
            for f, i in ChunkGrid.iter_neighbors_indices(chunk.index):
                tmp.ensure_chunk_at_index(i)

        result = tmp

    result.cleanup(remove=True)
    return result
    data_min, data_max = np.min(data, axis=0), np.max(data, axis=0)
    data_delta_max = np.max(data_max - data_min)

    resolution = 64

    grid = ChunkGrid(16, dtype=int, fill_value=0)
    scaled = (data - data_min) * resolution / data_delta_max
    assert scaled.shape[1] == 3

    grid[scaled] = 1

    # Add padding
    filled = set(tuple(c.index) for c in grid.chunks)
    extra = set(
        tuple(n) for i in grid.chunks.keys()
        for f, n in grid.iter_neighbors_indices(i))
    for e in extra:
        grid.ensure_chunk_at_index(e)

    fill_mask = flood_fill_at((7, 9, 7), grid == 0)
    grid[fill_mask] = 3

    ren = VoxelRender()
    fig = ren.make_figure()
    fig.add_trace(ren.grid_voxel(grid == 1, opacity=1.0, flatshading=True))
    fig.add_trace(ren.grid_wireframe(grid == 1, opacity=1.0, size=2.0))
    # fig.add_trace(ren.grid_voxel(grid == 3, opacity=0.1, flatshading=True))
    fig.add_trace(CloudRender().make_scatter(scaled, marker=dict(size=1)))
    fig.show()