prefab 0.4.7__py3-none-any.whl → 1.1.8__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 +15 -38
- prefab/__main__.py +95 -0
- prefab/compare.py +126 -0
- prefab/device.py +1486 -0
- prefab/geometry.py +394 -0
- prefab/models.py +114 -0
- prefab/predict.py +337 -0
- prefab/read.py +503 -0
- prefab/shapes.py +773 -0
- {prefab-0.4.7.dist-info → prefab-1.1.8.dist-info}/METADATA +47 -34
- prefab-1.1.8.dist-info/RECORD +14 -0
- {prefab-0.4.7.dist-info → prefab-1.1.8.dist-info}/WHEEL +1 -1
- prefab-1.1.8.dist-info/entry_points.txt +2 -0
- prefab/io.py +0 -200
- prefab/predictor.py +0 -161
- prefab/processor.py +0 -248
- prefab-0.4.7.dist-info/RECORD +0 -8
- {prefab-0.4.7.dist-info → prefab-1.1.8.dist-info}/licenses/LICENSE +0 -0
prefab/read.py
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
"""Functions to create a Device from various data sources."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
|
5
|
+
|
|
6
|
+
import cv2
|
|
7
|
+
import gdstk
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from . import geometry
|
|
11
|
+
from .device import BufferSpec, Device
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
import gdsfactory as gf
|
|
15
|
+
import tidy3d as td
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def from_ndarray(
|
|
19
|
+
ndarray: np.ndarray, resolution: float = 1.0, binarize: bool = True, **kwargs
|
|
20
|
+
) -> Device:
|
|
21
|
+
"""
|
|
22
|
+
Create a Device from an ndarray.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
ndarray : np.ndarray
|
|
27
|
+
The input array representing the device geometry.
|
|
28
|
+
resolution : float
|
|
29
|
+
The resolution of the ndarray in nanometers per pixel, defaulting to 1.0 nm per
|
|
30
|
+
pixel. If specified, the input array will be resized based on this resolution to
|
|
31
|
+
match the desired physical size.
|
|
32
|
+
binarize : bool
|
|
33
|
+
If True, the input array will be binarized (converted to binary values) before
|
|
34
|
+
conversion to a Device object. This is useful for processing grayscale arrays
|
|
35
|
+
into binary masks. Defaults to True.
|
|
36
|
+
**kwargs
|
|
37
|
+
Additional keyword arguments to be passed to the Device constructor.
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
Device
|
|
42
|
+
A Device object representing the input array, after resizing and binarization.
|
|
43
|
+
"""
|
|
44
|
+
device_array = ndarray
|
|
45
|
+
if resolution != 1.0:
|
|
46
|
+
device_array = cv2.resize(
|
|
47
|
+
device_array, dsize=(0, 0), fx=resolution, fy=resolution
|
|
48
|
+
)
|
|
49
|
+
if binarize:
|
|
50
|
+
device_array = geometry.binarize_hard(device_array)
|
|
51
|
+
return Device(device_array=device_array, **kwargs)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def from_img(
|
|
55
|
+
img_path: str, img_width_nm: Optional[int] = None, binarize: bool = True, **kwargs
|
|
56
|
+
) -> Device:
|
|
57
|
+
"""
|
|
58
|
+
Create a Device from an image file.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
img_path : str
|
|
63
|
+
The path to the image file to be converted into a Device object.
|
|
64
|
+
img_width_nm : Optional[int]
|
|
65
|
+
The width of the image in nanometers. If specified, the Device will be resized
|
|
66
|
+
to this width while maintaining aspect ratio. If None, no resizing is performed.
|
|
67
|
+
binarize : bool
|
|
68
|
+
If True, the image will be binarized (converted to binary values) before
|
|
69
|
+
conversion to a Device object. This is useful for processing grayscale images
|
|
70
|
+
into binary masks. Defaults to True.
|
|
71
|
+
**kwargs
|
|
72
|
+
Additional keyword arguments to be passed to the Device constructor.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
Device
|
|
77
|
+
A Device object representing the processed image, after optional resizing and
|
|
78
|
+
binarization.
|
|
79
|
+
"""
|
|
80
|
+
device_array = cv2.imread(img_path, flags=cv2.IMREAD_GRAYSCALE) / 255
|
|
81
|
+
if img_width_nm is not None:
|
|
82
|
+
resolution = img_width_nm / device_array.shape[1]
|
|
83
|
+
device_array = cv2.resize(
|
|
84
|
+
device_array, dsize=(0, 0), fx=resolution, fy=resolution
|
|
85
|
+
)
|
|
86
|
+
if binarize:
|
|
87
|
+
device_array = geometry.binarize_hard(device_array)
|
|
88
|
+
return Device(device_array=device_array, **kwargs)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def from_gds(
|
|
92
|
+
gds_path: str,
|
|
93
|
+
cell_name: str,
|
|
94
|
+
gds_layer: tuple[int, int] = (1, 0),
|
|
95
|
+
bounds: Optional[tuple[tuple[float, float], tuple[float, float]]] = None,
|
|
96
|
+
**kwargs,
|
|
97
|
+
) -> Device:
|
|
98
|
+
"""
|
|
99
|
+
Create a Device from a GDS cell.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
gds_path : str
|
|
104
|
+
The file path to the GDS file.
|
|
105
|
+
cell_name : str
|
|
106
|
+
The name of the cell within the GDS file to be converted into a Device object.
|
|
107
|
+
gds_layer : tuple[int, int]
|
|
108
|
+
A tuple specifying the layer and datatype to be used from the GDS file. Defaults
|
|
109
|
+
to (1, 0).
|
|
110
|
+
bounds : Optional[tuple[tuple[float, float], tuple[float, float]]]
|
|
111
|
+
A tuple specifying the bounds for cropping the cell before conversion, formatted
|
|
112
|
+
as ((min_x, min_y), (max_x, max_y)), in units of the GDS file. If None, the
|
|
113
|
+
entire cell is used.
|
|
114
|
+
**kwargs
|
|
115
|
+
Additional keyword arguments to be passed to the Device constructor.
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
Device
|
|
120
|
+
A Device object representing the specified cell from the GDS file, after
|
|
121
|
+
processing based on the specified layer.
|
|
122
|
+
"""
|
|
123
|
+
gdstk_library = gdstk.read_gds(gds_path)
|
|
124
|
+
gdstk_cell = gdstk_library[cell_name] # type: ignore
|
|
125
|
+
device_array = _gdstk_to_device_array(
|
|
126
|
+
gdstk_cell=gdstk_cell, gds_layer=gds_layer, bounds=bounds
|
|
127
|
+
)
|
|
128
|
+
return Device(device_array=device_array, **kwargs)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def from_gdstk(
|
|
132
|
+
gdstk_cell: gdstk.Cell,
|
|
133
|
+
gds_layer: tuple[int, int] = (1, 0),
|
|
134
|
+
bounds: Optional[tuple[tuple[float, float], tuple[float, float]]] = None,
|
|
135
|
+
**kwargs,
|
|
136
|
+
):
|
|
137
|
+
"""
|
|
138
|
+
Create a Device from a gdstk cell.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
gdstk_cell : gdstk.Cell
|
|
143
|
+
The gdstk.Cell object to be converted into a Device object.
|
|
144
|
+
gds_layer : tuple[int, int]
|
|
145
|
+
A tuple specifying the layer and datatype to be used from the cell. Defaults to
|
|
146
|
+
(1, 0).
|
|
147
|
+
bounds : tuple[tuple[float, float], tuple[float, float]]
|
|
148
|
+
A tuple specifying the bounds for cropping the cell before conversion, formatted
|
|
149
|
+
as ((min_x, min_y), (max_x, max_y)), in units of the GDS cell. If None, the
|
|
150
|
+
entire cell is used.
|
|
151
|
+
**kwargs
|
|
152
|
+
Additional keyword arguments to be passed to the Device constructor.
|
|
153
|
+
|
|
154
|
+
Returns
|
|
155
|
+
-------
|
|
156
|
+
Device
|
|
157
|
+
A Device object representing the gdstk.Cell, after processing based on the
|
|
158
|
+
specified layer.
|
|
159
|
+
"""
|
|
160
|
+
device_array = _gdstk_to_device_array(
|
|
161
|
+
gdstk_cell=gdstk_cell, gds_layer=gds_layer, bounds=bounds
|
|
162
|
+
)
|
|
163
|
+
return Device(device_array=device_array, **kwargs)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _gdstk_to_device_array(
|
|
167
|
+
gdstk_cell: gdstk.Cell,
|
|
168
|
+
gds_layer: tuple[int, int] = (1, 0),
|
|
169
|
+
bounds: Optional[tuple[tuple[float, float], tuple[float, float]]] = None,
|
|
170
|
+
) -> np.ndarray:
|
|
171
|
+
"""
|
|
172
|
+
Convert a gdstk.Cell to a device array.
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
gdstk_cell : gdstk.Cell
|
|
177
|
+
The gdstk.Cell object to be converted.
|
|
178
|
+
gds_layer : tuple[int, int]
|
|
179
|
+
The layer and datatype to be used from the cell. Defaults to (1, 0).
|
|
180
|
+
bounds : Optional[tuple[tuple[float, float], tuple[float, float]]]
|
|
181
|
+
Bounds for cropping the cell, formatted as ((min_x, min_y), (max_x, max_y)).
|
|
182
|
+
If None, the entire cell is used.
|
|
183
|
+
|
|
184
|
+
Returns
|
|
185
|
+
-------
|
|
186
|
+
np.ndarray
|
|
187
|
+
The resulting device array.
|
|
188
|
+
"""
|
|
189
|
+
polygons = gdstk_cell.get_polygons(layer=gds_layer[0], datatype=gds_layer[1])
|
|
190
|
+
if bounds:
|
|
191
|
+
polygons = gdstk.slice(
|
|
192
|
+
polygons, position=(bounds[0][0], bounds[1][0]), axis="x"
|
|
193
|
+
)[1]
|
|
194
|
+
polygons = gdstk.slice(
|
|
195
|
+
polygons, position=(bounds[0][1], bounds[1][1]), axis="y"
|
|
196
|
+
)[1]
|
|
197
|
+
bounds = (
|
|
198
|
+
(int(1000 * bounds[0][0]), int(1000 * bounds[0][1])),
|
|
199
|
+
(int(1000 * bounds[1][0]), int(1000 * bounds[1][1])),
|
|
200
|
+
)
|
|
201
|
+
else:
|
|
202
|
+
bbox = gdstk_cell.bounding_box()
|
|
203
|
+
if bbox is None:
|
|
204
|
+
raise ValueError("Cell has no geometry, cannot determine bounds.")
|
|
205
|
+
bounds = (
|
|
206
|
+
(float(1000 * bbox[0][0]), float(1000 * bbox[0][1])),
|
|
207
|
+
(float(1000 * bbox[1][0]), float(1000 * bbox[1][1])),
|
|
208
|
+
)
|
|
209
|
+
contours = [
|
|
210
|
+
np.array(
|
|
211
|
+
[
|
|
212
|
+
[
|
|
213
|
+
[
|
|
214
|
+
int(1000 * vertex[0] - bounds[0][0]),
|
|
215
|
+
int(1000 * vertex[1] - bounds[0][1]),
|
|
216
|
+
]
|
|
217
|
+
]
|
|
218
|
+
for vertex in polygon.points
|
|
219
|
+
],
|
|
220
|
+
)
|
|
221
|
+
for polygon in polygons
|
|
222
|
+
]
|
|
223
|
+
device_array = np.zeros(
|
|
224
|
+
(int(bounds[1][1] - bounds[0][1]), int(bounds[1][0] - bounds[0][0])),
|
|
225
|
+
dtype=np.uint8,
|
|
226
|
+
)
|
|
227
|
+
cv2.fillPoly(img=device_array, pts=contours, color=(1, 1, 1))
|
|
228
|
+
device_array = np.flipud(device_array)
|
|
229
|
+
return device_array
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def from_gdsfactory(
|
|
233
|
+
component: "gf.Component",
|
|
234
|
+
**kwargs,
|
|
235
|
+
) -> Device:
|
|
236
|
+
"""
|
|
237
|
+
Create a Device from a gdsfactory component.
|
|
238
|
+
|
|
239
|
+
Parameters
|
|
240
|
+
----------
|
|
241
|
+
component : gf.Component
|
|
242
|
+
The gdsfactory component to be converted into a Device object.
|
|
243
|
+
**kwargs
|
|
244
|
+
Additional keyword arguments to be passed to the Device constructor.
|
|
245
|
+
|
|
246
|
+
Returns
|
|
247
|
+
-------
|
|
248
|
+
Device
|
|
249
|
+
A Device object representing the gdsfactory component.
|
|
250
|
+
|
|
251
|
+
Raises
|
|
252
|
+
------
|
|
253
|
+
ImportError
|
|
254
|
+
If the gdsfactory package is not installed.
|
|
255
|
+
"""
|
|
256
|
+
try:
|
|
257
|
+
import gdsfactory as gf # noqa: F401
|
|
258
|
+
except ImportError:
|
|
259
|
+
raise ImportError(
|
|
260
|
+
"The gdsfactory package is required to use this function; "
|
|
261
|
+
"try `pip install gdsfactory`."
|
|
262
|
+
) from None
|
|
263
|
+
|
|
264
|
+
bounds = (
|
|
265
|
+
(component.xmin * 1000, component.ymin * 1000),
|
|
266
|
+
(component.xmax * 1000, component.ymax * 1000),
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
polygons = [
|
|
270
|
+
polygon
|
|
271
|
+
for polygons_list in component.get_polygons_points().values()
|
|
272
|
+
for polygon in polygons_list
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
contours = [
|
|
276
|
+
np.array(
|
|
277
|
+
[
|
|
278
|
+
[[int(1000 * x - bounds[0][0]), int(1000 * y - bounds[0][1])]]
|
|
279
|
+
for x, y in polygon # type: ignore
|
|
280
|
+
]
|
|
281
|
+
)
|
|
282
|
+
for polygon in polygons
|
|
283
|
+
]
|
|
284
|
+
|
|
285
|
+
device_array = np.zeros(
|
|
286
|
+
(int(bounds[1][1] - bounds[0][1]), int(bounds[1][0] - bounds[0][0])),
|
|
287
|
+
dtype=np.uint8,
|
|
288
|
+
)
|
|
289
|
+
cv2.fillPoly(img=device_array, pts=contours, color=(1, 1, 1))
|
|
290
|
+
device_array = np.flipud(device_array)
|
|
291
|
+
return Device(device_array=device_array, **kwargs)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def from_sem(
|
|
295
|
+
sem_path: str,
|
|
296
|
+
sem_resolution: Optional[float] = None,
|
|
297
|
+
sem_resolution_key: Optional[str] = None,
|
|
298
|
+
binarize: bool = False,
|
|
299
|
+
bounds: Optional[tuple[tuple[int, int], tuple[int, int]]] = None,
|
|
300
|
+
**kwargs,
|
|
301
|
+
) -> Device:
|
|
302
|
+
"""
|
|
303
|
+
Create a Device from a scanning electron microscope (SEM) image file.
|
|
304
|
+
|
|
305
|
+
Parameters
|
|
306
|
+
----------
|
|
307
|
+
sem_path : str
|
|
308
|
+
The file path to the SEM image.
|
|
309
|
+
sem_resolution : Optional[float]
|
|
310
|
+
The resolution of the SEM image in nanometers per pixel. If not provided, it
|
|
311
|
+
will be extracted from the image metadata using the `sem_resolution_key`.
|
|
312
|
+
sem_resolution_key : Optional[str]
|
|
313
|
+
The key to look for in the SEM image metadata to extract the resolution.
|
|
314
|
+
Required if `sem_resolution` is not provided.
|
|
315
|
+
binarize : bool
|
|
316
|
+
If True, the SEM image will be binarized (converted to binary values) before
|
|
317
|
+
conversion to a Device object. This is needed for processing grayscale images
|
|
318
|
+
into binary masks. Defaults to False.
|
|
319
|
+
bounds : Optional[tuple[tuple[int, int], tuple[int, int]]]
|
|
320
|
+
A tuple specifying the bounds in nm for cropping the image before conversion,
|
|
321
|
+
formatted as ((min_x, min_y), (max_x, max_y)). If None, the entire image is
|
|
322
|
+
used.
|
|
323
|
+
**kwargs
|
|
324
|
+
Additional keyword arguments to be passed to the Device constructor.
|
|
325
|
+
|
|
326
|
+
Returns
|
|
327
|
+
-------
|
|
328
|
+
Device
|
|
329
|
+
A Device object representing the processed SEM image.
|
|
330
|
+
|
|
331
|
+
Raises
|
|
332
|
+
------
|
|
333
|
+
ValueError
|
|
334
|
+
If neither `sem_resolution` nor `sem_resolution_key` is provided.
|
|
335
|
+
"""
|
|
336
|
+
if sem_resolution is None and sem_resolution_key is not None:
|
|
337
|
+
sem_resolution = get_sem_resolution(sem_path, sem_resolution_key)
|
|
338
|
+
elif sem_resolution is None:
|
|
339
|
+
raise ValueError("Either sem_resolution or resolution_key must be provided.")
|
|
340
|
+
|
|
341
|
+
device_array = cv2.imread(sem_path, flags=cv2.IMREAD_GRAYSCALE)
|
|
342
|
+
device_array = cv2.resize(
|
|
343
|
+
device_array, dsize=(0, 0), fx=sem_resolution, fy=sem_resolution
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
if bounds is not None:
|
|
347
|
+
pad_left = max(0, -bounds[0][0])
|
|
348
|
+
pad_right = max(0, bounds[1][0] - device_array.shape[1])
|
|
349
|
+
pad_bottom = max(0, -bounds[0][1])
|
|
350
|
+
pad_top = max(0, bounds[1][1] - device_array.shape[0])
|
|
351
|
+
|
|
352
|
+
if pad_left or pad_right or pad_top or pad_bottom:
|
|
353
|
+
device_array = np.pad(
|
|
354
|
+
device_array,
|
|
355
|
+
((pad_top, pad_bottom), (pad_left, pad_right)),
|
|
356
|
+
mode="constant",
|
|
357
|
+
constant_values=0,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
start_x = max(0, bounds[0][0] + pad_left)
|
|
361
|
+
end_x = min(device_array.shape[1], bounds[1][0] + pad_left)
|
|
362
|
+
start_y = max(0, device_array.shape[0] - (bounds[1][1] + pad_top))
|
|
363
|
+
end_y = min(
|
|
364
|
+
device_array.shape[0], device_array.shape[0] - (bounds[0][1] + pad_top)
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
if start_x >= end_x or start_y >= end_y:
|
|
368
|
+
raise ValueError(
|
|
369
|
+
"Invalid bounds resulted in zero-size array: "
|
|
370
|
+
f"x=[{start_x}, {end_x}], "
|
|
371
|
+
f"y=[{start_y}, {end_y}]"
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
device_array = device_array[start_y:end_y, start_x:end_x]
|
|
375
|
+
|
|
376
|
+
if binarize:
|
|
377
|
+
device_array = geometry.binarize_sem(device_array)
|
|
378
|
+
|
|
379
|
+
buffer_spec = BufferSpec(
|
|
380
|
+
mode={
|
|
381
|
+
"top": "none",
|
|
382
|
+
"bottom": "none",
|
|
383
|
+
"left": "none",
|
|
384
|
+
"right": "none",
|
|
385
|
+
},
|
|
386
|
+
thickness={
|
|
387
|
+
"top": 0,
|
|
388
|
+
"bottom": 0,
|
|
389
|
+
"left": 0,
|
|
390
|
+
"right": 0,
|
|
391
|
+
},
|
|
392
|
+
)
|
|
393
|
+
return Device(device_array=device_array, buffer_spec=buffer_spec, **kwargs)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def get_sem_resolution(sem_path: str, sem_resolution_key: str) -> float:
|
|
397
|
+
"""
|
|
398
|
+
Extracts the resolution of a scanning electron microscope (SEM) image from its
|
|
399
|
+
metadata.
|
|
400
|
+
|
|
401
|
+
Notes
|
|
402
|
+
-----
|
|
403
|
+
This function is used internally and may not be useful for most users.
|
|
404
|
+
|
|
405
|
+
Parameters
|
|
406
|
+
----------
|
|
407
|
+
sem_path : str
|
|
408
|
+
The file path to the SEM image.
|
|
409
|
+
sem_resolution_key : str
|
|
410
|
+
The key to look for in the SEM image metadata to extract the resolution.
|
|
411
|
+
|
|
412
|
+
Returns
|
|
413
|
+
-------
|
|
414
|
+
float
|
|
415
|
+
The resolution of the SEM image in nanometers per pixel.
|
|
416
|
+
|
|
417
|
+
Raises
|
|
418
|
+
------
|
|
419
|
+
ValueError
|
|
420
|
+
If the resolution key is not found in the SEM image metadata.
|
|
421
|
+
"""
|
|
422
|
+
with open(sem_path, "rb") as file:
|
|
423
|
+
resolution_key_bytes = sem_resolution_key.encode("utf-8")
|
|
424
|
+
for line in file:
|
|
425
|
+
if resolution_key_bytes in line:
|
|
426
|
+
line_str = line.decode("utf-8")
|
|
427
|
+
match = re.search(r"-?\d+(\.\d+)?", line_str)
|
|
428
|
+
if match:
|
|
429
|
+
value = float(match.group())
|
|
430
|
+
if value > 100:
|
|
431
|
+
value /= 1000
|
|
432
|
+
return value
|
|
433
|
+
raise ValueError(f"Resolution key '{sem_resolution_key}' not found in {sem_path}.")
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def from_tidy3d(
|
|
437
|
+
tidy3d_sim: "td.Simulation",
|
|
438
|
+
eps: float,
|
|
439
|
+
z: float,
|
|
440
|
+
freq: float,
|
|
441
|
+
buffer_width: float = 0.1,
|
|
442
|
+
**kwargs,
|
|
443
|
+
) -> Device:
|
|
444
|
+
"""
|
|
445
|
+
Create a Device from a Tidy3D simulation.
|
|
446
|
+
|
|
447
|
+
Parameters
|
|
448
|
+
----------
|
|
449
|
+
tidy3d_sim : tidy3d.Simulation
|
|
450
|
+
The Tidy3D simulation object.
|
|
451
|
+
eps : float
|
|
452
|
+
The permittivity of the layer to extract from the simulation.
|
|
453
|
+
z : float
|
|
454
|
+
The z-coordinate of the layer to extract from the simulation.
|
|
455
|
+
freq : float
|
|
456
|
+
The frequency at which to extract the permittivity.
|
|
457
|
+
buffer_width : float
|
|
458
|
+
The width of the buffer region around the layer to extract from the
|
|
459
|
+
simulation. Defaults to 0.1 µm. This is useful for ensuring the inputs/outputs
|
|
460
|
+
of the simulation are not affected by prediction.
|
|
461
|
+
**kwargs
|
|
462
|
+
Additional keyword arguments to be passed to the Device constructor.
|
|
463
|
+
|
|
464
|
+
Returns
|
|
465
|
+
-------
|
|
466
|
+
Device
|
|
467
|
+
A Device object representing the permittivity cross-section at the specified
|
|
468
|
+
z-coordinate for the Tidy3D simulation.
|
|
469
|
+
|
|
470
|
+
Raises
|
|
471
|
+
------
|
|
472
|
+
ImportError
|
|
473
|
+
If the tidy3d package is not installed.
|
|
474
|
+
"""
|
|
475
|
+
try:
|
|
476
|
+
from tidy3d import Coords, Grid
|
|
477
|
+
except ImportError:
|
|
478
|
+
raise ImportError(
|
|
479
|
+
"The tidy3d package is required to use this function; "
|
|
480
|
+
"try `pip install tidy3d`."
|
|
481
|
+
) from None
|
|
482
|
+
|
|
483
|
+
X = np.arange(
|
|
484
|
+
tidy3d_sim.bounds[0][0] - buffer_width,
|
|
485
|
+
tidy3d_sim.bounds[1][0] + buffer_width,
|
|
486
|
+
0.001,
|
|
487
|
+
)
|
|
488
|
+
Y = np.arange(
|
|
489
|
+
tidy3d_sim.bounds[0][1] - buffer_width,
|
|
490
|
+
tidy3d_sim.bounds[1][1] + buffer_width,
|
|
491
|
+
0.001,
|
|
492
|
+
)
|
|
493
|
+
Z = np.array([z])
|
|
494
|
+
|
|
495
|
+
grid = Grid(attrs={}, boundaries=Coords(attrs={}, x=X, y=Y, z=Z))
|
|
496
|
+
eps_array = np.real(
|
|
497
|
+
tidy3d_sim.epsilon_on_grid(grid=grid, coord_key="boundaries", freq=freq).values
|
|
498
|
+
)
|
|
499
|
+
device_array = geometry.binarize_hard(device_array=eps_array, eta=eps - 0.1)[
|
|
500
|
+
:, :, 0
|
|
501
|
+
]
|
|
502
|
+
device_array = np.rot90(device_array, k=1)
|
|
503
|
+
return Device(device_array=device_array, **kwargs)
|