Create a 3D mesh from sparse spaced 2D segmentation

Hello,
I’m trying to find the best meshing algorithm for a set of 200 evenly spaced 2D segmentations: every 20th slice is annotated in the isotropic data along the y axis with a total bounding box of 5197 x 3980 x 3425. The structure is a full human brain hippocampus, thus it has a bean-like shape.

The annotation is high-resolution and constraining the contours of the annotated slices is quite important. I’ve been trying to use a fancy ML-based interpolation tool from the biomedical field but its computation time makes it unviable.

I’ve read about a few algorithms, such as https://examples.vtk.org/site/PythonicAPI/Modelling/SmoothDiscreteFlyingEdges3D/ and Marching Cubes — skimage 0.25.2 documentation

  • If I treat my data as an anisotropic dataset (ratios 1 by 20 by 1), I will end up with a very strong staircase effect which I could try to smooth. Or is it better to keep the isotropic spacing then post-process by stretching the shape along y? (Is it different?)

  • A tetrahedron approach would mainly find in-plane triangles, right?

  • Should I instead create a point cloud (maybe with the hollow 2D contours) and run a convex hull or alpha shape algorithm? Does VTK have an alpha shape algorithm like https://alphashape.readthedocs.io/? Also, because of the high number of points, should I ignore most of the points and get a point cloud from a random subset of points?

Thanks for your help!

Can you provide the data, a subset of the data, and/or a more thorough description of the data?

In particular I don’t understand this every 20th slice annotation business.

If it’s volumetric analysis, you’d likely want to do everything using volumetric algorithms. I suspect generating points would produce data much too large for responsive performance.

Thanks, Will! I mean that, if you consider the 3D volume as a stack of 2D images, I’ve labeled the same structure in images with index 0, 20, 40, etc.
The technique is isotropic thus each voxel is 10x10x10um, but the annotations are spaced by 200um.

See an image attached with blue labels and an attempt at interpolating in red.

And here is a sample data: https://www.dropbox.com/scl/fo/88uakloxk442oasw7nh5o/ABRmsYnEJ_FwYWPXbxEgi2c?rlkey=z6iwc4vop9fvqvis2fwtm1yap&st=81rm75rm&dl=0

BW
Matt

(attachments)

Besides, if you want to generate the 3D volume with the downloaded slices:

import numpy as np
import tifffile
from pathlib import Path

def stack_tiff_slices_with_spacing_glob(
    directory, 
    pattern="*.tif", 
    axis=1, 
    spacing=20, 
    sort_files=True
):
    """
    Load 2D TIFF slices from files matching pattern in directory and create a 3D volume with each slice spaced along the axis.
    
    Args:
        directory (str or Path): Directory containing TIFF files.
        pattern (str): Glob pattern for TIFF files (default: "*.tif").
        axis (int): Axis along which to space slices (default: 1, y-axis).
        spacing (int): Number of positions between slices (default: 20).
        sort_files (bool): Whether to sort file list (default: True).
        
    Returns:
        np.ndarray: 3D volume with spaced TIFF slices.
        list: List of input file paths (in order used).
    """
    dir_path = Path(directory)
    files = list(dir_path.glob(pattern))
    if sort_files:
        files = sorted(files)
    if not files:
        raise ValueError("No files found matching pattern.")
        
    slices = [tifffile.imread(str(f)) for f in files]
    first_shape = slices[0].shape
    for i, arr in enumerate(slices):
        if arr.shape != first_shape:
            raise ValueError(f"Slice {i} shape {arr.shape} does not match {first_shape}")
    n_slices = len(slices)
    vol_shape = list(first_shape)
    vol_shape.insert(axis, (n_slices - 1) * spacing + 1)
    volume = np.zeros(vol_shape, dtype=slices[0].dtype)
    for i, slc in enumerate(slices):
        idx = [slice(None)] * len(vol_shape)
        idx[axis] = i * spacing
        volume[tuple(idx)] = slc
    return volume, files

# Example usage:
# vol, file_list = stack_tiff_slices_with_spacing_glob("/path/to/dir", pattern="*.tif", axis=1, spacing=20)
# tifffile.imwrite("/path/to/dir/3dvolume.tif",vol)