prefab 1.1.5__py3-none-any.whl → 1.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prefab/__init__.py +1 -1
- prefab/__main__.py +14 -11
- prefab/compare.py +16 -1
- prefab/device.py +183 -118
- prefab/geometry.py +66 -22
- prefab/predict.py +51 -21
- prefab/read.py +122 -76
- prefab/shapes.py +82 -81
- {prefab-1.1.5.dist-info → prefab-1.1.6.dist-info}/METADATA +4 -2
- prefab-1.1.6.dist-info/RECORD +13 -0
- {prefab-1.1.5.dist-info → prefab-1.1.6.dist-info}/WHEEL +1 -1
- prefab-1.1.5.dist-info/RECORD +0 -13
- {prefab-1.1.5.dist-info → prefab-1.1.6.dist-info}/licenses/LICENSE +0 -0
prefab/geometry.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""Provides functions for manipulating ndarrays of device geometries."""
|
|
2
2
|
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
3
5
|
import cv2
|
|
4
6
|
import numpy as np
|
|
5
|
-
from skimage.morphology import closing, disk, opening, square
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
def normalize(device_array: np.ndarray) -> np.ndarray:
|
|
@@ -34,9 +35,9 @@ def binarize(
|
|
|
34
35
|
----------
|
|
35
36
|
device_array : np.ndarray
|
|
36
37
|
The input array to be binarized.
|
|
37
|
-
eta : float
|
|
38
|
+
eta : float
|
|
38
39
|
The threshold value for binarization. Defaults to 0.5.
|
|
39
|
-
beta : float
|
|
40
|
+
beta : float
|
|
40
41
|
The scaling factor for the binarization process. A higher value makes the
|
|
41
42
|
transition sharper. Defaults to np.inf, which results in a hard threshold.
|
|
42
43
|
|
|
@@ -60,7 +61,7 @@ def binarize_hard(device_array: np.ndarray, eta: float = 0.5) -> np.ndarray:
|
|
|
60
61
|
----------
|
|
61
62
|
device_array : np.ndarray
|
|
62
63
|
The input array to be binarized.
|
|
63
|
-
eta : float
|
|
64
|
+
eta : float
|
|
64
65
|
The threshold value for binarization. Defaults to 0.5.
|
|
65
66
|
|
|
66
67
|
Returns
|
|
@@ -95,11 +96,12 @@ def binarize_sem(sem_array: np.ndarray) -> np.ndarray:
|
|
|
95
96
|
|
|
96
97
|
def binarize_monte_carlo(
|
|
97
98
|
device_array: np.ndarray,
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
noise_magnitude: float,
|
|
100
|
+
blur_radius: float,
|
|
100
101
|
) -> np.ndarray:
|
|
101
102
|
"""
|
|
102
|
-
Binarize the input ndarray using a
|
|
103
|
+
Binarize the input ndarray using a dynamic thresholding approach to simulate surface
|
|
104
|
+
roughness.
|
|
103
105
|
|
|
104
106
|
This function applies a dynamic thresholding technique where the threshold value is
|
|
105
107
|
determined by a base value perturbed by Gaussian-distributed random noise. The
|
|
@@ -107,14 +109,19 @@ def binarize_monte_carlo(
|
|
|
107
109
|
simulating a potentially more realistic scenario where the threshold is not uniform
|
|
108
110
|
across the device.
|
|
109
111
|
|
|
112
|
+
Notes
|
|
113
|
+
-----
|
|
114
|
+
This is a temporary solution, where the defaults are chosen based on what looks
|
|
115
|
+
good. A better, data-driven approach is needed.
|
|
116
|
+
|
|
110
117
|
Parameters
|
|
111
118
|
----------
|
|
112
119
|
device_array : np.ndarray
|
|
113
120
|
The input array to be binarized.
|
|
114
|
-
|
|
121
|
+
noise_magnitude : float
|
|
115
122
|
The standard deviation of the Gaussian distribution used to generate noise for
|
|
116
123
|
the threshold values. This controls the amount of randomness in the threshold.
|
|
117
|
-
|
|
124
|
+
blur_radius : float
|
|
118
125
|
The standard deviation for the Gaussian kernel used in blurring the threshold
|
|
119
126
|
map. This controls the spatial variation of the threshold across the array.
|
|
120
127
|
|
|
@@ -127,10 +134,10 @@ def binarize_monte_carlo(
|
|
|
127
134
|
device_array = np.squeeze(device_array)
|
|
128
135
|
base_threshold = np.random.normal(loc=0.5, scale=0.1)
|
|
129
136
|
threshold_noise = np.random.normal(
|
|
130
|
-
loc=0, scale=
|
|
137
|
+
loc=0, scale=noise_magnitude, size=device_array.shape
|
|
131
138
|
)
|
|
132
139
|
spatial_threshold = cv2.GaussianBlur(
|
|
133
|
-
threshold_noise, ksize=(0, 0), sigmaX=
|
|
140
|
+
threshold_noise, ksize=(0, 0), sigmaX=blur_radius
|
|
134
141
|
)
|
|
135
142
|
dynamic_threshold = base_threshold + spatial_threshold
|
|
136
143
|
binarized_array = np.where(device_array < dynamic_threshold, 0.0, 1.0)
|
|
@@ -149,9 +156,9 @@ def ternarize(
|
|
|
149
156
|
----------
|
|
150
157
|
device_array : np.ndarray
|
|
151
158
|
The input array to be ternarized.
|
|
152
|
-
eta1 : float
|
|
159
|
+
eta1 : float
|
|
153
160
|
The first threshold value for ternarization. Defaults to 1/3.
|
|
154
|
-
eta2 : float
|
|
161
|
+
eta2 : float
|
|
155
162
|
The second threshold value for ternarization. Defaults to 2/3.
|
|
156
163
|
|
|
157
164
|
Returns
|
|
@@ -162,7 +169,9 @@ def ternarize(
|
|
|
162
169
|
return np.where(device_array < eta1, 0.0, np.where(device_array >= eta2, 1.0, 0.5))
|
|
163
170
|
|
|
164
171
|
|
|
165
|
-
def trim(
|
|
172
|
+
def trim(
|
|
173
|
+
device_array: np.ndarray, buffer_thickness: Optional[dict[str, int]] = None
|
|
174
|
+
) -> np.ndarray:
|
|
166
175
|
"""
|
|
167
176
|
Trim the input ndarray by removing rows and columns that are completely zero.
|
|
168
177
|
|
|
@@ -170,7 +179,7 @@ def trim(device_array: np.ndarray, buffer_thickness: dict = None) -> np.ndarray:
|
|
|
170
179
|
----------
|
|
171
180
|
device_array : np.ndarray
|
|
172
181
|
The input array to be trimmed.
|
|
173
|
-
buffer_thickness : dict,
|
|
182
|
+
buffer_thickness : Optional[dict[str, int]]
|
|
174
183
|
A dictionary specifying the thickness of the buffer to leave around the non-zero
|
|
175
184
|
elements of the array. Should contain keys 'top', 'bottom', 'left', 'right'.
|
|
176
185
|
Defaults to None, which means no buffer is added.
|
|
@@ -200,6 +209,30 @@ def trim(device_array: np.ndarray, buffer_thickness: dict = None) -> np.ndarray:
|
|
|
200
209
|
]
|
|
201
210
|
|
|
202
211
|
|
|
212
|
+
def pad(device_array: np.ndarray, pad_width: int) -> np.ndarray:
|
|
213
|
+
"""
|
|
214
|
+
Pad the input ndarray uniformly with a specified width on all sides.
|
|
215
|
+
|
|
216
|
+
Parameters
|
|
217
|
+
----------
|
|
218
|
+
device_array : np.ndarray
|
|
219
|
+
The input array to be padded.
|
|
220
|
+
pad_width : int
|
|
221
|
+
The number of pixels to pad on each side.
|
|
222
|
+
|
|
223
|
+
Returns
|
|
224
|
+
-------
|
|
225
|
+
np.ndarray
|
|
226
|
+
The padded array.
|
|
227
|
+
"""
|
|
228
|
+
return np.pad(
|
|
229
|
+
device_array,
|
|
230
|
+
pad_width=((pad_width, pad_width), (pad_width, pad_width), (0, 0)),
|
|
231
|
+
mode="constant",
|
|
232
|
+
constant_values=0,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
203
236
|
def blur(device_array: np.ndarray, sigma: float = 1.0) -> np.ndarray:
|
|
204
237
|
"""
|
|
205
238
|
Apply Gaussian blur to the input ndarray and normalize the result.
|
|
@@ -208,7 +241,7 @@ def blur(device_array: np.ndarray, sigma: float = 1.0) -> np.ndarray:
|
|
|
208
241
|
----------
|
|
209
242
|
device_array : np.ndarray
|
|
210
243
|
The input array to be blurred.
|
|
211
|
-
sigma : float
|
|
244
|
+
sigma : float
|
|
212
245
|
The standard deviation for the Gaussian kernel. This controls the amount of
|
|
213
246
|
blurring. Defaults to 1.0.
|
|
214
247
|
|
|
@@ -318,13 +351,18 @@ def enforce_feature_size(
|
|
|
318
351
|
device geometry are at least the specified minimum size. It uses either a disk
|
|
319
352
|
or square structuring element for the operations.
|
|
320
353
|
|
|
354
|
+
Notes
|
|
355
|
+
-----
|
|
356
|
+
This function does not guarantee that the minimum feature size is enforced in all
|
|
357
|
+
cases. A better process is needed.
|
|
358
|
+
|
|
321
359
|
Parameters
|
|
322
360
|
----------
|
|
323
361
|
device_array : np.ndarray
|
|
324
362
|
The input array representing the device geometry.
|
|
325
363
|
min_feature_size : int
|
|
326
364
|
The minimum feature size to enforce, in nanometers.
|
|
327
|
-
strel : str
|
|
365
|
+
strel : str
|
|
328
366
|
The type of structuring element to use. Can be either "disk" or "square".
|
|
329
367
|
Defaults to "disk".
|
|
330
368
|
|
|
@@ -339,12 +377,18 @@ def enforce_feature_size(
|
|
|
339
377
|
If an invalid structuring element type is specified.
|
|
340
378
|
"""
|
|
341
379
|
if strel == "disk":
|
|
342
|
-
|
|
380
|
+
kernel = cv2.getStructuringElement(
|
|
381
|
+
cv2.MORPH_ELLIPSE, (min_feature_size, min_feature_size)
|
|
382
|
+
)
|
|
343
383
|
elif strel == "square":
|
|
344
|
-
|
|
384
|
+
kernel = cv2.getStructuringElement(
|
|
385
|
+
cv2.MORPH_RECT, (min_feature_size, min_feature_size)
|
|
386
|
+
)
|
|
345
387
|
else:
|
|
346
388
|
raise ValueError(f"Invalid structuring element: {strel}")
|
|
347
389
|
|
|
348
|
-
|
|
349
|
-
modified_geometry =
|
|
350
|
-
|
|
390
|
+
device_array_2d = (device_array[:, :, 0] * 255).astype(np.uint8)
|
|
391
|
+
modified_geometry = cv2.morphologyEx(device_array_2d, cv2.MORPH_CLOSE, kernel)
|
|
392
|
+
modified_geometry = cv2.morphologyEx(modified_geometry, cv2.MORPH_OPEN, kernel)
|
|
393
|
+
|
|
394
|
+
return np.expand_dims(modified_geometry.astype(float) / 255, axis=-1)
|
prefab/predict.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Prediction functions for ndarrays of device geometries."""
|
|
2
2
|
|
|
3
3
|
import base64
|
|
4
4
|
import io
|
|
@@ -16,7 +16,8 @@ from tqdm import tqdm
|
|
|
16
16
|
from .geometry import binarize_hard
|
|
17
17
|
from .models import Model
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
BASE_ENDPOINT_URL = "https://prefab-photonics--predict"
|
|
20
|
+
ENDPOINT_VERSION = 1
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def predict_array(
|
|
@@ -46,15 +47,16 @@ def predict_array(
|
|
|
46
47
|
creation and the data it was trained on, ensuring the prediction is tailored to
|
|
47
48
|
specific fabrication parameters.
|
|
48
49
|
model_type : str
|
|
49
|
-
The type of model to use (e.g., 'p', 'c', 's'
|
|
50
|
+
The type of model to use (e.g., 'p' for prediction, 'c' for correction, or 's'
|
|
51
|
+
for SEMulate).
|
|
50
52
|
binarize : bool
|
|
51
53
|
If True, the predicted device geometry will be binarized using a threshold
|
|
52
54
|
method. This is useful for converting probabilistic predictions into binary
|
|
53
55
|
geometries.
|
|
54
|
-
gpu : bool
|
|
56
|
+
gpu : bool
|
|
55
57
|
If True, the prediction will be performed on a GPU. Defaults to False. Note: The
|
|
56
|
-
GPU option has more overhead and will take longer for small devices, but
|
|
57
|
-
faster for larger devices.
|
|
58
|
+
GPU option has more startup overhead and will take longer for small devices, but
|
|
59
|
+
will be faster for larger devices.
|
|
58
60
|
|
|
59
61
|
Returns
|
|
60
62
|
-------
|
|
@@ -68,7 +70,11 @@ def predict_array(
|
|
|
68
70
|
"""
|
|
69
71
|
headers = _prepare_headers()
|
|
70
72
|
predict_data = _prepare_predict_data(device_array, model, model_type, binarize)
|
|
71
|
-
endpoint_url =
|
|
73
|
+
endpoint_url = (
|
|
74
|
+
f"{BASE_ENDPOINT_URL}-gpu-v{ENDPOINT_VERSION}.modal.run"
|
|
75
|
+
if gpu
|
|
76
|
+
else f"{BASE_ENDPOINT_URL}-v{ENDPOINT_VERSION}.modal.run"
|
|
77
|
+
)
|
|
72
78
|
|
|
73
79
|
try:
|
|
74
80
|
with requests.post(
|
|
@@ -78,7 +84,10 @@ def predict_array(
|
|
|
78
84
|
stream=True,
|
|
79
85
|
) as response:
|
|
80
86
|
response.raise_for_status()
|
|
81
|
-
|
|
87
|
+
result = _process_response(response, model_type, binarize)
|
|
88
|
+
if result is None:
|
|
89
|
+
raise RuntimeError("No prediction result received.")
|
|
90
|
+
return result
|
|
82
91
|
except requests.RequestException as e:
|
|
83
92
|
raise RuntimeError(f"Request failed: {e}") from e
|
|
84
93
|
|
|
@@ -86,9 +95,33 @@ def predict_array(
|
|
|
86
95
|
def _predict_array_with_grad(
|
|
87
96
|
device_array: np.ndarray, model: Model
|
|
88
97
|
) -> tuple[np.ndarray, np.ndarray]:
|
|
98
|
+
"""
|
|
99
|
+
Predict the nanofabrication outcome of a device array and compute its gradient.
|
|
100
|
+
|
|
101
|
+
This function predicts the outcome of the nanofabrication process for a given
|
|
102
|
+
device array using a specified model. It also computes the gradient of the
|
|
103
|
+
prediction with respect to the input device array.
|
|
104
|
+
|
|
105
|
+
Notes
|
|
106
|
+
-----
|
|
107
|
+
This function is currently not used in the main `predict_array` function as
|
|
108
|
+
the main `predict_array` function (e.g., GPU support and progress bar) for now.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
device_array : np.ndarray
|
|
113
|
+
A 2D array representing the planar geometry of the device.
|
|
114
|
+
model : Model
|
|
115
|
+
The model to use for prediction, representing a specific fabrication process.
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
tuple[np.ndarray, np.ndarray]
|
|
120
|
+
The predicted output array and gradient array.
|
|
121
|
+
"""
|
|
89
122
|
headers = _prepare_headers()
|
|
90
123
|
predict_data = _prepare_predict_data(device_array, model, "p", False)
|
|
91
|
-
endpoint_url = f"{
|
|
124
|
+
endpoint_url = f"{BASE_ENDPOINT_URL}-with-grad-v{ENDPOINT_VERSION}.modal.run"
|
|
92
125
|
|
|
93
126
|
response = requests.post(
|
|
94
127
|
endpoint_url, data=json.dumps(predict_data), headers=headers
|
|
@@ -99,7 +132,6 @@ def _predict_array_with_grad(
|
|
|
99
132
|
gradient_max = response.json()["gradient_max"]
|
|
100
133
|
gradient_range = gradient_max - gradient_min
|
|
101
134
|
gradient_array = gradient_array * gradient_range + gradient_min
|
|
102
|
-
|
|
103
135
|
return (prediction_array, gradient_array)
|
|
104
136
|
|
|
105
137
|
|
|
@@ -110,7 +142,8 @@ def predict_array_with_grad(device_array: np.ndarray, model: Model) -> np.ndarra
|
|
|
110
142
|
|
|
111
143
|
This function predicts the outcome of the nanofabrication process for a given
|
|
112
144
|
device array using a specified model. It also computes the gradient of the
|
|
113
|
-
prediction with respect to the input device array
|
|
145
|
+
prediction with respect to the input device array, making it suitable for use in
|
|
146
|
+
automatic differentiation applications (e.g., autograd).
|
|
114
147
|
|
|
115
148
|
Parameters
|
|
116
149
|
----------
|
|
@@ -127,32 +160,29 @@ def predict_array_with_grad(device_array: np.ndarray, model: Model) -> np.ndarra
|
|
|
127
160
|
prediction_array, gradient_array = _predict_array_with_grad(
|
|
128
161
|
device_array=device_array, model=model
|
|
129
162
|
)
|
|
130
|
-
predict_array_with_grad.gradient_array = gradient_array
|
|
163
|
+
predict_array_with_grad.gradient_array = gradient_array # type: ignore
|
|
131
164
|
return prediction_array
|
|
132
165
|
|
|
133
166
|
|
|
134
|
-
def predict_array_with_grad_vjp(ans: np.ndarray,
|
|
167
|
+
def predict_array_with_grad_vjp(ans: np.ndarray, device_array: np.ndarray, *args):
|
|
135
168
|
"""
|
|
136
169
|
Define the vector-Jacobian product (VJP) for the prediction function.
|
|
137
170
|
|
|
138
|
-
This function provides the VJP for the `predict_array_with_grad` function,
|
|
139
|
-
which is used in reverse-mode automatic differentiation to compute gradients.
|
|
140
|
-
|
|
141
171
|
Parameters
|
|
142
172
|
----------
|
|
143
173
|
ans : np.ndarray
|
|
144
174
|
The output of the `predict_array_with_grad` function.
|
|
145
|
-
|
|
175
|
+
device_array : np.ndarray
|
|
146
176
|
The input device array for which the gradient is computed.
|
|
147
|
-
|
|
148
|
-
|
|
177
|
+
*args :
|
|
178
|
+
Additional arguments that aren't used in the VJP computation.
|
|
149
179
|
|
|
150
180
|
Returns
|
|
151
181
|
-------
|
|
152
182
|
function
|
|
153
183
|
A function that computes the VJP given an upstream gradient `g`.
|
|
154
184
|
"""
|
|
155
|
-
grad_x = predict_array_with_grad.gradient_array
|
|
185
|
+
grad_x = predict_array_with_grad.gradient_array # type: ignore
|
|
156
186
|
|
|
157
187
|
def vjp(g: np.ndarray) -> np.ndarray:
|
|
158
188
|
return g * grad_x
|
|
@@ -195,7 +225,7 @@ def _read_tokens():
|
|
|
195
225
|
"Could not validate user.\n"
|
|
196
226
|
"Please update prefab using: pip install --upgrade prefab.\n"
|
|
197
227
|
"Signup/login and generate a new token.\n"
|
|
198
|
-
"See https://
|
|
228
|
+
"See https://docs.prefabphotonics.com/."
|
|
199
229
|
) from None
|
|
200
230
|
|
|
201
231
|
|