> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ntop.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Slice (Python)

This example loads a `.implicit` file (a gyroid sphere), computes its bounding box, and slices the body at 11 evenly-spaced Z heights within that box using `ntop_core_query_contours`. For each height it plots the resulting polyline contours with Matplotlib and saves them as a stack of PNG images.

```python theme={null}
"""Example of slicing an `.implicit`"""

from ctypes import (
    CDLL,
    CFUNCTYPE,
    pointer,
    POINTER,
    c_bool,
    c_char_p,
    c_double,
    c_uint32,
    c_void_p,
    Structure
)
import os
from pathlib import Path
from typing import Any, List, Tuple

import matplotlib.pyplot as plt
from matplotlib.figure import Figure
import numpy as np


# Load the nTop Core library
NTOP_CORE_LIB_ENVVAR = "NTOP_CORE_LIB"
if NTOP_CORE_LIB_ENVVAR in os.environ:
    env_lib_path = Path(os.environ[NTOP_CORE_LIB_ENVVAR])
    if env_lib_path.exists() and env_lib_path.is_file():
        path_to_lib = env_lib_path
    else:
        raise FileNotFoundError(env_lib_path)

lib = CDLL(str(path_to_lib))
# In order to successfully load the nTop Core Library the dependent libraries of
# the `ntop_core` library must be on the PATH or the current working directory must contain
# those libraries not found on the PATH.
# For simplicity here we are chosing, for the moment, to append to the PATH environment
if str(path_to_lib.parent) not in os.environ["PATH"]:
    os.environ["PATH"] += os.pathsep + str(path_to_lib.parent)

# Setup the data structures and function signatures needed to communicate with the library
class Vec2(Structure):
    """X, Y components representing a point or or vector on a known plane of reference."""
    _fields_ = [("x", c_double), ("y", c_double)]

class Vec3(Structure):
    """X, Y, Z components representing a point or vector."""
    _fields_ = [("x", c_double), ("y", c_double), ("z", c_double)]

class Frame(Structure):
    """
    A point and two orthogonal axes defining a frame of reference.
    """
    _fields_ = [("origin", Vec3), ("x_axis", Vec3), ("y_axis", Vec3)]

class BoundingBox(Structure):
    """
    Minimal, axis-aligned bounding box of an Implicit.

    The representation is comprised of two points (min_x, min_y, min_z) and (max_x, max_y, max_z).
    """
    _fields_ = [("min", Vec3), ("max", Vec3)]


# Define the data type marshalling between Python and nTop Core

# Load the `.implicit` into the library
lib.ntop_core_import_from_file.restype = c_uint32
lib.ntop_core_import_from_file.argtypes = [
    c_char_p,
    POINTER(c_void_p)
]
def callback_import_from_file_error_check(result: c_uint32, func, arguments: Any) -> bool:
        # Check to see if the 
        if result != 0:
            raise RuntimeError(f"Cannot load implicit: {arguments[1]}.")

        return True
lib.ntop_core_import_from_file.errcheck = callback_import_from_file_error_check

# Release the memory used for the `.implicit`
lib.ntop_core_release.restype = None
lib.ntop_core_release.argtypes = [c_void_p]

# Bounding Box call
lib.ntop_core_query_bounding_box.restype = None
lib.ntop_core_query_bounding_box.argtypes = [
    c_void_p,
    POINTER(BoundingBox)
]

# Declare the callback made by the contour call
CFUNCTYPE_CONTOUR_CALLBACK = CFUNCTYPE(None, c_void_p, POINTER(Vec2), c_uint32, c_bool)
lib.ntop_core_query_contours.restype = None
lib.ntop_core_query_contours.argtypes = [
    c_void_p,
    POINTER(Frame),
    c_double,
    c_void_p,
    CFUNCTYPE_CONTOUR_CALLBACK
]
def callback_query_contours(scope_pass: List[List[Tuple[float, float]]]) -> CFUNCTYPE_CONTOUR_CALLBACK:
    """
    Callback for the ntop_core_query_contours to smuggle out the slice data.
    
    `scope_pass` is populated with the slice data.
    """    
    
    def _smuggle(context: c_void_p, polyline: List[Vec2], count: int, is_closed: bool) -> None:
        curve = []

        for i in range(count):
            curve.append((polyline[i].x, polyline[i].y))

        scope_pass.append(curve)

    return CFUNCTYPE_CONTOUR_CALLBACK(_smuggle)


def plot_slice(slice: List[List[Tuple[float, float]]], bounding_box: BoundingBox) -> Figure:
    """Plots a single slice."""

    # Make the figures consistent in size across slices
    fig = plt.figure(figsize=[
        1000 * (bounding_box.max.x - bounding_box.min.x),
        1000 * (bounding_box.max.y - bounding_box.min.y)
    ])
    
    cur_fig = fig.add_subplot(1, 1, 1)
    # Iterate over the curves in the slice
    for curve in slice:
        # iterate over the curves in the contour
        x_vals = [pt[0] for pt in curve]
        y_vals = [pt[1] for pt in curve]

        # Close the curve
        x_vals.append(curve[0][0])
        y_vals.append(curve[0][1])
        
        cur_fig.plot(
            x_vals,
            y_vals,
            scalex=0.001,
            scaley=0.001
        )

    # Annotate the figure
    cur_fig.set_title(f"Z-Offset: {int(round(z_offset, 3)*1000)} mm")
    cur_fig.set_xlabel("meters")
    cur_fig.set_ylabel("meters")
    x_ticks = np.arange(round(bounding_box.min.x, 3), round(bounding_box.max.x, 3) + 0.001, 0.001)
    y_ticks = np.arange(round(bounding_box.min.y, 3), round(bounding_box.max.y, 3) + 0.001, 0.001)
    cur_fig.set_xticks(x_ticks)
    cur_fig.set_yticks(y_ticks)
    cur_fig.set_xbound(bounding_box.min.x - 0.001, bounding_box.max.x + 0.001)
    cur_fig.set_ybound(bounding_box.min.y - 0.001, bounding_box.max.y + 0.001)

    return fig


# Load up the implicit
gyroid_implicit_file_path = Path(__file__).parent.parent / "assets" / "gyroid_sphere_5mm_radius.implicit"
gyroid_handle = c_void_p()

lib.ntop_core_import_from_file(str(gyroid_implicit_file_path).encode("utf8"), gyroid_handle)

## Setup the slice stack
# Get the bounding box to slice the full body of the implicit
bounding_box = BoundingBox()
lib.ntop_core_query_bounding_box(gyroid_handle, pointer(bounding_box))

# Offset from the bounding box slightly to capture the top and
# bottom surfaces
slice_top_bottom_offset = 0.0001

# Slice at 11 heights within the bounding box of the `.implicit`
slice_z_offsets = np.linspace(
    bounding_box.min.z + slice_top_bottom_offset,
    bounding_box.max.z - slice_top_bottom_offset,
    11
    ).tolist()

# Capture features 50 microns in size
min_feat_size = 50 * (10 ** -6)

# Make a folder to capture the output
output_folder = Path(__file__).parent / "output"
output_folder.mkdir(exist_ok=True)

# Create a slice stack
output_paths: list[Path] = []

for idx, z_offset in enumerate(slice_z_offsets):
    slice_plane = Frame(
        Vec3(0, 0, z_offset),
        Vec3(1, 0, 0),
        Vec3(0, 1, 0)
    )

    # List containing a list of tuples representing closed polyline curves
    slice = []

    # slice the implicit on each frame
    lib.ntop_core_query_contours(gyroid_handle, pointer(slice_plane), min_feat_size, None, callback_query_contours(slice))

    # save a plot of the figure
    fig = plot_slice(slice, bounding_box)
    output_path = output_folder / f"slice_{idx}_{gyroid_implicit_file_path.stem}_{int(round(z_offset, 3)*1000)}mmZ.png"
    fig.savefig(output_path, format="png")
    output_paths.append(output_path)

# Print out the location of the generated files
print("Generated the following slices:")
for f in output_paths:
    print(f"\t{f}")

# Unload the `.implicit`
lib.ntop_core_release(gyroid_handle)
```
