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()