Skip to content

Commit

Permalink
fix adjoint util filter radius, rename "feature_size" to "radius"
Browse files Browse the repository at this point in the history
  • Loading branch information
tylerflex committed Aug 29, 2023
1 parent a6f54f7 commit a2fd7e4
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 28 deletions.
6 changes: 4 additions & 2 deletions tests/test_plugins/test_adjoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from tidy3d.plugins.adjoint.web import run_local, run_async_local
from tidy3d.plugins.adjoint.components.data.data_array import VALUE_FILTER_THRESHOLD
from tidy3d.plugins.adjoint.utils.penalty import RadiusPenalty
from tidy3d.plugins.adjoint.utils.filter import ConicFilter, BinaryProjector
from tidy3d.plugins.adjoint.utils.filter import ConicFilter, BinaryProjector, CircularFilter
from tidy3d.web.container import BatchData

from ..utils import run_emulated, assert_log_level, log_capture, run_async_emulated
Expand Down Expand Up @@ -1430,7 +1430,9 @@ def test_adjoint_utils(strict_binarize):
# projection / filtering
image = sim.input_structures[2].medium.eps_dataset.eps_xx.values

filter = ConicFilter(feature_size=1.5, design_region_dl=0.1)
filter = ConicFilter(radius=1.5, design_region_dl=0.1)
filter.evaluate(image)
filter = CircularFilter(radius=1.5, design_region_dl=0.1)
filter.evaluate(image)
projector = BinaryProjector(vmin=1.0, vmax=2.0, beta=1.5, strict_binarize=strict_binarize)
projector.evaluate(image)
Expand Down
100 changes: 74 additions & 26 deletions tidy3d/plugins/adjoint/utils/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,13 @@ def evaluate(self, spatial_data: jnp.array) -> jnp.array:
"""Process supplied array containing spatial data."""


class ConicFilter(Filter):
"""Filter that convolves an image with a conical mask, used for larger feature sizes.
Note
----
.. math::
filter(radius) = max(feature_radius - radius, 0)
"""
class AbstractCircularFilter(Filter, ABC):
"""Abstract filter class. Initializes with parameters and .evaluate() on a design."""

feature_size: float = pd.Field(
radius: float = pd.Field(
...,
title="Filter Radius",
description="Convolve spatial data with a conic filter. "
"Useful for smoothing feature sizes.",
description="Radius of the filter to convolve with supplied spatial data.",
units=MICROMETER,
)

Expand All @@ -46,27 +37,84 @@ class ConicFilter(Filter):
)

@property
def filter_radius(self) -> float:
"""Filter radius."""
return np.ceil((self.feature_size * np.sqrt(3)) / self.design_region_dl)
def filter_radius_pixels(self) -> int:
"""Filter radius in pixels."""
return np.ceil(self.radius / self.design_region_dl)

@pd.root_validator(pre=True)
def _deprecate_feature_size(cls, values):
"""Extra warning for user using `feature_size` field."""
if "feature_size" in values:
raise pd.ValidationError(
"The 'feature_size' field of circular filters available in 2.4 pre-releases was "
"renamed to 'radius' for the official 2.4.0 release. "
"If you're seeing this message, please change your script to use that field name."
)
return values

@abstractmethod
def make_kernel(self, coords_rad: jnp.array) -> jnp.array:
"""Function to make the kernel out of a coordinate grid of radius values."""

def evaluate(self, spatial_data: jnp.array) -> jnp.array:
"""Process on supplied spatial data."""

rho = jnp.squeeze(spatial_data)
dims = len(rho.shape)
num_dims = len(rho.shape)

# Builds the conic filter and apply it to design parameters.
coords_1d = np.linspace(
-self.filter_radius, self.filter_radius, int(2 * self.filter_radius + 1)
)
meshgrid_args = [coords_1d.copy() for _ in range(dims)]

coords_1d = np.arange(-self.filter_radius_pixels, self.filter_radius_pixels + 1)
meshgrid_args = [coords_1d.copy() for _ in range(num_dims)]
meshgrid_coords = np.meshgrid(*meshgrid_args)
coords_rad = np.sqrt(np.sum([np.square(v) for v in meshgrid_coords], axis=0))
kernel = jnp.where(self.filter_radius - coords_rad > 0, self.filter_radius - coords_rad, 0)
filt_den = jsp.signal.convolve(jnp.ones_like(rho), kernel, mode="same")
return jsp.signal.convolve(rho, kernel, mode="same") / filt_den

# construct the kernel
kernel = self.make_kernel(coords_rad)

# normalize by the kernel operating on a spatial_data of all ones
num = jsp.signal.convolve(rho, kernel, mode="same")
den = jsp.signal.convolve(jnp.ones_like(rho), kernel, mode="same")

return num / den


class ConicFilter(AbstractCircularFilter):
"""Filter that convolves an image with a conical mask, used for larger feature sizes.
Note
----
.. math::
filter(r) = max(radius - r, 0)
"""

def make_kernel(self, coords_rad: jnp.array) -> jnp.array:
"""Function to make the kernel out of a coordinate grid of radius values (in pixels)."""

kernel = self.filter_radius_pixels - coords_rad
kernel[coords_rad > self.filter_radius_pixels] = 0.0
return kernel


class CircularFilter(AbstractCircularFilter):
"""Filter that convolves an image with a circular mask, used for larger feature sizes.
Note
----
.. math::
filter(r) = 1 if r <= radius else 0
"""

def make_kernel(self, coords_rad: jnp.array) -> jnp.array:
"""Function to make the kernel out of a coordinate grid of radius values (in pixels)."""

# construct the kernel
kernel = np.ones_like(coords_rad)
kernel[coords_rad > self.filter_radius_pixels] = 0.0
return kernel


class BinaryProjector(Filter):
Expand All @@ -83,7 +131,7 @@ class BinaryProjector(Filter):

vmin: float = pd.Field(..., title="Min Value", description="Minimum value to project to.")

vmax: float = pd.Field(..., title="Min Value", description="Maximum value to project to.")
vmax: float = pd.Field(..., title="Max Value", description="Maximum value to project to.")

beta: float = pd.Field(
1.0,
Expand Down

0 comments on commit a2fd7e4

Please sign in to comment.