Пример #1
0
    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()))
Пример #2
0
    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 crust_dilation(crust: ChunkGrid[np.bool8],
                   max_components=5,
                   reverse_steps=3,
                   max_steps=5):
    """Dilate a crust until the inner component vanishes and return result of some steps reversed"""
    assert max_steps > 0
    max_count = 0
    dilation_step = 0
    crusts_all = []
    components_all = []
    counts_all = []

    for dilation_step in range(max_steps):
        print(f"\t\tDilation-Step {dilation_step}")
        components, count = fill_components(crust,
                                            max_components=max_components)
        crusts_all.append(crust)
        components_all.append(components)
        counts_all.append(count)

        # plot_voxels(components == 0, components)
        # print(count)

        if max_count >= count and count == 2:
            break
        else:
            max_count = max(max_count, count)
            crust = dilate(crust)
            assert crust.any()

    print("\tSteps: ", dilation_step)

    # Take the crust one step before the inner component vanished.

    step = max(0, dilation_step - reverse_steps)
    crust = crusts_all[step]
    components = components_all[step]
    count_prev = counts_all[step]
    crust.cleanup(remove=True)
    components.cleanup(remove=True)

    # Cleanup components and select only the largest component
    # This will set all other components (0, 3,4,5,...) to be part of crust (1)
    cleanup_components(crust, components, count_prev)

    return crust, components, step
def crust_fix(
        crust: ChunkGrid[np.bool8],
        outer_fill: ChunkGrid[np.bool8],
        crust_outer: ChunkGrid[np.bool8],
        crust_inner: ChunkGrid[np.bool8],
        min_distance: int = 1,
        data_pts: Optional[np.ndarray] = None,  # for plotting,
        export_path: Optional[str] = None):
    CHUNKSIZE = crust.chunk_size
    normal_kernel = make_normal_kernel()

    inv_outer_fill = ~outer_fill

    # Method cache (prevent lookup in loop)
    __grid_set_value = ChunkGrid.set_value
    __np_sum = np.sum

    print("\tCreate Normals: ")
    with timed("\t\tTime: "):
        # normal_zero = np.zeros(3, dtype=np.float32)
        normal_pos = np.array(list(crust_outer.where()))
        normal_val = np.full((len(normal_pos), 3), 0.0, dtype=np.float32)
        for n, p in enumerate(normal_pos):
            x, y, z = p
            mask: np.ndarray = outer_fill[x - 1:x + 2, y - 1:y + 2,
                                          z - 1:z + 2]
            normal_val[n] = __np_sum(normal_kernel[mask], axis=0)
        normal_val = (normal_val.T / np.linalg.norm(normal_val, axis=1)).T

    print("\tGrid Normals: ")
    with timed("\t\tTime: "):
        normals: ChunkGrid[np.float32] = ChunkGrid(
            CHUNKSIZE, np.dtype((np.float32, (3, ))), 0.0)
        normals[normal_pos] = normal_val

    print("\tRender Normal Propagation: ")
    with timed("\t\tTime: "):
        markers_outer = np.array([
            v for p, n in normals.items(mask=crust_outer)
            for v in (p, p + n, (np.nan, np.nan, np.nan))
        ],
                                 dtype=np.float32) + 0.5
        markers_outer_tips = np.array(
            [p + n for p, n in normals.items(mask=crust_outer)],
            dtype=np.float32) + 0.5

        ren = CloudRender()
        fig = ren.make_figure(title="Crust-Fix: Start Normal Propagation")
        fig.add_trace(
            ren.make_scatter(markers_outer,
                             marker=dict(opacity=0.5, ),
                             mode="lines",
                             name="Start normal"))
        fig.add_trace(
            ren.make_scatter(markers_outer_tips,
                             marker=dict(size=1, symbol='x'),
                             name="Start nromal end"))
        if data_pts is not None:
            fig.add_trace(
                ren.make_scatter(data_pts, opacity=0.1, size=1, name='Model'))
        if export_path:
            fig.write_html(os.path.join(export_path, "normal_start.html"))
        fig.show()

    print("\tNormal Propagation")
    with timed("\t\tTime: "):
        iterations = CHUNKSIZE * 2
        nfield = propagate_normals(iterations, normals, crust_outer,
                                   inv_outer_fill)
        field_reset_mask = outer_fill ^ crust_outer
        nfield[field_reset_mask] = 0
        nfield.cleanup(remove=True)

    # print("\tRender Normal Field: ")
    # with timed("\t\tTime: "):
    #
    #     markers_crust = np.array(
    #         [v for p, n in nfield.items(mask=crust) for v in (p, p + n, (np.nan, np.nan, np.nan))],
    #         dtype=np.float32) + 0.5
    #     markers_outer = np.array(
    #         [v for p, n in nfield.items(mask=crust_outer) for v in (p, p + n, (np.nan, np.nan, np.nan))],
    #         dtype=np.float32) + 0.5
    #     markers_outer_tips = np.array(
    #         [p + n for p, n in nfield.items(mask=crust_outer)],
    #         dtype=np.float32) + 0.5
    #
    #     ren = CloudRender()
    #     fig = ren.make_figure(title="Crust-Fix: Normal Field")
    #     fig.add_trace(ren.make_scatter(markers_outer, marker=dict(opacity=0.5, ), mode="lines", name="Start normal"))
    #     fig.add_trace(ren.make_scatter(markers_outer_tips, marker=dict(size=1, symbol='x'), name="Start normal end"))
    #     fig.add_trace(ren.make_scatter(markers_crust, marker=dict(opacity=0.5, ), mode="lines", name="Normal field"))
    #     if data_pts is not None:
    #         fig.add_trace(ren.make_scatter(data_pts, opacity=0.1, size=1, name='Model'))
    #     if export_path:
    #         fig.write_html(os.path.join(export_path, "normal_field.html"))
    #     fig.show()

    print("\tNormal cone: ")
    with timed("\t\tTime: "):
        medial = ChunkGrid(crust.chunk_size, np.bool8, False)
        cone_threshold: float = 0.5 * np.pi
        min_norm: float = 1e-15
        for chunk in nfield.chunks:
            padded = nfield.padding_at(chunk.index,
                                       1,
                                       corners=True,
                                       edges=True)
            cones = normal_cone_angles(padded, cone_threshold, min_norm)
            medial.ensure_chunk_at_index(chunk.index).set_array(cones.copy())
        medial.cleanup(remove=True)

    print("\tResult: ")
    with timed("\t\tTime: "):
        # Remove artifacts where the inner and outer crusts are touching
        artifacts_fix = outer_fill.copy().pad_chunks(1)
        artifacts_fix.fill_value = False
        artifacts_fix = ~dilate(artifacts_fix,
                                steps=max(0, min_distance) + 2) & ~outer_fill
        medial_cleaned = medial & artifacts_fix
        medial_cleaned.cleanup(remove=True)

    print("\tRender 2: ")
    with timed("\t\tTime: "):
        time.sleep(0.01)
        ren = VoxelRender()
        fig = ren.make_figure(title="Crust-Normal-Fix: Result before cleanup")
        print("Ren2-medial")
        fig.add_trace(ren.grid_voxel(medial, opacity=0.3, name='Medial'))
        # fig.add_trace(ren.grid_voxel(medial_cleaned, opacity=0.05, name='Fixed'))
        print("Ren2-crust_outer")
        fig.add_trace(ren.grid_voxel(crust_outer, opacity=0.05, name='Outer'))
        if data_pts is not None:
            print("Ren2-data_pts")
            fig.add_trace(CloudRender().make_scatter(data_pts,
                                                     opacity=0.2,
                                                     size=1,
                                                     name='Model'))
        print("Ren2-show")
        if export_path:
            fig.write_html(os.path.join(export_path, "medial.html"))
        fig.show()

    print("\tRender 3: ")
    with timed("\t\tTime: "):
        time.sleep(0.01)
        ren = VoxelRender()
        fig = ren.make_figure(title="Crust-Fix: Result after cleanup")
        # fig.add_trace(ren.grid_voxel(medial, opacity=0.3, name='Fixed'))
        print("Ren2-medial_cleaned")
        fig.add_trace(
            ren.grid_voxel(medial_cleaned, opacity=0.3, name='Medial-Cleaned'))
        print("Ren3-crust_outer")
        fig.add_trace(ren.grid_voxel(crust_outer, opacity=0.05, name='Outer'))
        if data_pts is not None:
            print("Ren3-data_pts")
            fig.add_trace(CloudRender().make_scatter(data_pts,
                                                     opacity=0.2,
                                                     size=1,
                                                     name='Model'))
        print("Ren3-show")
        if export_path:
            fig.write_html(os.path.join(export_path, "medial_cleaned.html"))
        fig.show()

    return medial_cleaned
Пример #5
0
    crust.cleanup(remove=True)

    # ren = VoxelRender()
    # fig = ren.make_figure()
    # fig.add_trace(ren.grid_voxel(initial_crust, opacity=0.1, name='Initial'))
    # fig.add_trace(CloudRender().make_scatter(data_pts, size=1, name='Model'))
    # fig.show()

    print("Dilation")
    with timed("\tTime: "):
        crust, components, dilation_step = crust_dilation(crust, max_steps=dilations_max,
                                                          reverse_steps=dilations_reverse)
        # assert components._fill_value == 2

        plot_voxels(components == 0, components, title=f"Initial Dilation").show()
        crust_dilate = dilate(crust)
        outer_fill = components == 2
        crust_outer = outer_fill & crust_dilate
        crust_inner = (components == 3) & crust_dilate

        assert crust_dilate._fill_value == False
        assert outer_fill._fill_value == True
        assert crust_outer._fill_value == False
        assert crust_inner._fill_value == False

    """
    Increase resolution and make the crust_fixmesh approximation finer
    """
    for resolution_step in range(0, STEPS):
        print(f"RESOLUTION STEP: {resolution_step}")