def test_eq_3(self): a = ChunkGrid(1, bool, False) b = ChunkGrid(1, bool, False) x = a == b self.assertEqual(0, len(x.chunks)) self.assertTrue(x.fill_value)
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 test__getitem_empty(self): a = ChunkGrid(2, int, -1) a.set_or_fill((0, 0, 0), 1) res = a[-1:1, -1:1, -1:1] expected = np.full((2, 2, 2), -1) expected[1, 1, 1] = 1 self.assertEqual(str(expected), str(res))
def test_reflected(self): grid = ChunkGrid(2, float, -1.0) grid.set_value((0, 0, 0), 0.5) grid.set_value((0, 0, 1), 1.0) result = (0 < grid) & (grid < 1.0) actual = result.to_dense() expected = np.zeros((2, 2, 2), dtype=bool) expected[0, 0, 0] = True self.assertEqual(str(expected), str(actual))
def test_eq_1(self): a = ChunkGrid(2, bool, False) b = ChunkGrid(2, bool, False) a.set_value((0, 0, 0), True) result = (a == b).to_dense() self.assertIsInstance((a == b), ChunkGrid) expected = np.ones((2, 2, 2), dtype=bool) expected[0, 0, 0] = False assert result.shape == expected.shape self.assertEqual(str(expected), str(result))
def grid_segments(self) -> Tuple[ChunkGrid[np.bool8], ChunkGrid[np.bool8]]: to_voxel = MinCut.to_voxel segments = self.segments() if self._grid_segment0 is None: self._grid_segment0 = ChunkGrid(self.crust.chunk_size, np.bool8, False) self._grid_segment0[[p for node, s in zip(self.nodes, segments) if s == False for p in to_voxel(node)]] = True if self._grid_segment1 is None: self._grid_segment1 = ChunkGrid(self.crust.chunk_size, np.bool8, False) self._grid_segment1[[p for node, s in zip(self.nodes, segments) if s == True for p in to_voxel(node)]] = True return self._grid_segment0, self._grid_segment1
def _test_inplace_modified(self, op, a: ChunkGrid, a0: ChunkGrid, b: ChunkGrid, b0: ChunkGrid, inplace: bool): # Test if inplace (or not) is working intended ad = a.to_dense() a0d = a0.to_dense() bd = b.to_dense() b0d = b0.to_dense() if inplace: # Inplace modification - change opa = ad == op(ad, bd) opb = bd == b0d else: # Not inplace modification - no change opa = ad == a0d opb = bd == b0d self.assertTrue(np.all(opa), f"Failure-Inplace-{inplace} {op}! \n{ad}\n-------\n{a0d}") self.assertTrue(np.all(opb), f"Failure-Inplace-{inplace} {op}! \n{bd}\n-------\n{b0d}")
def test_set_pos(self): a = ChunkGrid(2, bool, False) a.set_value((0, 0, 0), True) a.set_value((2, 2, 2), True) self.assertFalse(a.chunks[0, 0, 0].is_filled()) result = a.to_dense() expected = np.zeros((4, 4, 4), dtype=bool) expected[0, 0, 0] = True expected[2, 2, 2] = True assert result.shape == expected.shape self.assertEqual(str(expected), str(result))
def test_special_dtype(self): t = np.dtype((np.int, (3,))) a = ChunkGrid(2, dtype=t, fill_value=np.zeros(3)) a.set_value((0, 0, 0), np.ones(3)) a.set_value((2, 2, 2), np.ones(3)) dense = a.to_dense() expected = np.zeros((4, 4, 4), dtype=t) expected[0, 0, 0] = 1 expected[2, 2, 2] = 1 self.assertEqual(expected.shape, dense.shape) self.assertEqual(str(expected), str(dense))
def test_single_negative(self): a = ChunkGrid(2, bool, False) a.set_value((0, 0, 0), True) res = dilate(a, steps=1) self.assertEqual(4, len(res.chunks)) c0 = res.chunks.get((0, 0, 0)) self.assertIsNotNone(c0) self.assertFalse(c0.is_filled()) expected = np.zeros((4, 4, 4), dtype=bool) expected[2, 2, 1:4] = True expected[2, 1:4, 2] = True expected[1:4, 2, 2] = True self.assertEqual(str(expected), str(res.to_dense()))
def test_filled(self): a = ChunkGrid(2, bool, False) a.ensure_chunk_at_index((0, 0, 0)).set_fill(True) res = dilate(a, steps=1) self.assertEqual(7, len(res.chunks)) c0 = res.chunks.get((0, 0, 0)) self.assertIsNotNone(c0) self.assertTrue(c0.is_filled()) self.assertTrue(c0.value) expected = np.zeros((6, 6, 6), dtype=bool) expected[2:-2, 2:-2, 1:-1] = True expected[2:-2, 1:-1, 2:-2] = True expected[1:-1, 2:-2, 2:-2] = True self.assertEqual(str(expected), str(res.to_dense()))
def flood_fill_at(position: Vec3i, mask: ChunkGrid, max_steps: Optional[int] = None, verbose=False, **kwargs) -> ChunkGrid[b8]: image = ChunkGrid(mask.chunk_size, b8, False) image[position] = True return flood_fill(image, mask, max_steps, verbose=verbose, **kwargs)
def __init__(self, mask: ChunkGrid, verbose=False): self.mask: ChunkGrid[b8] = mask.copy(dtype=b8) self.mask.cleanup(remove=True).pad_chunks(1) self.verbose = verbose self.__mask_chunks_get = self.mask.chunks.get self.min, self.max = self.mask.chunks.minmax(True) self.min -= 1 self.max += 1
def grid_normals(surface: ChunkGrid[np.bool8], outer: ChunkGrid[np.bool8], normal_kernel: Optional[np.ndarray] = None) \ -> ChunkGrid[Vec3f]: normal_pos, normal_val = detect_normals(surface, outer, normal_kernel) normals: ChunkGrid[np.float32] = ChunkGrid(surface.chunk_size, np.dtype((np.float32, (3, ))), 0.0) normals[normal_pos] = normal_val return normals
def test_padding_1(self): a = ChunkGrid(2, int, 0) expected = np.zeros((6, 6, 6), dtype=int) for n, i in enumerate(np.ndindex(6, 6, 6)): pos = np.array(i, dtype=int) - 2 a.set_value(pos, n + 1) expected[i] = n + 1 data = a.padding_at((0, 0, 0), 2) self.assertEqual(expected.shape, data.shape) self.assertEqual(str(expected), str(data)) expected2 = expected[1:-1, 1:-1, 1:-1] data2 = a.padding_at((0, 0, 0), 1) self.assertEqual(expected2.shape, data2.shape) self.assertEqual(str(expected2), str(data2)) expected3_tmp = np.zeros((6, 6, 6), dtype=int) for n, i in enumerate(np.ndindex(6, 6, 6)): expected3_tmp[i] = n + 1 expected3 = np.zeros((8, 8, 8), dtype=int) expected3[1:-1, 1:-1, 1:-1] = expected3_tmp data3 = a.padding_at((0, 0, 0), 3) self.assertEqual(expected3.shape, data3.shape) self.assertEqual(str(expected3), str(data3))
def test_padding_vec3_4(self): dtype = np.dtype((int, (3,))) a = ChunkGrid(2, dtype, np.zeros(3)) expected = np.zeros((6, 6, 6, 3), dtype=int) for n, i in enumerate(np.ndindex(6, 6, 6)): pos = np.array(i, dtype=int) - 2 value = np.array([1, 2, 3]) + (n + 1) * 3 a.set_value(pos, value) s = np.sum(pos < 0) + np.sum(2 <= pos) if s >= 2: continue expected[i] = value data = a.padding_at((0, 0, 0), 2, corners=False, edges=False) self.assertEqual(expected.shape, data.shape) self.assertEqual(str(expected), str(data)) expected2 = expected[1:-1, 1:-1, 1:-1] data2 = a.padding_at((0, 0, 0), 1, corners=False, edges=False) self.assertEqual(expected2.shape, data2.shape) self.assertEqual(str(expected2), str(data2)) expected3_tmp = np.zeros((6, 6, 6, 3), dtype=int) for n, i in enumerate(np.ndindex(6, 6, 6)): value = np.array([1, 2, 3]) + (n + 1) * 3 expected3_tmp[i] = value expected3 = np.zeros((8, 8, 8, 3), dtype=int) expected3[1:-1, 1:-1, 1:-1] = expected3_tmp data3 = a.padding_at((0, 0, 0), 3, corners=False, edges=False) self.assertEqual(expected3.shape, data3.shape) self.assertEqual(str(expected3), str(data3))
def test_padding_2(self): a = ChunkGrid(2, int, 0) expected = np.zeros((6, 6, 6), dtype=int) for n, i in enumerate(np.ndindex(6, 6, 6)): pos = np.array(i, dtype=int) - 2 a.set_value(pos, n + 1) s = np.sum(pos < 0) + np.sum(2 <= pos) if s >= 2: continue expected[i] = n + 1 data = a.padding_at((0, 0, 0), 2, corners=False, edges=False) self.assertEqual(expected.shape, data.shape) self.assertEqual(str(expected), str(data)) expected2 = expected[1:-1, 1:-1, 1:-1] data2 = a.padding_at((0, 0, 0), 1, corners=False, edges=False) self.assertEqual(expected2.shape, data2.shape) self.assertEqual(str(expected2), str(data2)) expected3_tmp = np.zeros((6, 6, 6), dtype=int) for n, i in enumerate(np.ndindex(6, 6, 6)): expected3_tmp[i] = n + 1 expected3 = np.zeros((8, 8, 8), dtype=int) expected3[1:-1, 1:-1, 1:-1] = expected3_tmp data3 = a.padding_at((0, 0, 0), 3, corners=False, edges=False) self.assertEqual(expected3.shape, data3.shape) self.assertEqual(str(expected3), str(data3))
def test_eq_2(self): a = ChunkGrid(2, bool, False) b = ChunkGrid(2, bool, False) a.set_value((0, 0, 0), True) a.set_value((2, 2, 2), True) comp = a == b self.assertIsInstance(comp, ChunkGrid) self.assertIs(comp.dtype.type, np.bool8) expected = np.ones((4, 4, 4), dtype=bool) expected[0, 0, 0] = False expected[2, 2, 2] = False result = comp.to_dense() assert result.shape == expected.shape self.assertEqual(str(expected), str(result))
def test_getitem(self): CS = 2 shape = (CS * 3, CS * 3, CS * 3) soll = np.arange(shape[0] * shape[1] * shape[2]).reshape(shape) a = ChunkGrid(CS, int, -1) for u in range(shape[0] // CS): for v in range(shape[1] // CS): for w in range(shape[2] // CS): x, y, z = u * CS, v * CS, w * CS index = (u, v, w) a.ensure_chunk_at_index(index).set_array(soll[x:x + CS, y:y + CS, z:z + CS]) dense = a.to_dense() self.assertEqual(soll.shape, dense.shape) self.assertEqual(str(soll), str(dense)) self.assertEqual(str(soll[1: shape[0] - 1, 1: shape[1] - 1, 1: shape[2] - 1]), str(a[1: shape[0] - 1, 1: shape[1] - 1, 1: shape[2] - 1]))
def make_value_scatter(self, grid: ChunkGrid, mask: ChunkGrid[bool], **kwargs): items = list(grid.items(mask=mask)) points, values = zip(*items) # type: Sequence[Vec3i], Sequence pts = np.array(points, dtype=np.float32) + 0.5 values = np.array(values) merge_default(kwargs, marker=dict(color=values)) return self.make_scatter(pts, **kwargs)
def test_padding(self): grid = ChunkGrid(2, int, -1) grid.ensure_chunk_at_index((0, 0, 0)).set_fill(1) grid.ensure_chunk_at_index((0, 0, 1)).set_fill(2) grid.ensure_chunk_at_index((0, 1, 0)).set_fill(3) grid.ensure_chunk_at_index((0, 1, 1)).set_fill(4) t = grid.chunks[(0, 0, 1)] t.set_array(np.array([ [(111, 112), (121, 122)], [(211, 212), (221, 222)] ])) expected1 = t.to_array()[:, :, 0] c = grid.chunks.get((0, 0, 0)) pad = c.padding(grid, 1) actual = pad expected = np.ones((4, 4, 4), int) * -1 expected[1:3, 1:3, 1:3] = 1 expected[1:-1, 1:-1, -1] = expected1 expected[1:-1, -1, 1:-1] = 3 self.assertEqual(expected.shape, actual.shape) # self.assertTrue(np.all(actual == expected), f"Failure! \n{actual}\n-------\n{expected}") self.assertEqual(str(expected), str(actual))
def fill_at_pos(self, position: Vec3i, max_steps: Optional[int] = None) -> ChunkGrid[bool]: """ Start the flood fill at a position :param position: the starting point :param max_steps: maximum propagation steps between chunks :return: """ image: ChunkGrid[b8] = ChunkGrid(self.mask.chunk_size, dtype=b8, fill_value=b8(False)) image.set_value(position, True) return self.fill(image, max_steps=max_steps)
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 test_getitem_offset(self): CS = 2 shape = (CS * 3, CS * 3, CS * 3) soll = np.arange(shape[0] * shape[1] * shape[2]).reshape(shape) offset_chunk = (-1, 0, 1) a = ChunkGrid(CS, int, -1) for u in range(shape[0] // CS): for v in range(shape[1] // CS): for w in range(shape[2] // CS): index = np.add((u, v, w), offset_chunk) x, y, z = np.multiply((u, v, w), CS) a.ensure_chunk_at_index(index).set_array(soll[x:x + CS, y:y + CS, z:z + CS]) offset_voxel = np.multiply(offset_chunk, CS) dense, off = a.to_dense(return_offset=True) self.assertEqual(soll.shape, dense.shape) self.assertEqual(str(soll), str(dense)) self.assertEqual(list(offset_voxel), list(off)) ox, oy, oz = off self.assertEqual(str(soll[1: shape[0] - 1, 1: shape[1] - 1, 1: shape[2] - 1]), str(a[1 + ox: shape[0] - 1 + ox, 1 + oy: shape[1] - 1 + oy, 1 + oz: shape[2] - 1 + oz]))
def _test_operator1_int(self, op): a = ChunkGrid(2, int, -1) a.set_value((0, 0, 0), 0) a.set_value((0, 0, 1), 2) a.set_value((0, 1, 0), -2) expected = op(a.to_dense()) res = op(a) self.assertIsInstance(res, ChunkGrid) result = res.to_dense() assert result.shape == expected.shape self.assertTrue(np.all(result == expected), f"Failure {op}! \n{result}\n-------\n{expected}")
def test_split(self): a = ChunkGrid(2, int, 0) a[0, 0, 0] = 1 a[0, 0, 1] = 2 a[0, 1, 0] = 3 self.assertEqual(1, len(a.chunks)) pos = np.array([ (0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1), ]) b = a.split(2) self.assertEqual(8, len(b.chunks)) offset = (0, 0, 0) for p in pos: self.assertEqual(1, b.get_value(p + offset)) offset = (0, 0, 2) for p in pos: self.assertEqual(2, b.get_value(p + offset)) offset = (0, 2, 0) for p in pos: self.assertEqual(3, b.get_value(p + offset)) offset = (2, 0, 0) for p in pos: self.assertEqual(0, b.get_value(p + offset))
def test_get_block_1(self): a = ChunkGrid(2, int, 0) expected = np.zeros((6, 6, 6), dtype=int) for n, i in enumerate(np.ndindex(6, 6, 6)): pos = np.array(i, dtype=int) - 2 a.set_value(pos, n + 1) expected[i] = n + 1 block = a.get_block_at((0, 0, 0), (3, 3, 3), corners=True, edges=True) self.assertEqual(str(expected), str(a.block_to_array(block)))
def test_mask_1(self): a = ChunkGrid(2, int, 0) mask = ChunkGrid(2, bool, False) mask[1:3, 1:3, 1:3] = True self.assertEqual((4, 4, 4), mask.to_dense().shape) a[mask] = 3 expected = np.zeros((4, 4, 4), dtype=int) expected[1:3, 1:3, 1:3] = 3 self.assertEqual(str(expected), str(a.to_dense())) tmp = a == 3 self.assertEqual(str(mask.to_dense()), str(tmp.to_dense()))
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
def test_get_block_2(self): a = ChunkGrid(2, int, 0) expected = np.zeros((6, 6, 6), dtype=int) for n, i in enumerate(np.ndindex(6, 6, 6)): pos = np.array(i, dtype=int) - 2 a.set_value(pos, n + 1) s = np.sum(pos < 0) + np.sum(2 <= pos) if s >= 2: continue expected[i] = n + 1 block = a.get_block_at((0, 0, 0), (3, 3, 3), corners=False, edges=False) self.assertEqual(str(expected), str(a.block_to_array(block)))