Skip to content

Commit

Permalink
Refactor meshing code (#140)
Browse files Browse the repository at this point in the history
* Re-organize code
* Use delayed typing/TYPE_CHECKING to avoid circular imports (https://stackoverflow.com/a/69427502)
* Use relative imports where possible
* Fix bugs in notebooks
  • Loading branch information
stefsmeets authored Nov 11, 2021
1 parent 4b7adb1 commit 766dd35
Show file tree
Hide file tree
Showing 24 changed files with 246 additions and 3,240 deletions.
11 changes: 9 additions & 2 deletions nanomesh/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
"""Documentation about nanomesh."""
import logging
import sys

from .mesh2d import Mesher2D, compare_mesh_with_image, simple_triangulate
from .mesh3d import Mesher3D
from .mesh_container import TetraMesh, TriangleMesh
from .plane import Plane
from .volume import Volume

Expand All @@ -18,6 +19,12 @@
'__author__',
'__email__',
'__version__',
'compare_mesh_with_image',
'Mesher2D',
'Mesher3D',
'Plane',
'simple_triangulate',
'TetraMesh',
'TriangleMesh',
'Volume',
]
3 changes: 2 additions & 1 deletion nanomesh/_mesh_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

import numpy as np

from nanomesh import Plane, Volume
from .plane import Plane
from .volume import Volume

logger = logging.getLogger(__name__)

Expand Down
10 changes: 10 additions & 0 deletions nanomesh/mesh2d/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from .helpers import compare_mesh_with_image, pad, simple_triangulate
from .mesher import Mesher2D, generate_2d_mesh

__all__ = [
'compare_mesh_with_image',
'generate_2d_mesh',
'Mesher2D',
'pad',
'simple_triangulate',
]
11 changes: 10 additions & 1 deletion nanomesh/mesh_utils.py → nanomesh/mesh2d/helpers.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from __future__ import annotations

from typing import TYPE_CHECKING

import matplotlib.pyplot as plt
import numpy as np
import triangle as tr

from nanomesh.mesh_container import TriangleMesh
if TYPE_CHECKING:
from nanomesh.mesh_container import TriangleMesh


def _legend_with_triplot_fix(ax: plt.Axes):
Expand Down Expand Up @@ -72,6 +77,8 @@ def simple_triangulate(points: np.ndarray,
mesh : TriangleMesh
Triangle mesh
"""
from nanomesh.mesh_container import TriangleMesh

triangle_dict_in = {'vertices': points}

if segments is not None:
Expand Down Expand Up @@ -118,6 +125,8 @@ def pad(mesh: TriangleMesh,
ValueError
When the value of `side` is invalid.
"""
from nanomesh.mesh_container import TriangleMesh

if label is None:
label = mesh.unique_labels.max() + 1

Expand Down
6 changes: 3 additions & 3 deletions nanomesh/mesh2d.py → nanomesh/mesh2d/mesher.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
import numpy as np
from skimage import measure

from nanomesh.mesh_utils import simple_triangulate
from nanomesh._mesh_shared import BaseMesher
from nanomesh.mesh_container import TriangleMesh

from ._mesh_shared import BaseMesher
from .mesh_container import TriangleMesh
from .helpers import simple_triangulate

logger = logging.getLogger(__name__)

Expand Down
10 changes: 10 additions & 0 deletions nanomesh/mesh3d/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from .bounding_box import BoundingBox
from .helpers import pad
from .mesher import Mesher3D, generate_3d_mesh, get_region_markers

__all__ = [
'BoundingBox',
'generate_3d_mesh',
'Mesher3D',
'pad',
]
73 changes: 73 additions & 0 deletions nanomesh/mesh3d/bounding_box.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from dataclasses import dataclass
from typing import Tuple

import numpy as np


@dataclass
class BoundingBox:
"""Container for bounding box coordinates."""
xmin: float
xmax: float
ymin: float
ymax: float
zmin: float
zmax: float

@classmethod
def from_shape(cls, shape: tuple):
"""Generate bounding box from data shape."""
xmin, ymin, zmin = 0, 0, 0
xmax, ymax, zmax = np.array(shape) - 1
return cls(xmin=xmin,
ymin=ymin,
zmin=zmin,
xmax=xmax,
ymax=ymax,
zmax=zmax)

@property
def dimensions(self) -> Tuple[float, float, float]:
"""Return dimensions of bounding box."""
return (
self.xmax - self.xmin,
self.ymax - self.ymin,
self.zmax - self.zmin,
)

@classmethod
def from_points(cls, points: np.array):
"""Generate bounding box from set of points or coordinates."""
xmax, ymax, zmax = np.max(points, axis=0)
xmin, ymin, zmin = np.min(points, axis=0)

return cls(
xmin=xmin,
xmax=xmax,
ymin=ymin,
ymax=ymax,
zmin=zmin,
zmax=zmax,
)

def to_points(self) -> np.ndarray:
"""Return (m,3) array with corner points."""
return np.array([
[self.xmin, self.ymin, self.zmin],
[self.xmin, self.ymin, self.zmax],
[self.xmin, self.ymax, self.zmin],
[self.xmin, self.ymax, self.zmax],
[self.xmax, self.ymin, self.zmin],
[self.xmax, self.ymin, self.zmax],
[self.xmax, self.ymax, self.zmin],
[self.xmax, self.ymax, self.zmax],
])

@property
def center(self) -> np.ndarray:
"""Return center of the bounding box."""
return np.array((
(self.xmin + self.xmax) / 2,
(self.ymin + self.ymax) / 2,
(self.zmin + self.zmax) / 2,
))
25 changes: 16 additions & 9 deletions nanomesh/mesh_utils_3d.py → nanomesh/mesh3d/helpers.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np

from nanomesh.mesh3d import BoundingBox
from nanomesh.mesh_container import TriangleMesh
from nanomesh.utils import pairwise

from .bounding_box import BoundingBox

if TYPE_CHECKING:
from nanomesh.mesh_container import TriangleMesh


def pad3d(mesh: TriangleMesh,
*,
side: str,
width: int,
label: int = None) -> TriangleMesh:
"""Pad a tetra mesh.
def pad(mesh: TriangleMesh,
*,
side: str,
width: int,
label: int = None) -> TriangleMesh:
"""Pad a triangle mesh.
Parameters
----------
Expand Down Expand Up @@ -137,7 +144,7 @@ def pad3d(mesh: TriangleMesh,

cells = np.vstack([mesh.cells, new_triangles])

new_mesh = TriangleMesh(points=points, cells=cells)
new_mesh = mesh.__class__(points=points, cells=cells)

center = extra_coords.mean(axis=0)
center[col] = (center[col] + edge_value) / 2
Expand Down
92 changes: 17 additions & 75 deletions nanomesh/mesh3d.py → nanomesh/mesh3d/mesher.py
Original file line number Diff line number Diff line change
@@ -1,88 +1,23 @@
from __future__ import annotations

import logging
from dataclasses import dataclass
from typing import List, Tuple, Union
from typing import TYPE_CHECKING, List, Tuple, Union

import matplotlib.pyplot as plt
import meshio
import numpy as np
from skimage import measure, morphology

from nanomesh import Volume
from nanomesh.mesh_utils import simple_triangulate
from nanomesh._mesh_shared import BaseMesher
from nanomesh.mesh2d import simple_triangulate
from nanomesh.volume import Volume

from ._mesh_shared import BaseMesher
from .mesh_container import TriangleMesh
from .bounding_box import BoundingBox

logger = logging.getLogger(__name__)


@dataclass
class BoundingBox:
"""Container for bounding box coordinates."""
xmin: float
xmax: float
ymin: float
ymax: float
zmin: float
zmax: float

@classmethod
def from_shape(cls, shape: tuple):
"""Generate bounding box from data shape."""
xmin, ymin, zmin = 0, 0, 0
xmax, ymax, zmax = np.array(shape) - 1
return cls(xmin=xmin,
ymin=ymin,
zmin=zmin,
xmax=xmax,
ymax=ymax,
zmax=zmax)

@property
def dimensions(self) -> Tuple[float, float, float]:
"""Return dimensions of bounding box."""
return (
self.xmax - self.xmin,
self.ymax - self.ymin,
self.zmax - self.zmin,
)

@classmethod
def from_points(cls, points: np.array):
"""Generate bounding box from set of points or coordinates."""
xmax, ymax, zmax = np.max(points, axis=0)
xmin, ymin, zmin = np.min(points, axis=0)

return cls(
xmin=xmin,
xmax=xmax,
ymin=ymin,
ymax=ymax,
zmin=zmin,
zmax=zmax,
)

def to_points(self) -> np.ndarray:
"""Return (m,3) array with corner points."""
return np.array([
[self.xmin, self.ymin, self.zmin],
[self.xmin, self.ymin, self.zmax],
[self.xmin, self.ymax, self.zmin],
[self.xmin, self.ymax, self.zmax],
[self.xmax, self.ymin, self.zmin],
[self.xmax, self.ymin, self.zmax],
[self.xmax, self.ymax, self.zmin],
[self.xmax, self.ymax, self.zmax],
])

@property
def center(self) -> np.ndarray:
"""Return center of the bounding box."""
return np.array((
(self.xmin + self.xmax) / 2,
(self.ymin + self.ymax) / 2,
(self.zmin + self.zmax) / 2,
))
if TYPE_CHECKING:
from nanomesh.mesh_container import TriangleMesh


def get_point_in_prop(
Expand Down Expand Up @@ -165,7 +100,11 @@ def add_corner_points(mesh: TriangleMesh, bbox: BoundingBox) -> None:
mesh.points = np.vstack([mesh.points, corners])


def close_side(mesh, *, side: str, bbox: BoundingBox, ax: plt.Axes = None):
def close_side(mesh: TriangleMesh,
*,
side: str,
bbox: BoundingBox,
ax: plt.Axes = None):
"""Fill a side of the bounding box with triangles.
Parameters
Expand All @@ -190,6 +129,7 @@ def close_side(mesh, *, side: str, bbox: BoundingBox, ax: plt.Axes = None):
ValueError
When the value of `side` is invalid.
"""
from nanomesh.mesh_container import TriangleMesh
all_points = mesh.points

if side == 'top':
Expand Down Expand Up @@ -301,6 +241,8 @@ def generate_contour(
By default takes the average of the min and max value. Can be
ignored if a binary image is passed to `Mesher3D`.
"""
from nanomesh.mesh_container import TriangleMesh

points, cells, *_ = measure.marching_cubes(
self.image,
level=level,
Expand Down
Loading

0 comments on commit 766dd35

Please sign in to comment.