move to simpler docstring format and improve type annotations

This commit is contained in:
Jan Petykiewicz 2021-01-08 22:43:22 -08:00
parent 4e0a5ede21
commit db9e7831b9
5 changed files with 224 additions and 170 deletions

View File

@ -3,8 +3,6 @@ from typing import Any
def is_scalar(var: Any) -> bool:
"""
Alias for 'not hasattr(var, "__len__")'
:param var: Checks if var has a length.
Alias for `not hasattr(var, "__len__")`
"""
return not hasattr(var, "__len__")

View File

@ -1,9 +1,9 @@
"""
Drawing-related methods for Grid class
"""
from typing import List
from typing import List, Optional, Union, Sequence, Callable
import numpy
import numpy # type: ignore
from numpy import diff, floor, ceil, zeros, hstack, newaxis
from float_raster import raster
@ -12,27 +12,34 @@ from . import GridError, Direction
from ._helpers import is_scalar
eps_callable_t = Callable[[numpy.ndarray, numpy.ndarray, numpy.ndarray], numpy.ndarray]
def draw_polygons(self,
surface_normal: Direction or int,
center: List or numpy.ndarray,
polygons: List[numpy.ndarray or List],
surface_normal: Union[Direction, int],
center: numpy.ndarray,
polygons: Sequence[numpy.ndarray],
thickness: float,
eps: List[float or eps_callable_type] or float or eps_callable_type):
eps: Union[Sequence[Union[float, eps_callable_t]], float, eps_callable_t],
) -> None:
"""
Draw polygons on an axis-aligned plane.
:param surface_normal: Axis normal to the plane we're drawing on. Can be a Direction or
integer in range(3)
:param center: 3-element ndarray or list specifying an offset applied to all the polygons
:param polygons: List of Nx2 or Nx3 ndarrays, each specifying the vertices of a polygon
(non-closed, clockwise). If Nx3, the surface_normal coordinate is ignored. Each polygon
must have at least 3 vertices.
:param thickness: Thickness of the layer to draw
:param eps: Value to draw with ('epsilon'). Can be scalar, callable, or a list
of any of these (1 per grid). Callable values should take ndarrays x, y, z of equal
shape and return an ndarray of equal shape containing the eps value at the given x, y,
and z (natural, not grid coordinates).
:raises: GridError
Args:
surface_normal: Axis normal to the plane we're drawing on. Can be a `Direction` or
integer in `range(3)`
center: 3-element ndarray or list specifying an offset applied to all the polygons
polygons: List of Nx2 or Nx3 ndarrays, each specifying the vertices of a polygon
(non-closed, clockwise). If Nx3, the surface_normal coordinate is ignored. Each
polygon must have at least 3 vertices.
thickness: Thickness of the layer to draw
eps: Value to draw with ('epsilon'). Can be scalar, callable, or a list
of any of these (1 per grid). Callable values should take an ndarray the shape of the
grid and return an ndarray of equal shape containing the eps value at the given x, y,
and z (natural, not grid coordinates).
Raises:
GridError
"""
# Turn surface_normal into its integer representation
if isinstance(surface_normal, Direction):
@ -180,39 +187,43 @@ def draw_polygons(self,
def draw_polygon(self,
surface_normal: Direction or int,
center: List or numpy.ndarray,
polygon: List or numpy.ndarray,
surface_normal: Union[Direction, int],
center: numpy.ndarray,
polygon: numpy.ndarray,
thickness: float,
eps: List[float or eps_callable_type] or float or eps_callable_type):
eps: Union[Sequence[Union[float, eps_callable_t]], float, eps_callable_t],
) -> None:
"""
Draw a polygon on an axis-aligned plane.
:param surface_normal: Axis normal to the plane we're drawing on. Can be a Direction or
integer in range(3)
:param center: 3-element ndarray or list specifying an offset applied to the polygon
:param polygon: Nx2 or Nx3 ndarray specifying the vertices of a polygon (non-closed,
clockwise). If Nx3, the surface_normal coordinate is ignored. Must have at least 3
vertices.
:param thickness: Thickness of the layer to draw
:param eps: Value to draw with ('epsilon'). See draw_polygons() for details.
Args:
surface_normal: Axis normal to the plane we're drawing on. Can be a Direction or
integer in range(3)
center: 3-element ndarray or list specifying an offset applied to the polygon
polygon: Nx2 or Nx3 ndarray specifying the vertices of a polygon (non-closed,
clockwise). If Nx3, the surface_normal coordinate is ignored. Must have at
least 3 vertices.
thickness: Thickness of the layer to draw
eps: Value to draw with ('epsilon'). See `draw_polygons()` for details.
"""
self.draw_polygons(surface_normal, center, [polygon], thickness, eps)
def draw_slab(self,
surface_normal: Direction or int,
center: List or numpy.ndarray,
surface_normal: Union[Direction, int],
center: numpy.ndarray,
thickness: float,
eps: List[float or eps_callable_type] or float or eps_callable_type):
eps: Union[List[Union[float, eps_callable_t]], float, eps_callable_t],
) -> None:
"""
Draw an axis-aligned infinite slab.
:param surface_normal: Axis normal to the plane we're drawing on. Can be a Direction or
integer in range(3)
:param center: Surface_normal coordinate at the center of the slab
:param thickness: Thickness of the layer to draw
:param eps: Value to draw with ('epsilon'). See draw_polygons() for details.
Args:
surface_normal: Axis normal to the plane we're drawing on. Can be a `Direction` or
integer in `range(3)`
center: Surface_normal coordinate at the center of the slab
thickness: Thickness of the layer to draw
eps: Value to draw with ('epsilon'). See `draw_polygons()` for details.
"""
# Turn surface_normal into its integer representation
if isinstance(surface_normal, Direction):
@ -250,16 +261,18 @@ def draw_slab(self,
def draw_cuboid(self,
center: List or numpy.ndarray,
dimensions: List or numpy.ndarray,
eps: List[float or eps_callable_type] or float or eps_callable_type):
center: numpy.ndarray,
dimensions: numpy.ndarray,
eps: Union[List[Union[float, eps_callable_t]], float, eps_callable_t],
) -> None:
"""
Draw an axis-aligned cuboid
:param center: 3-element ndarray or list specifying the cuboid's center
:param dimensions: 3-element list or ndarray containing the x, y, and z edge-to-edge
sizes of the cuboid
:param eps: Value to draw with ('epsilon'). See draw_polygons() for details.
Args:
center: 3-element ndarray or list specifying the cuboid's center
dimensions: 3-element list or ndarray containing the x, y, and z edge-to-edge
sizes of the cuboid
eps: Value to draw with ('epsilon'). See `draw_polygons()` for details.
"""
p = numpy.array([[-dimensions[0], +dimensions[1]],
[+dimensions[0], +dimensions[1]],
@ -270,22 +283,24 @@ def draw_cuboid(self,
def draw_cylinder(self,
surface_normal: Direction or int,
center: List or numpy.ndarray,
surface_normal: Union[Direction, int],
center: numpy.ndarray,
radius: float,
thickness: float,
num_points: int,
eps: List[float or eps_callable_type] or float or eps_callable_type):
eps: Union[List[Union[float, eps_callable_t]], float, eps_callable_t],
) -> None:
"""
Draw an axis-aligned cylinder. Approximated by a num_points-gon
:param surface_normal: Axis normal to the plane we're drawing on. Can be a Direction or
integer in range(3)
:param center: 3-element ndarray or list specifying the cylinder's center
:param radius: cylinder radius
:param thickness: Thickness of the layer to draw
:param num_points: The circle is approximated by a polygon with num_points vertices
:param eps: Value to draw with ('epsilon'). See draw_polygons() for details.
Args:
surface_normal: Axis normal to the plane we're drawing on. Can be a `Direction` or
integer in `range(3)`
center: 3-element ndarray or list specifying the cylinder's center
radius: cylinder radius
thickness: Thickness of the layer to draw
num_points: The circle is approximated by a polygon with `num_points` vertices
eps: Value to draw with ('epsilon'). See `draw_polygons()` for details.
"""
theta = numpy.linspace(0, 2*numpy.pi, num_points, endpoint=False)
x = radius * numpy.sin(theta)
@ -295,17 +310,19 @@ def draw_cylinder(self,
def draw_extrude_rectangle(self,
rectangle: List or numpy.ndarray,
direction: Direction or int,
rectangle: numpy.ndarray,
direction: Union[Direction, int],
polarity: int,
distance: float):
distance: float,
) -> None:
"""
Extrude a rectangle of a previously-drawn structure along an axis.
:param rectangle: 2x3 ndarray or list specifying the rectangle's corners
:param direction: Direction to extrude in. Direction enum or int in range(3)
:param polarity: +1 or -1, direction along axis to extrude in
:param distance: How far to extrude
Args:
rectangle: 2x3 ndarray or list specifying the rectangle's corners
direction: Direction to extrude in. Direction enum or int in range(3)
polarity: +1 or -1, direction along axis to extrude in
distance: How far to extrude
"""
# Turn extrude_direction into its integer representation
if isinstance(direction, Direction):

View File

@ -1,6 +1,6 @@
from typing import List, Tuple, Callable, Dict
from typing import List, Tuple, Callable, Dict, Optional, Union, Sequence, ClassVar
import numpy
import numpy # type: ignore
from numpy import diff, floor, ceil, zeros, hstack, newaxis
import pickle
@ -23,24 +23,26 @@ class Grid(object):
is generated based on the coordinates of the boundary points). Also does
straightforward natural <-> grid unit conversion.
self.grids[i][a,b,c] contains the value of epsilon for the cell located around
`self.grids[i][a,b,c]` contains the value of epsilon for the cell located around
```
(xyz[0][a] + dxyz[0][a] * shifts[i, 0],
xyz[1][b] + dxyz[1][b] * shifts[i, 1],
xyz[2][c] + dxyz[2][c] * shifts[i, 2]).
You can get raw edge coordinates (exyz),
center coordinates (xyz),
cell sizes (dxyz),
```
You can get raw edge coordinates (`exyz`),
center coordinates (`xyz`),
cell sizes (`dxyz`),
from the properties named as above, or get them for a given grid by using the
self.shifted_*xyz(which_shifts) functions.
`self.shifted_*xyz(which_shifts)` functions.
The sizes of adjacent cells are taken into account when applying shifts. The
total shift for each edge is chosen using (shift * dx_of_cell_being_moved_through).
total shift for each edge is chosen using `(shift * dx_of_cell_being_moved_through)`.
It is tricky to determine the size of the right-most cell after shifting,
since its right boundary should shift by shifts[i][a] * dxyz[a][dxyz[a].size],
since its right boundary should shift by `shifts[i][a] * dxyz[a][dxyz[a].size]`,
where the dxyz element refers to a cell that does not exist.
Because of this, we either assume this 'ghost' cell is the same size as the last
real cell, or, if self.periodic[a] is set to True, the same size as the first cell.
real cell, or, if `self.periodic[a]` is set to `True`, the same size as the first cell.
"""
Yee_Shifts_E = 0.5 * numpy.array([[1, 0, 0],
@ -63,7 +65,8 @@ class Grid(object):
"""
Cell sizes for each axis, no shifts applied
:return: List of 3 ndarrays of cell sizes
Returns:
List of 3 ndarrays of cell sizes
"""
return [diff(self.exyz[a]) for a in range(3)]
@ -72,7 +75,8 @@ class Grid(object):
"""
Cell centers for each axis, no shifts applied
:return: List of 3 ndarrays of cell edges
Returns:
List of 3 ndarrays of cell edges
"""
return [self.exyz[a][:-1] + self.dxyz[a] / 2.0 for a in range(3)]
@ -81,7 +85,8 @@ class Grid(object):
"""
The number of cells in x, y, and z
:return: ndarray [x_centers.size, y_centers.size, z_centers.size]
Returns:
ndarray of [x_centers.size, y_centers.size, z_centers.size]
"""
return numpy.array([coord.size - 1 for coord in self.exyz], dtype=int)
@ -95,7 +100,8 @@ class Grid(object):
If periodic, final edge shifts same amount as first
Otherwise, final edge shifts same amount as second-to-last
:return: list of [dxs, dys, dzs] with each element same length as elements of self.xyz
Returns:
list of [dxs, dys, dzs] with each element same length as elements of `self.xyz`
"""
el = [0 if p else -1 for p in self.periodic]
return [hstack((self.dxyz[a], self.dxyz[a][e])) for a, e in zip(range(3), el)]
@ -104,7 +110,9 @@ class Grid(object):
def center(self) -> numpy.ndarray:
"""
Center position of the entire grid, no shifts applied
:return: ndarray [x_center, y_center, z_center]
Returns:
ndarray of [x_center, y_center, z_center]
"""
# center is just average of first and last xyz, which is just the average of the
# first two and last two exyz
@ -118,18 +126,22 @@ class Grid(object):
ndarrays. No shifts are applied, so these are extreme bounds on these values (as a
weighted average is performed when shifting).
:return: List of 2 ndarrays, d_min=[min(dx), min(dy), min(dz)] and d_max=[...]
Returns:
Tuple of 2 ndarrays, `d_min=[min(dx), min(dy), min(dz)]` and `d_max=[...]`
"""
d_min = numpy.array([min(self.dxyz[a]) for a in range(3)], dtype=float)
d_max = numpy.array([max(self.dxyz[a]) for a in range(3)], dtype=float)
return d_min, d_max
def shifted_exyz(self, which_shifts: int or None) -> List[numpy.ndarray]:
def shifted_exyz(self, which_shifts: Optional[int]) -> List[numpy.ndarray]:
"""
Returns edges for which_shifts.
:param which_shifts: Which grid (which shifts) to use, or None for unshifted
:return: List of 3 ndarrays of cell edges
Args:
which_shifts: Which grid (which shifts) to use, or `None` for unshifted
Returns:
List of 3 ndarrays of cell edges
"""
if which_shifts is None:
return self.exyz
@ -143,12 +155,15 @@ class Grid(object):
return [self.exyz[a] + dxyz[a] * shifts[a] for a in range(3)]
def shifted_dxyz(self, which_shifts: int or None) -> List[numpy.ndarray]:
def shifted_dxyz(self, which_shifts: Optional[int]) -> List[numpy.ndarray]:
"""
Returns cell sizes for which_shifts.
Returns cell sizes for `which_shifts`.
:param which_shifts: Which grid (which shifts) to use, or None for unshifted
:return: List of 3 ndarrays of cell sizes
Args:
which_shifts: Which grid (which shifts) to use, or `None` for unshifted
Returns:
List of 3 ndarrays of cell sizes
"""
if which_shifts is None:
return self.dxyz
@ -167,12 +182,15 @@ class Grid(object):
return sdxyz
def shifted_xyz(self, which_shifts: int or None) -> List[numpy.ndarray]:
def shifted_xyz(self, which_shifts: Optional[int]) -> List[numpy.ndarray]:
"""
Returns cell centers for which_shifts.
Returns cell centers for `which_shifts`.
:param which_shifts: Which grid (which shifts) to use, or None for unshifted
:return: List of 3 ndarrays of cell centers
Args:
which_shifts: Which grid (which shifts) to use, or `None` for unshifted
Returns:
List of 3 ndarrays of cell centers
"""
if which_shifts is None:
return self.xyz
@ -184,7 +202,8 @@ class Grid(object):
"""
Return cell widths, with each dimension shifted by the corresponding shifts.
:return: [grid.shifted_dxyz(which_shifts=a)[a] for a in range(3)]
Returns:
`[grid.shifted_dxyz(which_shifts=a)[a] for a in range(3)]`
"""
if len(self.grids) != 3:
raise GridError('autoshifting requires exactly 3 grids')
@ -192,29 +211,31 @@ class Grid(object):
def __init__(self,
pixel_edge_coordinates: List[List or numpy.ndarray],
shifts: numpy.ndarray or List = Yee_Shifts_E,
initial: float or numpy.ndarray or List[float] or List[numpy.ndarray] = 1.0,
num_grids: int = None,
periodic: bool or List[bool] = False):
pixel_edge_coordinates: Sequence[numpy.ndarray],
shifts: numpy.ndarray = Yee_Shifts_E,
initial: Union[float, numpy.ndarray] = 1.0,
num_grids: Optional[int] = None,
periodic: Union[bool, Sequence[bool]] = False,
) -> None:
"""
Initialize a new Grid
Args:
pixel_edge_coordinates: 3-element list of (ndarrays or lists) specifying the
coordinates of the pixel edges in each dimensions
(ie, `[[x0, x1, x2,...], [y0,...], [z0,...]]` where the first pixel has x-edges x=`x0` and
x=`x1`, the second has edges x=`x1` and x=`x2`, etc.)
shifts: Nx3 array containing `[x, y, z]` offsets for each of N grids.
E-field Yee shifts are used by default.
initial: Grids are initialized to this value. If scalar, all grids are initialized
with ndarrays full of the scalar. If a list of scalars, `grid[i]` is initialized to an
ndarray full of `initial[i]`. If a list of ndarrays of the same shape as the grids, `grid[i]`
is set to `initial[i]`. Default `1.0`.
num_grids: How many grids to create. Must be <= `shifts.shape[0]`.
Default is `shifts.shape[0]`
periodic: Specifies how the sizes of edge cells are calculated; see main class
documentation. List of 3 bool, or a single bool that gets broadcast. Default `False`.
:param pixel_edge_coordinates: 3-element list of (ndarrays or lists) specifying the
coordinates of the pixel edges in each dimensions
(ie, [[x0, x1, x2,...], [y0,...], [z0,...]] where the first pixel has x-edges x=x0 and
x=x1, the second has edges x=x1 and x=x2, etc.)
:param shifts: Nx3 array containing [x, y, z] offsets for each of N grids.
E-field Yee shifts are used by default.
:param initial: Grids are initialized to this value. If scalar, all grids are initialized
with ndarrays full of the scalar. If a list of scalars, grid[i] is initialized to an
ndarray full of initial[i]. If a list of ndarrays of the same shape as the grids, grid[i]
is set to initial[i]. Default 1.
:param num_grids: How many grids to create. Must be <= shifts.shape[0].
Default is shifts.shape[0]
:param periodic: Specifies how the sizes of edge cells are calculated; see main class
documentation. List of 3 bool, or a single bool that gets broadcast. Default False.
:raises: GridError
Raises:
`GridError` on invalid input
"""
self.exyz = [numpy.unique(pixel_edge_coordinates[i]) for i in range(3)] # type: List[numpy.ndarray]
"""Cell edges. Monotonically increasing without duplicates."""
@ -280,7 +301,8 @@ class Grid(object):
"""
Load a grid from a file
:param filename: Filename to load from.
Args:
filename: Filename to load from.
"""
with open(filename, 'rb') as f:
tmp_dict = pickle.load(f)
@ -293,16 +315,15 @@ class Grid(object):
"""
Save to file.
:param filename: Filename to save to.
Args:
filename: Filename to save to.
"""
with open(filename, 'wb') as f:
pickle.dump(self.__dict__, f, protocol=2)
def copy(self):
"""
Return a deep copy of the grid.
:return: Deep copy of the grid.
Returns:
Deep copy of the grid.
"""
return copy.deepcopy(self)

View File

@ -1,34 +1,39 @@
"""
Position-related methods for Grid class
"""
from typing import List, Optional
from typing import List
import numpy
import numpy # type: ignore
from numpy import zeros
from . import GridError
def ind2pos(self,
ind: numpy.ndarray or List,
which_shifts: int = None,
ind: numpy.ndarray,
which_shifts: Optional[int] = None,
round_ind: bool = True,
check_bounds: bool = True
) -> numpy.ndarray:
"""
Returns the natural position corresponding to the specified cell center indices.
The resulting position is clipped to the bounds of the grid
(to cell centers if round_ind=True, or cell outer edges if round_ind=False)
(to cell centers if `round_ind=True`, or cell outer edges if `round_ind=False`)
:param ind: Indices of the position. Can be fractional. (3-element ndarray or list)
:param which_shifts: which grid number (shifts) to use
:param round_ind: Whether to round ind to the nearest integer position before indexing
(default True)
:param check_bounds: Whether to raise an GridError if the provided ind is outside of
the grid, as defined above (centers if round_ind, else edges) (default True)
:return: 3-element ndarray specifying the natural position
:raises: GridError
Args:
ind: Indices of the position. Can be fractional. (3-element ndarray or list)
which_shifts: which grid number (`shifts`) to use
round_ind: Whether to round ind to the nearest integer position before indexing
(default `True`)
check_bounds: Whether to raise an `GridError` if the provided ind is outside of
the grid, as defined above (centers if `round_ind`, else edges) (default `True`)
Returns:
3-element ndarray specifying the natural position
Raises:
`GridError` if invalid `which_shifts`
`GridError` if `check_bounds` and out of bounds
"""
if which_shifts is not None and which_shifts >= self.shifts.shape[0]:
raise GridError('Invalid shifts')
@ -56,21 +61,27 @@ def ind2pos(self,
def pos2ind(self,
r: numpy.ndarray or List,
which_shifts: int or None,
round_ind: bool=True,
check_bounds: bool=True
r: numpy.ndarray,
which_shifts: Optional[int],
round_ind: bool = True,
check_bounds: bool = True
) -> numpy.ndarray:
"""
Returns the cell-center indices corresponding to the specified natural position.
The resulting position is clipped to within the outer centers of the grid.
:param r: Natural position that we will convert into indices (3-element ndarray or list)
:param which_shifts: which grid number (shifts) to use
:param round_ind: Whether to round the returned indices to the nearest integers.
:param check_bounds: Whether to throw an GridError if r is outside the grid edges
:return: 3-element ndarray specifying the indices
:raises: GridError
Args:
r: Natural position that we will convert into indices (3-element ndarray or list)
which_shifts: which grid number (`shifts`) to use
round_ind: Whether to round the returned indices to the nearest integers.
check_bounds: Whether to throw an `GridError` if `r` is outside the grid edges
Returns:
3-element ndarray specifying the indices
Raises:
`GridError` if invalid `which_shifts`
`GridError` if `check_bounds` and out of bounds
"""
r = numpy.squeeze(r)
if r.size != 3:

View File

@ -1,9 +1,9 @@
"""
Readback and visualization methods for Grid class
"""
from typing import Dict
from typing import Dict, Optional, Union, Any
import numpy
import numpy # type: ignore
from numpy import floor, ceil, zeros
from . import GridError, Direction
@ -15,21 +15,24 @@ from ._helpers import is_scalar
def get_slice(self,
surface_normal: Direction or int,
surface_normal: Union[Direction, int],
center: float,
which_shifts: int = 0,
sample_period: int = 1
) -> numpy.ndarray:
"""
Retrieve a slice of a grid.
Interpolates if given a position between two planes.
Retrieve a slice of a grid.
Interpolates if given a position between two planes.
:param surface_normal: Axis normal to the plane we're displaying. Can be a Direction or
integer in range(3)
:param center: Scalar specifying position along surface_normal axis.
:param which_shifts: Which grid to display. Default is the first grid (0).
:param sample_period: Period for down-sampling the image. Default 1 (disabled)
:return Array containing the portion of the grid.
Args:
surface_normal: Axis normal to the plane we're displaying. Can be a `Direction` or
integer in `range(3)`
center: Scalar specifying position along surface_normal axis.
which_shifts: Which grid to display. Default is the first grid (0).
sample_period: Period for down-sampling the image. Default 1 (disabled)
Returns:
Array containing the portion of the grid.
"""
if not is_scalar(center) and numpy.isreal(center):
raise GridError('center must be a real scalar')
@ -77,22 +80,24 @@ def get_slice(self,
def visualize_slice(self,
surface_normal: Direction or int,
surface_normal: Union[Direction, int],
center: float,
which_shifts: int = 0,
sample_period: int = 1,
finalize: bool = True,
pcolormesh_args: Dict = None):
pcolormesh_args: Optional[Dict[str, Any]] = None,
) -> None:
"""
Visualize a slice of a grid.
Interpolates if given a position between two planes.
:param surface_normal: Axis normal to the plane we're displaying. Can be a Direction or
integer in range(3)
:param center: Scalar specifying position along surface_normal axis.
:param which_shifts: Which grid to display. Default is the first grid (0).
:param sample_period: Period for down-sampling the image. Default 1 (disabled)
:param finalize: Whether to call pyplot.show() after constructing the plot. Default True
Args:
surface_normal: Axis normal to the plane we're displaying. Can be a `Direction` or
integer in `range(3)`
center: Scalar specifying position along surface_normal axis.
which_shifts: Which grid to display. Default is the first grid (0).
sample_period: Period for down-sampling the image. Default 1 (disabled)
finalize: Whether to call `pyplot.show()` after constructing the plot. Default `True`
"""
from matplotlib import pyplot
@ -125,19 +130,21 @@ def visualize_slice(self,
def visualize_isosurface(self,
level: float = None,
level: Optional[float] = None,
which_shifts: int = 0,
sample_period: int = 1,
show_edges: bool = True,
finalize: bool = True):
finalize: bool = True,
) -> None:
"""
Draw an isosurface plot of the device.
:param level: Value at which to find isosurface. Default (None) uses mean value in grid.
:param which_shifts: Which grid to display. Default is the first grid (0).
:param sample_period: Period for down-sampling the image. Default 1 (disabled)
:param show_edges: Whether to draw triangle edges. Default True
:param finalize: Whether to call pyplot.show() after constructing the plot. Default True
Args:
level: Value at which to find isosurface. Default (None) uses mean value in grid.
which_shifts: Which grid to display. Default is the first grid (0).
sample_period: Period for down-sampling the image. Default 1 (disabled)
show_edges: Whether to draw triangle edges. Default `True`
finalize: Whether to call `pyplot.show()` after constructing the plot. Default `True`
"""
from matplotlib import pyplot
import skimage.measure