prefab 1.1.4__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 +18 -3
- prefab/device.py +223 -133
- prefab/geometry.py +66 -22
- prefab/predict.py +51 -21
- prefab/read.py +122 -76
- prefab/shapes.py +82 -81
- {prefab-1.1.4.dist-info → prefab-1.1.6.dist-info}/METADATA +4 -2
- prefab-1.1.6.dist-info/RECORD +13 -0
- {prefab-1.1.4.dist-info → prefab-1.1.6.dist-info}/WHEEL +1 -1
- prefab-1.1.4.dist-info/RECORD +0 -13
- {prefab-1.1.4.dist-info → prefab-1.1.6.dist-info}/licenses/LICENSE +0 -0
prefab/read.py
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Functions to create a Device from various data sources."""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
5
|
|
|
5
6
|
import cv2
|
|
6
7
|
import gdstk
|
|
7
8
|
import numpy as np
|
|
8
9
|
|
|
9
10
|
from . import geometry
|
|
10
|
-
from .device import Device
|
|
11
|
+
from .device import BufferSpec, Device
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
import gdsfactory as gf
|
|
15
|
+
import tidy3d as td
|
|
11
16
|
|
|
12
17
|
|
|
13
18
|
def from_ndarray(
|
|
@@ -19,12 +24,12 @@ def from_ndarray(
|
|
|
19
24
|
Parameters
|
|
20
25
|
----------
|
|
21
26
|
ndarray : np.ndarray
|
|
22
|
-
The input array representing the device
|
|
23
|
-
resolution : float
|
|
27
|
+
The input array representing the device geometry.
|
|
28
|
+
resolution : float
|
|
24
29
|
The resolution of the ndarray in nanometers per pixel, defaulting to 1.0 nm per
|
|
25
30
|
pixel. If specified, the input array will be resized based on this resolution to
|
|
26
31
|
match the desired physical size.
|
|
27
|
-
binarize : bool
|
|
32
|
+
binarize : bool
|
|
28
33
|
If True, the input array will be binarized (converted to binary values) before
|
|
29
34
|
conversion to a Device object. This is useful for processing grayscale arrays
|
|
30
35
|
into binary masks. Defaults to True.
|
|
@@ -34,8 +39,7 @@ def from_ndarray(
|
|
|
34
39
|
Returns
|
|
35
40
|
-------
|
|
36
41
|
Device
|
|
37
|
-
A Device object representing the input array, after
|
|
38
|
-
binarization.
|
|
42
|
+
A Device object representing the input array, after resizing and binarization.
|
|
39
43
|
"""
|
|
40
44
|
device_array = ndarray
|
|
41
45
|
if resolution != 1.0:
|
|
@@ -48,7 +52,7 @@ def from_ndarray(
|
|
|
48
52
|
|
|
49
53
|
|
|
50
54
|
def from_img(
|
|
51
|
-
img_path: str, img_width_nm: int = None, binarize: bool = True, **kwargs
|
|
55
|
+
img_path: str, img_width_nm: Optional[int] = None, binarize: bool = True, **kwargs
|
|
52
56
|
) -> Device:
|
|
53
57
|
"""
|
|
54
58
|
Create a Device from an image file.
|
|
@@ -57,10 +61,10 @@ def from_img(
|
|
|
57
61
|
----------
|
|
58
62
|
img_path : str
|
|
59
63
|
The path to the image file to be converted into a Device object.
|
|
60
|
-
img_width_nm : int
|
|
64
|
+
img_width_nm : Optional[int]
|
|
61
65
|
The width of the image in nanometers. If specified, the Device will be resized
|
|
62
66
|
to this width while maintaining aspect ratio. If None, no resizing is performed.
|
|
63
|
-
binarize : bool
|
|
67
|
+
binarize : bool
|
|
64
68
|
If True, the image will be binarized (converted to binary values) before
|
|
65
69
|
conversion to a Device object. This is useful for processing grayscale images
|
|
66
70
|
into binary masks. Defaults to True.
|
|
@@ -88,9 +92,9 @@ def from_gds(
|
|
|
88
92
|
gds_path: str,
|
|
89
93
|
cell_name: str,
|
|
90
94
|
gds_layer: tuple[int, int] = (1, 0),
|
|
91
|
-
bounds: tuple[tuple[
|
|
95
|
+
bounds: Optional[tuple[tuple[float, float], tuple[float, float]]] = None,
|
|
92
96
|
**kwargs,
|
|
93
|
-
):
|
|
97
|
+
) -> Device:
|
|
94
98
|
"""
|
|
95
99
|
Create a Device from a GDS cell.
|
|
96
100
|
|
|
@@ -100,10 +104,10 @@ def from_gds(
|
|
|
100
104
|
The file path to the GDS file.
|
|
101
105
|
cell_name : str
|
|
102
106
|
The name of the cell within the GDS file to be converted into a Device object.
|
|
103
|
-
gds_layer : tuple[int, int]
|
|
107
|
+
gds_layer : tuple[int, int]
|
|
104
108
|
A tuple specifying the layer and datatype to be used from the GDS file. Defaults
|
|
105
109
|
to (1, 0).
|
|
106
|
-
bounds : tuple[tuple[
|
|
110
|
+
bounds : Optional[tuple[tuple[float, float], tuple[float, float]]]
|
|
107
111
|
A tuple specifying the bounds for cropping the cell before conversion, formatted
|
|
108
112
|
as ((min_x, min_y), (max_x, max_y)), in units of the GDS file. If None, the
|
|
109
113
|
entire cell is used.
|
|
@@ -117,7 +121,7 @@ def from_gds(
|
|
|
117
121
|
processing based on the specified layer.
|
|
118
122
|
"""
|
|
119
123
|
gdstk_library = gdstk.read_gds(gds_path)
|
|
120
|
-
gdstk_cell = gdstk_library[cell_name]
|
|
124
|
+
gdstk_cell = gdstk_library[cell_name] # type: ignore
|
|
121
125
|
device_array = _gdstk_to_device_array(
|
|
122
126
|
gdstk_cell=gdstk_cell, gds_layer=gds_layer, bounds=bounds
|
|
123
127
|
)
|
|
@@ -127,7 +131,7 @@ def from_gds(
|
|
|
127
131
|
def from_gdstk(
|
|
128
132
|
gdstk_cell: gdstk.Cell,
|
|
129
133
|
gds_layer: tuple[int, int] = (1, 0),
|
|
130
|
-
bounds: tuple[tuple[
|
|
134
|
+
bounds: Optional[tuple[tuple[float, float], tuple[float, float]]] = None,
|
|
131
135
|
**kwargs,
|
|
132
136
|
):
|
|
133
137
|
"""
|
|
@@ -137,12 +141,12 @@ def from_gdstk(
|
|
|
137
141
|
----------
|
|
138
142
|
gdstk_cell : gdstk.Cell
|
|
139
143
|
The gdstk.Cell object to be converted into a Device object.
|
|
140
|
-
gds_layer : tuple[int, int]
|
|
144
|
+
gds_layer : tuple[int, int]
|
|
141
145
|
A tuple specifying the layer and datatype to be used from the cell. Defaults to
|
|
142
146
|
(1, 0).
|
|
143
|
-
bounds : tuple[tuple[
|
|
147
|
+
bounds : tuple[tuple[float, float], tuple[float, float]]
|
|
144
148
|
A tuple specifying the bounds for cropping the cell before conversion, formatted
|
|
145
|
-
as ((min_x, min_y), (max_x, max_y)), in units of the GDS
|
|
149
|
+
as ((min_x, min_y), (max_x, max_y)), in units of the GDS cell. If None, the
|
|
146
150
|
entire cell is used.
|
|
147
151
|
**kwargs
|
|
148
152
|
Additional keyword arguments to be passed to the Device constructor.
|
|
@@ -162,7 +166,7 @@ def from_gdstk(
|
|
|
162
166
|
def _gdstk_to_device_array(
|
|
163
167
|
gdstk_cell: gdstk.Cell,
|
|
164
168
|
gds_layer: tuple[int, int] = (1, 0),
|
|
165
|
-
bounds: tuple[tuple[
|
|
169
|
+
bounds: Optional[tuple[tuple[float, float], tuple[float, float]]] = None,
|
|
166
170
|
) -> np.ndarray:
|
|
167
171
|
"""
|
|
168
172
|
Convert a gdstk.Cell to a device array.
|
|
@@ -171,9 +175,9 @@ def _gdstk_to_device_array(
|
|
|
171
175
|
----------
|
|
172
176
|
gdstk_cell : gdstk.Cell
|
|
173
177
|
The gdstk.Cell object to be converted.
|
|
174
|
-
gds_layer : tuple[int, int]
|
|
178
|
+
gds_layer : tuple[int, int]
|
|
175
179
|
The layer and datatype to be used from the cell. Defaults to (1, 0).
|
|
176
|
-
bounds : tuple[tuple[
|
|
180
|
+
bounds : Optional[tuple[tuple[float, float], tuple[float, float]]]
|
|
177
181
|
Bounds for cropping the cell, formatted as ((min_x, min_y), (max_x, max_y)).
|
|
178
182
|
If None, the entire cell is used.
|
|
179
183
|
|
|
@@ -190,11 +194,17 @@ def _gdstk_to_device_array(
|
|
|
190
194
|
polygons = gdstk.slice(
|
|
191
195
|
polygons, position=(bounds[0][1], bounds[1][1]), axis="y"
|
|
192
196
|
)[1]
|
|
193
|
-
bounds =
|
|
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
|
+
)
|
|
194
201
|
else:
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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])),
|
|
198
208
|
)
|
|
199
209
|
contours = [
|
|
200
210
|
np.array(
|
|
@@ -220,7 +230,7 @@ def _gdstk_to_device_array(
|
|
|
220
230
|
|
|
221
231
|
|
|
222
232
|
def from_gdsfactory(
|
|
223
|
-
component: "gf.Component",
|
|
233
|
+
component: "gf.Component",
|
|
224
234
|
**kwargs,
|
|
225
235
|
) -> Device:
|
|
226
236
|
"""
|
|
@@ -265,13 +275,8 @@ def from_gdsfactory(
|
|
|
265
275
|
contours = [
|
|
266
276
|
np.array(
|
|
267
277
|
[
|
|
268
|
-
[
|
|
269
|
-
|
|
270
|
-
int(1000 * vertex[0] - bounds[0][0]),
|
|
271
|
-
int(1000 * vertex[1] - bounds[0][1]),
|
|
272
|
-
]
|
|
273
|
-
]
|
|
274
|
-
for vertex in polygon
|
|
278
|
+
[[int(1000 * x - bounds[0][0]), int(1000 * y - bounds[0][1])]]
|
|
279
|
+
for x, y in polygon # type: ignore
|
|
275
280
|
]
|
|
276
281
|
)
|
|
277
282
|
for polygon in polygons
|
|
@@ -288,10 +293,10 @@ def from_gdsfactory(
|
|
|
288
293
|
|
|
289
294
|
def from_sem(
|
|
290
295
|
sem_path: str,
|
|
291
|
-
sem_resolution: float = None,
|
|
292
|
-
sem_resolution_key: str = None,
|
|
296
|
+
sem_resolution: Optional[float] = None,
|
|
297
|
+
sem_resolution_key: Optional[str] = None,
|
|
293
298
|
binarize: bool = False,
|
|
294
|
-
bounds: tuple[tuple[int, int], tuple[int, int]] = None,
|
|
299
|
+
bounds: Optional[tuple[tuple[int, int], tuple[int, int]]] = None,
|
|
295
300
|
**kwargs,
|
|
296
301
|
) -> Device:
|
|
297
302
|
"""
|
|
@@ -301,18 +306,18 @@ def from_sem(
|
|
|
301
306
|
----------
|
|
302
307
|
sem_path : str
|
|
303
308
|
The file path to the SEM image.
|
|
304
|
-
sem_resolution : float
|
|
309
|
+
sem_resolution : Optional[float]
|
|
305
310
|
The resolution of the SEM image in nanometers per pixel. If not provided, it
|
|
306
311
|
will be extracted from the image metadata using the `sem_resolution_key`.
|
|
307
|
-
sem_resolution_key : str
|
|
312
|
+
sem_resolution_key : Optional[str]
|
|
308
313
|
The key to look for in the SEM image metadata to extract the resolution.
|
|
309
314
|
Required if `sem_resolution` is not provided.
|
|
310
|
-
binarize : bool
|
|
315
|
+
binarize : bool
|
|
311
316
|
If True, the SEM image will be binarized (converted to binary values) before
|
|
312
317
|
conversion to a Device object. This is needed for processing grayscale images
|
|
313
318
|
into binary masks. Defaults to False.
|
|
314
|
-
bounds : tuple[tuple[int, int], tuple[int, int]]
|
|
315
|
-
A tuple specifying the bounds for cropping the image before conversion,
|
|
319
|
+
bounds : Optional[tuple[tuple[int, int], tuple[int, int]]]
|
|
320
|
+
A tuple specifying the bounds in nm for cropping the image before conversion,
|
|
316
321
|
formatted as ((min_x, min_y), (max_x, max_y)). If None, the entire image is
|
|
317
322
|
used.
|
|
318
323
|
**kwargs
|
|
@@ -337,14 +342,55 @@ def from_sem(
|
|
|
337
342
|
device_array = cv2.resize(
|
|
338
343
|
device_array, dsize=(0, 0), fx=sem_resolution, fy=sem_resolution
|
|
339
344
|
)
|
|
345
|
+
|
|
340
346
|
if bounds is not None:
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
]
|
|
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
|
+
|
|
345
376
|
if binarize:
|
|
346
377
|
device_array = geometry.binarize_sem(device_array)
|
|
347
|
-
|
|
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)
|
|
348
394
|
|
|
349
395
|
|
|
350
396
|
def get_sem_resolution(sem_path: str, sem_resolution_key: str) -> float:
|
|
@@ -352,7 +398,7 @@ def get_sem_resolution(sem_path: str, sem_resolution_key: str) -> float:
|
|
|
352
398
|
Extracts the resolution of a scanning electron microscope (SEM) image from its
|
|
353
399
|
metadata.
|
|
354
400
|
|
|
355
|
-
|
|
401
|
+
Notes
|
|
356
402
|
-----
|
|
357
403
|
This function is used internally and may not be useful for most users.
|
|
358
404
|
|
|
@@ -388,9 +434,11 @@ def get_sem_resolution(sem_path: str, sem_resolution_key: str) -> float:
|
|
|
388
434
|
|
|
389
435
|
|
|
390
436
|
def from_tidy3d(
|
|
391
|
-
tidy3d_sim: "
|
|
392
|
-
|
|
437
|
+
tidy3d_sim: "td.Simulation",
|
|
438
|
+
eps: float,
|
|
393
439
|
z: float,
|
|
440
|
+
freq: float,
|
|
441
|
+
buffer_width: float = 0.1,
|
|
394
442
|
**kwargs,
|
|
395
443
|
) -> Device:
|
|
396
444
|
"""
|
|
@@ -400,10 +448,16 @@ def from_tidy3d(
|
|
|
400
448
|
----------
|
|
401
449
|
tidy3d_sim : tidy3d.Simulation
|
|
402
450
|
The Tidy3D simulation object.
|
|
403
|
-
|
|
404
|
-
The
|
|
451
|
+
eps : float
|
|
452
|
+
The permittivity of the layer to extract from the simulation.
|
|
405
453
|
z : float
|
|
406
|
-
The z-coordinate
|
|
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.
|
|
407
461
|
**kwargs
|
|
408
462
|
Additional keyword arguments to be passed to the Device constructor.
|
|
409
463
|
|
|
@@ -415,9 +469,6 @@ def from_tidy3d(
|
|
|
415
469
|
|
|
416
470
|
Raises
|
|
417
471
|
------
|
|
418
|
-
ValueError
|
|
419
|
-
If the z-coordinate is outside the bounds of the simulation size in the
|
|
420
|
-
z-direction.
|
|
421
472
|
ImportError
|
|
422
473
|
If the tidy3d package is not installed.
|
|
423
474
|
"""
|
|
@@ -429,29 +480,24 @@ def from_tidy3d(
|
|
|
429
480
|
"try `pip install tidy3d`."
|
|
430
481
|
) from None
|
|
431
482
|
|
|
432
|
-
|
|
433
|
-
tidy3d_sim.
|
|
434
|
-
|
|
435
|
-
<= tidy3d_sim.center[2] + tidy3d_sim.size[2] / 2
|
|
436
|
-
):
|
|
437
|
-
raise ValueError(
|
|
438
|
-
f"z={z} is outside the bounds of the simulation size in the z-direction."
|
|
439
|
-
)
|
|
440
|
-
|
|
441
|
-
x = np.arange(
|
|
442
|
-
tidy3d_sim.center[0] - tidy3d_sim.size[0] / 2,
|
|
443
|
-
tidy3d_sim.center[0] + tidy3d_sim.size[0] / 2,
|
|
483
|
+
X = np.arange(
|
|
484
|
+
tidy3d_sim.bounds[0][0] - buffer_width,
|
|
485
|
+
tidy3d_sim.bounds[1][0] + buffer_width,
|
|
444
486
|
0.001,
|
|
445
487
|
)
|
|
446
|
-
|
|
447
|
-
tidy3d_sim.
|
|
448
|
-
tidy3d_sim.
|
|
488
|
+
Y = np.arange(
|
|
489
|
+
tidy3d_sim.bounds[0][1] - buffer_width,
|
|
490
|
+
tidy3d_sim.bounds[1][1] + buffer_width,
|
|
449
491
|
0.001,
|
|
450
492
|
)
|
|
451
|
-
|
|
493
|
+
Z = np.array([z])
|
|
452
494
|
|
|
453
|
-
grid = Grid(boundaries=Coords(x=
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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)
|
|
457
503
|
return Device(device_array=device_array, **kwargs)
|