Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
Dusandinho committed Dec 30, 2024
2 parents 0c4474c + 5782060 commit e32f090
Show file tree
Hide file tree
Showing 14 changed files with 53,759 additions and 54 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

PreFab is a _virtual nanofabrication environment_ that leverages **deep learning** and **computer vision** to predict and correct for structural variations in integrated photonic devices during nanofabrication.

> **Try Rosette**: Want a more visual experience? Try [Rosette](https://rosette.dev) - our new layout tool with PreFab models built in, designed for rapid chip design.
## Prediction

PreFab predicts process-induced structural variations, including corner rounding, loss of small lines and islands, filling of narrow holes and channels, sidewall angle deviations, and stochastic effects. This allows designers to rapidly prototype and evaluate expected performance pre-fabrication.
Expand Down
8 changes: 8 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## [1.1.6](https://github.com/PreFab-Photonics/PreFab/releases/tag/v1.1.6) - 2024-12-30

- Added Tidy3D fabrication-aware inverse design (FAID) example notebook.
- Remove buffer from `Device` created with `read.from_sem` method.
- Handling of extended bounds in `read.from_sem` method.
- Use OpenCV for morphological operations in `geometry.enforce_feature_size`.
- Add handling for `None` casee for `BufferSpec` in `Device` constructor.

## [1.1.5](https://github.com/PreFab-Photonics/PreFab/releases/tag/v1.1.5) - 2024-11-05

- Fix alignment issue in `Device._device_to_gdstk` method, which is used in `Device.to_gdstk` and `Device.to_gds`.
Expand Down
Binary file added docs/assets/images/flux_ID_nopredict.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/flux_ID_predict.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/examples/5_prediction_simulation.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# Predicting and simulating waveguide Bragg gratings\n",
"# Simulating device predictions\n",
"\n",
"## Introduction\n",
"\n",
Expand Down
53,612 changes: 53,612 additions & 0 deletions docs/examples/6_fabrication-aware_inverse_design.ipynb

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ This guide will get you all set up and ready to use PreFab to predict the fabric
Before you can make PreFab requests, you will need to create an account[^1]. Sign
up [here](https://www.prefabphotonics.com/signup).

!!! info "Try Rosette"

Want a more visual experience? Try [Rosette](https://rosette.dev) - our new layout tool with PreFab models built in, designed for rapid chip design.

## Install PreFab

Before making your first prediction, follow the steps below to install the [PreFab Python package](https://pypi.org/project/prefab/).
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ nav:
- Working with sidewall angles: examples/3_sidewall_angle.ipynb
- SEM generation: examples/4_SEM_generation.ipynb
- Simulating a predicted device: examples/5_prediction_simulation.ipynb
- Fabrication-aware inverse design: examples/6_fabrication-aware_inverse_design.ipynb
- Blog:
- blog/index.md
- Reference:
Expand Down
2 changes: 1 addition & 1 deletion prefab/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import prefab as pf
"""

__version__ = "1.1.5"
__version__ = "1.1.6"

from . import compare, geometry, predict, read, shapes
from .device import BufferSpec, Device
Expand Down
101 changes: 67 additions & 34 deletions prefab/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from matplotlib.axes import Axes
from matplotlib.patches import Rectangle
from PIL import Image
from pydantic import BaseModel, Field, root_validator, validator
from pydantic import BaseModel, Field, model_validator, validator
from scipy.ndimage import distance_transform_edt
from skimage import measure

Expand Down Expand Up @@ -37,19 +37,21 @@ class BufferSpec(BaseModel):
----------
mode : dict[str, str]
A dictionary that defines the buffer mode for each side of the device
('top', 'bottom', 'left', 'right'), where 'constant' is used for isolated
structures and 'edge' is utilized for preserving the edge, such as for waveguide
connections.
('top', 'bottom', 'left', 'right'), where:
- 'constant' is used for isolated structures
- 'edge' is utilized for preserving the edge, such as for waveguide connections
- 'none' for no buffer on that side
thickness : dict[str, int]
A dictionary that defines the thickness of the buffer zone for each side of the
device ('top', 'bottom', 'left', 'right'). Each value must be greater than 0.
device ('top', 'bottom', 'left', 'right'). Each value must be greater than or
equal to 0.
Raises
------
ValueError
If any of the modes specified in the 'mode' dictionary are not one of the
allowed values ('constant', 'edge'). Or if any of the thickness values are not
greater than 0.
allowed values ('constant', 'edge', 'none'). Or if any of the thickness values
are negative.
Example
-------
Expand All @@ -58,20 +60,20 @@ class BufferSpec(BaseModel):
buffer_spec = pf.BufferSpec(
mode={
"top": "constant",
"bottom": "edge",
"bottom": "none",
"left": "constant",
"right": "edge",
},
thickness={
"top": 150,
"bottom": 100,
"bottom": 0,
"left": 200,
"right": 250,
},
)
"""

mode: dict[str, Literal["constant", "edge"]] = Field(
mode: dict[str, Literal["constant", "edge", "none"]] = Field(
default_factory=lambda: {
"top": "constant",
"bottom": "constant",
Expand All @@ -90,17 +92,32 @@ class BufferSpec(BaseModel):

@validator("mode", pre=True)
def check_mode(cls, v):
allowed_modes = ["constant", "edge"]
allowed_modes = ["constant", "edge", "none"]
if not all(mode in allowed_modes for mode in v.values()):
raise ValueError(f"Buffer mode must be one of {allowed_modes}, got '{v}'.")
return v

@validator("thickness")
def check_thickness(cls, v):
if not all(t > 0 for t in v.values()):
raise ValueError("All thickness values must be greater than 0.")
if not all(t >= 0 for t in v.values()):
raise ValueError("All thickness values must be greater than or equal to 0.")
return v

@model_validator(mode="after")
def check_none_thickness(cls, values):
mode = values.mode
thickness = values.thickness
for side in mode:
if mode[side] == "none" and thickness[side] != 0:
raise ValueError(
f"Thickness must be 0 when mode is 'none' for {side} side"
)
if mode[side] != "none" and thickness[side] == 0:
raise ValueError(
f"Mode must be 'none' when thickness is 0 for {side} side"
)
return values


class Device(BaseModel):
device_array: np.ndarray = Field(...)
Expand Down Expand Up @@ -164,30 +181,37 @@ def _initial_processing(self):
buffer_thickness = self.buffer_spec.thickness
buffer_mode = self.buffer_spec.mode

self.device_array = np.pad(
self.device_array,
pad_width=((buffer_thickness["top"], 0), (0, 0)),
mode=buffer_mode["top"],
)
self.device_array = np.pad(
self.device_array,
pad_width=((0, buffer_thickness["bottom"]), (0, 0)),
mode=buffer_mode["bottom"],
)
self.device_array = np.pad(
self.device_array,
pad_width=((0, 0), (buffer_thickness["left"], 0)),
mode=buffer_mode["left"],
)
self.device_array = np.pad(
self.device_array,
pad_width=((0, 0), (0, buffer_thickness["right"])),
mode=buffer_mode["right"],
)
if buffer_mode["top"] != "none":
self.device_array = np.pad(
self.device_array,
pad_width=((buffer_thickness["top"], 0), (0, 0)),
mode=buffer_mode["top"],
)

if buffer_mode["bottom"] != "none":
self.device_array = np.pad(
self.device_array,
pad_width=((0, buffer_thickness["bottom"]), (0, 0)),
mode=buffer_mode["bottom"],
)

if buffer_mode["left"] != "none":
self.device_array = np.pad(
self.device_array,
pad_width=((0, 0), (buffer_thickness["left"], 0)),
mode=buffer_mode["left"],
)

if buffer_mode["right"] != "none":
self.device_array = np.pad(
self.device_array,
pad_width=((0, 0), (0, buffer_thickness["right"])),
mode=buffer_mode["right"],
)

self.device_array = np.expand_dims(self.device_array, axis=-1)

@root_validator(pre=True)
@model_validator(mode="before")
def check_device_array(cls, values):
device_array = values.get("device_array")
if not isinstance(device_array, np.ndarray):
Expand Down Expand Up @@ -1251,6 +1275,15 @@ def trim(self) -> "Device":
)
return self.model_copy(update={"device_array": trimmed_device_array})

def pad(self, pad_width: int) -> "Device":
"""
Pad the device geometry with a specified width on all sides.
"""
padded_device_array = geometry.pad(
device_array=self.device_array, pad_width=pad_width
)
return self.model_copy(update={"device_array": padded_device_array})

def blur(self, sigma: float = 1.0) -> "Device":
"""
Apply Gaussian blur to the device geometry and normalize the result.
Expand Down
17 changes: 11 additions & 6 deletions prefab/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import cv2
import numpy as np
from skimage.morphology import closing, disk, opening, square


def normalize(device_array: np.ndarray) -> np.ndarray:
Expand Down Expand Up @@ -378,12 +377,18 @@ def enforce_feature_size(
If an invalid structuring element type is specified.
"""
if strel == "disk":
structuring_element = disk(radius=min_feature_size / 2)
kernel = cv2.getStructuringElement(
cv2.MORPH_ELLIPSE, (min_feature_size, min_feature_size)
)
elif strel == "square":
structuring_element = square(width=min_feature_size)
kernel = cv2.getStructuringElement(
cv2.MORPH_RECT, (min_feature_size, min_feature_size)
)
else:
raise ValueError(f"Invalid structuring element: {strel}")

modified_geometry = closing(device_array[:, :, 0], structuring_element)
modified_geometry = opening(modified_geometry, structuring_element)
return np.expand_dims(modified_geometry, axis=-1)
device_array_2d = (device_array[:, :, 0] * 255).astype(np.uint8)
modified_geometry = cv2.morphologyEx(device_array_2d, cv2.MORPH_CLOSE, kernel)
modified_geometry = cv2.morphologyEx(modified_geometry, cv2.MORPH_OPEN, kernel)

return np.expand_dims(modified_geometry.astype(float) / 255, axis=-1)
9 changes: 4 additions & 5 deletions prefab/predict.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,19 +164,18 @@ def predict_array_with_grad(device_array: np.ndarray, model: Model) -> np.ndarra
return prediction_array


def predict_array_with_grad_vjp(ans: np.ndarray, x: np.ndarray):
def predict_array_with_grad_vjp(ans: np.ndarray, device_array: np.ndarray, *args):
"""
Define the vector-Jacobian product (VJP) for the prediction function.
This function provides the VJP for the `predict_array_with_grad` function,
which is used in reverse-mode automatic differentiation to compute gradients.
Parameters
----------
ans : np.ndarray
The output of the `predict_array_with_grad` function.
x : np.ndarray
device_array : np.ndarray
The input device array for which the gradient is computed.
*args :
Additional arguments that aren't used in the VJP computation.
Returns
-------
Expand Down
53 changes: 47 additions & 6 deletions prefab/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import numpy as np

from . import geometry
from .device import Device
from .device import BufferSpec, Device

if TYPE_CHECKING:
import gdsfactory as gf
Expand Down Expand Up @@ -342,14 +342,55 @@ def from_sem(
device_array = cv2.resize(
device_array, dsize=(0, 0), fx=sem_resolution, fy=sem_resolution
)

if bounds is not None:
device_array = device_array[
device_array.shape[0] - bounds[1][1] : device_array.shape[0] - bounds[0][1],
bounds[0][0] : bounds[1][0],
]
pad_left = max(0, -bounds[0][0])
pad_right = max(0, bounds[1][0] - device_array.shape[1])
pad_bottom = max(0, -bounds[0][1])
pad_top = max(0, bounds[1][1] - device_array.shape[0])

if pad_left or pad_right or pad_top or pad_bottom:
device_array = np.pad(
device_array,
((pad_top, pad_bottom), (pad_left, pad_right)),
mode="constant",
constant_values=0,
)

start_x = max(0, bounds[0][0] + pad_left)
end_x = min(device_array.shape[1], bounds[1][0] + pad_left)
start_y = max(0, device_array.shape[0] - (bounds[1][1] + pad_top))
end_y = min(
device_array.shape[0], device_array.shape[0] - (bounds[0][1] + pad_top)
)

if start_x >= end_x or start_y >= end_y:
raise ValueError(
"Invalid bounds resulted in zero-size array: "
f"x=[{start_x}, {end_x}], "
f"y=[{start_y}, {end_y}]"
)

device_array = device_array[start_y:end_y, start_x:end_x]

if binarize:
device_array = geometry.binarize_sem(device_array)
return Device(device_array=device_array, **kwargs)

buffer_spec = BufferSpec(
mode={
"top": "none",
"bottom": "none",
"left": "none",
"right": "none",
},
thickness={
"top": 0,
"bottom": 0,
"left": 0,
"right": 0,
},
)
return Device(device_array=device_array, buffer_spec=buffer_spec, **kwargs)


def get_sem_resolution(sem_path: str, sem_resolution_key: str) -> float:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "prefab"
version = "1.1.5"
version = "1.1.6"
description = "Artificial nanofabrication of integrated photonic circuits using deep learning"
authors = [{ name = "Dusan Gostimirovic", email = "dusan@prefabphotonics.com" }]
keywords = ["photonics", "nanofabrication", "machine-learning"]
Expand Down

0 comments on commit e32f090

Please sign in to comment.