prefab 1.0.3__py3-none-any.whl → 1.1.0__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/device.py +338 -70
- prefab/read.py +137 -0
- {prefab-1.0.3.dist-info → prefab-1.1.0.dist-info}/METADATA +2 -1
- prefab-1.1.0.dist-info/RECORD +12 -0
- prefab-1.0.3.dist-info/RECORD +0 -12
- {prefab-1.0.3.dist-info → prefab-1.1.0.dist-info}/WHEEL +0 -0
- {prefab-1.0.3.dist-info → prefab-1.1.0.dist-info}/licenses/LICENSE +0 -0
prefab/__init__.py
CHANGED
prefab/device.py
CHANGED
|
@@ -16,9 +16,12 @@ from matplotlib.axes import Axes
|
|
|
16
16
|
from matplotlib.patches import Rectangle
|
|
17
17
|
from PIL import Image
|
|
18
18
|
from pydantic import BaseModel, Field, conint, root_validator, validator
|
|
19
|
+
from scipy.ndimage import distance_transform_edt
|
|
20
|
+
from skimage import measure
|
|
21
|
+
from skimage.morphology import closing, disk, opening, square
|
|
19
22
|
from tqdm import tqdm
|
|
20
23
|
|
|
21
|
-
from . import geometry
|
|
24
|
+
from . import compare, geometry
|
|
22
25
|
from .models import Model
|
|
23
26
|
|
|
24
27
|
Image.MAX_IMAGE_PIXELS = None
|
|
@@ -224,6 +227,7 @@ class Device(BaseModel):
|
|
|
224
227
|
model: Model,
|
|
225
228
|
model_type: str,
|
|
226
229
|
binarize: bool,
|
|
230
|
+
gpu: bool = False,
|
|
227
231
|
) -> "Device":
|
|
228
232
|
try:
|
|
229
233
|
with open(os.path.expanduser("~/.prefab.toml")) as file:
|
|
@@ -245,6 +249,7 @@ class Device(BaseModel):
|
|
|
245
249
|
"Signup/login and generate a new token.\n"
|
|
246
250
|
"See https://www.prefabphotonics.com/docs/guides/quickstart."
|
|
247
251
|
) from None
|
|
252
|
+
|
|
248
253
|
headers = {
|
|
249
254
|
"Authorization": f"Bearer {access_token}",
|
|
250
255
|
"X-Refresh-Token": refresh_token,
|
|
@@ -258,84 +263,99 @@ class Device(BaseModel):
|
|
|
258
263
|
}
|
|
259
264
|
json_data = json.dumps(predict_data)
|
|
260
265
|
|
|
261
|
-
endpoint_url =
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
)
|
|
266
|
-
response.raise_for_status()
|
|
267
|
-
event_type = None
|
|
268
|
-
model_descriptions = {"p": "Prediction", "c": "Correction", "s": "SEMulate"}
|
|
269
|
-
progress_bar = tqdm(
|
|
270
|
-
total=100,
|
|
271
|
-
desc=f"{model_descriptions[model_type]}",
|
|
272
|
-
unit="%",
|
|
273
|
-
colour="green",
|
|
274
|
-
bar_format="{l_bar}{bar:30}{r_bar}{bar:-10b}",
|
|
275
|
-
)
|
|
266
|
+
endpoint_url = (
|
|
267
|
+
"https://prefab-photonics--predict-gpu-v1.modal.run"
|
|
268
|
+
if gpu
|
|
269
|
+
else "https://prefab-photonics--predict-v1.modal.run"
|
|
270
|
+
)
|
|
276
271
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
results.append(decoded_image)
|
|
272
|
+
try:
|
|
273
|
+
with requests.post(
|
|
274
|
+
endpoint_url, data=json_data, headers=headers, stream=True
|
|
275
|
+
) as response:
|
|
276
|
+
response.raise_for_status()
|
|
277
|
+
event_type = None
|
|
278
|
+
model_descriptions = {
|
|
279
|
+
"p": "Prediction",
|
|
280
|
+
"c": "Correction",
|
|
281
|
+
"s": "SEMulate",
|
|
282
|
+
}
|
|
283
|
+
progress_bar = tqdm(
|
|
284
|
+
total=100,
|
|
285
|
+
desc=f"{model_descriptions[model_type]}",
|
|
286
|
+
unit="%",
|
|
287
|
+
colour="green",
|
|
288
|
+
bar_format="{l_bar}{bar:30}{r_bar}{bar:-10b}",
|
|
289
|
+
)
|
|
296
290
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
291
|
+
for line in response.iter_lines():
|
|
292
|
+
if line:
|
|
293
|
+
decoded_line = line.decode("utf-8").strip()
|
|
294
|
+
if decoded_line.startswith("event:"):
|
|
295
|
+
event_type = decoded_line.split(":")[1].strip()
|
|
296
|
+
elif decoded_line.startswith("data:"):
|
|
297
|
+
try:
|
|
298
|
+
data_content = json.loads(
|
|
299
|
+
decoded_line.split("data: ")[1]
|
|
300
|
+
)
|
|
301
|
+
if event_type == "progress":
|
|
302
|
+
progress = round(100 * data_content["progress"])
|
|
303
|
+
progress_bar.update(progress - progress_bar.n)
|
|
304
|
+
elif event_type == "result":
|
|
305
|
+
results = []
|
|
306
|
+
for key in sorted(data_content.keys()):
|
|
307
|
+
if key.startswith("result"):
|
|
308
|
+
decoded_image = self._decode_array(
|
|
309
|
+
data_content[key]
|
|
310
|
+
)
|
|
311
|
+
results.append(decoded_image)
|
|
312
|
+
|
|
313
|
+
if results:
|
|
314
|
+
prediction = np.stack(results, axis=-1)
|
|
315
|
+
if binarize:
|
|
316
|
+
prediction = geometry.binarize_hard(
|
|
317
|
+
prediction
|
|
318
|
+
)
|
|
319
|
+
progress_bar.close()
|
|
320
|
+
return prediction
|
|
321
|
+
elif event_type == "end":
|
|
322
|
+
print("Stream ended.")
|
|
301
323
|
progress_bar.close()
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
elif event_type == "auth":
|
|
308
|
-
if "new_refresh_token" in data_content["auth"]:
|
|
309
|
-
prefab_file_path = os.path.expanduser(
|
|
310
|
-
"~/.prefab.toml"
|
|
311
|
-
)
|
|
312
|
-
with open(
|
|
313
|
-
prefab_file_path, "w", encoding="utf-8"
|
|
314
|
-
) as toml_file:
|
|
315
|
-
toml.dump(
|
|
316
|
-
{
|
|
317
|
-
"access_token": data_content["auth"][
|
|
318
|
-
"new_access_token"
|
|
319
|
-
],
|
|
320
|
-
"refresh_token": data_content["auth"][
|
|
321
|
-
"new_refresh_token"
|
|
322
|
-
],
|
|
323
|
-
},
|
|
324
|
-
toml_file,
|
|
324
|
+
break
|
|
325
|
+
elif event_type == "auth":
|
|
326
|
+
if "new_refresh_token" in data_content["auth"]:
|
|
327
|
+
prefab_file_path = os.path.expanduser(
|
|
328
|
+
"~/.prefab.toml"
|
|
325
329
|
)
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
330
|
+
with open(
|
|
331
|
+
prefab_file_path, "w", encoding="utf-8"
|
|
332
|
+
) as toml_file:
|
|
333
|
+
toml.dump(
|
|
334
|
+
{
|
|
335
|
+
"access_token": data_content[
|
|
336
|
+
"auth"
|
|
337
|
+
]["new_access_token"],
|
|
338
|
+
"refresh_token": data_content[
|
|
339
|
+
"auth"
|
|
340
|
+
]["new_refresh_token"],
|
|
341
|
+
},
|
|
342
|
+
toml_file,
|
|
343
|
+
)
|
|
344
|
+
elif event_type == "error":
|
|
345
|
+
raise ValueError(f"{data_content['error']}")
|
|
346
|
+
except json.JSONDecodeError:
|
|
347
|
+
raise ValueError(
|
|
348
|
+
"Failed to decode JSON:",
|
|
349
|
+
decoded_line.split("data: ")[1],
|
|
350
|
+
) from None
|
|
351
|
+
except requests.RequestException as e:
|
|
352
|
+
raise RuntimeError(f"Request failed: {e}") from e
|
|
334
353
|
|
|
335
354
|
def predict(
|
|
336
355
|
self,
|
|
337
356
|
model: Model,
|
|
338
357
|
binarize: bool = False,
|
|
358
|
+
gpu: bool = False,
|
|
339
359
|
) -> "Device":
|
|
340
360
|
"""
|
|
341
361
|
Predict the nanofabrication outcome of the device using a specified model.
|
|
@@ -357,6 +377,10 @@ class Device(BaseModel):
|
|
|
357
377
|
If True, the predicted device geometry will be binarized using a threshold
|
|
358
378
|
method. This is useful for converting probabilistic predictions into binary
|
|
359
379
|
geometries. Defaults to False.
|
|
380
|
+
gpu : bool, optional
|
|
381
|
+
If True, the prediction will be performed on a GPU. Defaults to False.
|
|
382
|
+
Note: The GPU option has more overhead and will take longer for small
|
|
383
|
+
devices, but will be faster for larger devices.
|
|
360
384
|
|
|
361
385
|
Returns
|
|
362
386
|
-------
|
|
@@ -373,6 +397,7 @@ class Device(BaseModel):
|
|
|
373
397
|
model=model,
|
|
374
398
|
model_type="p",
|
|
375
399
|
binarize=binarize,
|
|
400
|
+
gpu=gpu,
|
|
376
401
|
)
|
|
377
402
|
return self.model_copy(update={"device_array": prediction_array})
|
|
378
403
|
|
|
@@ -380,6 +405,7 @@ class Device(BaseModel):
|
|
|
380
405
|
self,
|
|
381
406
|
model: Model,
|
|
382
407
|
binarize: bool = True,
|
|
408
|
+
gpu: bool = False,
|
|
383
409
|
) -> "Device":
|
|
384
410
|
"""
|
|
385
411
|
Correct the nanofabrication outcome of the device using a specified model.
|
|
@@ -403,6 +429,10 @@ class Device(BaseModel):
|
|
|
403
429
|
If True, the corrected device geometry will be binarized using a threshold
|
|
404
430
|
method. This is useful for converting probabilistic corrections into binary
|
|
405
431
|
geometries. Defaults to True.
|
|
432
|
+
gpu : bool, optional
|
|
433
|
+
If True, the prediction will be performed on a GPU. Defaults to False.
|
|
434
|
+
Note: The GPU option has more overhead and will take longer for small
|
|
435
|
+
devices, but will be faster for larger devices.
|
|
406
436
|
|
|
407
437
|
Returns
|
|
408
438
|
-------
|
|
@@ -419,12 +449,14 @@ class Device(BaseModel):
|
|
|
419
449
|
model=model,
|
|
420
450
|
model_type="c",
|
|
421
451
|
binarize=binarize,
|
|
452
|
+
gpu=gpu,
|
|
422
453
|
)
|
|
423
454
|
return self.model_copy(update={"device_array": correction_array})
|
|
424
455
|
|
|
425
456
|
def semulate(
|
|
426
457
|
self,
|
|
427
458
|
model: Model,
|
|
459
|
+
gpu: bool = False,
|
|
428
460
|
) -> "Device":
|
|
429
461
|
"""
|
|
430
462
|
Simulate the appearance of the device as if viewed under a scanning electron
|
|
@@ -444,6 +476,10 @@ class Device(BaseModel):
|
|
|
444
476
|
in `models.py`. Each model is associated with a version and dataset that
|
|
445
477
|
detail its creation and the data it was trained on, ensuring the SEMulation
|
|
446
478
|
is tailored to specific fabrication parameters.
|
|
479
|
+
gpu : bool, optional
|
|
480
|
+
If True, the prediction will be performed on a GPU. Defaults to False.
|
|
481
|
+
Note: The GPU option has more overhead and will take longer for small
|
|
482
|
+
devices, but will be faster for larger devices.
|
|
447
483
|
|
|
448
484
|
Returns
|
|
449
485
|
-------
|
|
@@ -455,6 +491,7 @@ class Device(BaseModel):
|
|
|
455
491
|
model=model,
|
|
456
492
|
model_type="s",
|
|
457
493
|
binarize=False,
|
|
494
|
+
gpu=gpu,
|
|
458
495
|
)
|
|
459
496
|
return self.model_copy(update={"device_array": semulated_array})
|
|
460
497
|
|
|
@@ -636,6 +673,149 @@ class Device(BaseModel):
|
|
|
636
673
|
|
|
637
674
|
return cell
|
|
638
675
|
|
|
676
|
+
def to_gdsfactory(self) -> "gf.Component": # noqa: F821
|
|
677
|
+
"""
|
|
678
|
+
Convert the device geometry to a gdsfactory Component.
|
|
679
|
+
|
|
680
|
+
Returns
|
|
681
|
+
-------
|
|
682
|
+
gf.Component
|
|
683
|
+
A gdsfactory Component object representing the device geometry.
|
|
684
|
+
|
|
685
|
+
Raises
|
|
686
|
+
------
|
|
687
|
+
ImportError
|
|
688
|
+
If the gdsfactory package is not installed.
|
|
689
|
+
"""
|
|
690
|
+
try:
|
|
691
|
+
import gdsfactory as gf
|
|
692
|
+
except ImportError:
|
|
693
|
+
raise ImportError(
|
|
694
|
+
"The gdsfactory package is required to use this function; "
|
|
695
|
+
"try `pip install gdsfactory`."
|
|
696
|
+
) from None
|
|
697
|
+
|
|
698
|
+
device_array = np.rot90(self.device_array, k=-1)
|
|
699
|
+
return gf.read.from_np(device_array[:, :, 0], nm_per_pixel=1)
|
|
700
|
+
|
|
701
|
+
def to_tidy3d(
|
|
702
|
+
self,
|
|
703
|
+
eps0: float,
|
|
704
|
+
thickness: float,
|
|
705
|
+
) -> "td.Structure": # noqa: F821
|
|
706
|
+
"""
|
|
707
|
+
Convert the device geometry to a Tidy3D Structure.
|
|
708
|
+
|
|
709
|
+
Parameters
|
|
710
|
+
----------
|
|
711
|
+
eps0 : float
|
|
712
|
+
The permittivity value to assign to the device array.
|
|
713
|
+
thickness : float
|
|
714
|
+
The thickness of the device in the z-direction.
|
|
715
|
+
|
|
716
|
+
Returns
|
|
717
|
+
-------
|
|
718
|
+
td.Structure
|
|
719
|
+
A Tidy3D Structure object representing the device geometry.
|
|
720
|
+
|
|
721
|
+
Raises
|
|
722
|
+
------
|
|
723
|
+
ImportError
|
|
724
|
+
If the tidy3d package is not installed.
|
|
725
|
+
"""
|
|
726
|
+
try:
|
|
727
|
+
from tidy3d import Box, CustomMedium, SpatialDataArray, Structure, inf
|
|
728
|
+
except ImportError:
|
|
729
|
+
raise ImportError(
|
|
730
|
+
"The tidy3d package is required to use this function; "
|
|
731
|
+
"try `pip install tidy3d`."
|
|
732
|
+
) from None
|
|
733
|
+
|
|
734
|
+
X = np.linspace(-self.shape[1] / 2000, self.shape[1] / 2000, self.shape[1])
|
|
735
|
+
Y = np.linspace(-self.shape[0] / 2000, self.shape[0] / 2000, self.shape[0])
|
|
736
|
+
Z = np.array([0])
|
|
737
|
+
|
|
738
|
+
device_array = np.rot90(np.fliplr(self.device_array), k=1)
|
|
739
|
+
eps_array = np.where(device_array >= 1.0, eps0, device_array)
|
|
740
|
+
eps_array = np.where(eps_array < 1.0, 1.0, eps_array)
|
|
741
|
+
eps_dataset = SpatialDataArray(eps_array, coords=dict(x=X, y=Y, z=Z))
|
|
742
|
+
medium = CustomMedium.from_eps_raw(eps_dataset)
|
|
743
|
+
return Structure(
|
|
744
|
+
geometry=Box(center=(0, 0, 0), size=(inf, inf, thickness)), medium=medium
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
def to_3d(self, thickness_nm: int) -> np.ndarray:
|
|
748
|
+
"""
|
|
749
|
+
Convert the 2D device geometry into a 3D representation.
|
|
750
|
+
|
|
751
|
+
This method creates a 3D array by interpolating between the bottom and top
|
|
752
|
+
layers of the device geometry. The interpolation is linear.
|
|
753
|
+
|
|
754
|
+
Parameters
|
|
755
|
+
----------
|
|
756
|
+
thickness_nm : int
|
|
757
|
+
The thickness of the 3D representation in nanometers.
|
|
758
|
+
|
|
759
|
+
Returns
|
|
760
|
+
-------
|
|
761
|
+
np.ndarray
|
|
762
|
+
A 3D narray representing the device geometry with the specified thickness.
|
|
763
|
+
"""
|
|
764
|
+
bottom_layer = self.device_array[:, :, 0]
|
|
765
|
+
top_layer = self.device_array[:, :, -1]
|
|
766
|
+
dt_bottom = distance_transform_edt(bottom_layer) - distance_transform_edt(
|
|
767
|
+
1 - bottom_layer
|
|
768
|
+
)
|
|
769
|
+
dt_top = distance_transform_edt(top_layer) - distance_transform_edt(
|
|
770
|
+
1 - top_layer
|
|
771
|
+
)
|
|
772
|
+
weights = np.linspace(0, 1, thickness_nm)
|
|
773
|
+
layered_array = np.zeros(
|
|
774
|
+
(bottom_layer.shape[0], bottom_layer.shape[1], thickness_nm)
|
|
775
|
+
)
|
|
776
|
+
for i, w in enumerate(weights):
|
|
777
|
+
dt_interp = (1 - w) * dt_bottom + w * dt_top
|
|
778
|
+
layered_array[:, :, i] = dt_interp >= 0
|
|
779
|
+
return layered_array
|
|
780
|
+
|
|
781
|
+
def to_stl(self, thickness_nm: int, filename: str = "prefab_device.stl"):
|
|
782
|
+
"""
|
|
783
|
+
Export the device geometry as an STL file.
|
|
784
|
+
|
|
785
|
+
Parameters
|
|
786
|
+
----------
|
|
787
|
+
thickness_nm : int
|
|
788
|
+
The thickness of the 3D representation in nanometers.
|
|
789
|
+
filename : str, optional
|
|
790
|
+
The name of the STL file to save. Defaults to "prefab_device.stl".
|
|
791
|
+
|
|
792
|
+
Raises
|
|
793
|
+
------
|
|
794
|
+
ValueError
|
|
795
|
+
If the thickness is not a positive integer.
|
|
796
|
+
ImportError
|
|
797
|
+
If the numpy-stl package is not installed.
|
|
798
|
+
"""
|
|
799
|
+
try:
|
|
800
|
+
from stl import mesh
|
|
801
|
+
except ImportError:
|
|
802
|
+
raise ImportError(
|
|
803
|
+
"The stl package is required to use this function; "
|
|
804
|
+
"try `pip install numpy-stl`."
|
|
805
|
+
) from None
|
|
806
|
+
|
|
807
|
+
if thickness_nm <= 0:
|
|
808
|
+
raise ValueError("Thickness must be a positive integer.")
|
|
809
|
+
|
|
810
|
+
layered_array = self.to_3d(thickness_nm)
|
|
811
|
+
verts, faces, _, _ = measure.marching_cubes(layered_array, level=0.5)
|
|
812
|
+
cube = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype))
|
|
813
|
+
for i, f in enumerate(faces):
|
|
814
|
+
for j in range(3):
|
|
815
|
+
cube.vectors[i][j] = verts[f[j], :]
|
|
816
|
+
cube.save(filename)
|
|
817
|
+
print(f"Saved Device to '{filename}'")
|
|
818
|
+
|
|
639
819
|
def _plot_base(
|
|
640
820
|
self,
|
|
641
821
|
plot_array: np.ndarray,
|
|
@@ -1259,3 +1439,91 @@ class Device(BaseModel):
|
|
|
1259
1439
|
with higher values indicating greater uncertainty.
|
|
1260
1440
|
"""
|
|
1261
1441
|
return 1 - 2 * np.abs(0.5 - self.device_array)
|
|
1442
|
+
|
|
1443
|
+
def enforce_feature_size(
|
|
1444
|
+
self, min_feature_size: int, strel: str = "disk"
|
|
1445
|
+
) -> "Device":
|
|
1446
|
+
"""
|
|
1447
|
+
Enforce a minimum feature size on the device geometry.
|
|
1448
|
+
|
|
1449
|
+
This method applies morphological operations to ensure that all features in the
|
|
1450
|
+
device geometry are at least the specified minimum size. It uses either a disk
|
|
1451
|
+
or square structuring element for the operations.
|
|
1452
|
+
|
|
1453
|
+
Parameters
|
|
1454
|
+
----------
|
|
1455
|
+
min_feature_size : int
|
|
1456
|
+
The minimum feature size to enforce, in nanometers.
|
|
1457
|
+
strel : str, optional
|
|
1458
|
+
The type of structuring element to use. Can be either "disk" or "square".
|
|
1459
|
+
Defaults to "disk".
|
|
1460
|
+
|
|
1461
|
+
Returns
|
|
1462
|
+
-------
|
|
1463
|
+
Device
|
|
1464
|
+
A new instance of the Device with the modified geometry.
|
|
1465
|
+
|
|
1466
|
+
Raises
|
|
1467
|
+
------
|
|
1468
|
+
ValueError
|
|
1469
|
+
If an invalid structuring element type is specified.
|
|
1470
|
+
"""
|
|
1471
|
+
if strel == "disk":
|
|
1472
|
+
structuring_element = disk(radius=min_feature_size / 2)
|
|
1473
|
+
elif strel == "square":
|
|
1474
|
+
structuring_element = square(width=min_feature_size)
|
|
1475
|
+
else:
|
|
1476
|
+
raise ValueError(f"Invalid structuring element: {strel}")
|
|
1477
|
+
|
|
1478
|
+
modified_geometry = closing(self.device_array[:, :, 0], structuring_element)
|
|
1479
|
+
modified_geometry = opening(modified_geometry, structuring_element)
|
|
1480
|
+
modified_geometry = np.expand_dims(modified_geometry, axis=-1)
|
|
1481
|
+
return self.model_copy(update={"device_array": modified_geometry})
|
|
1482
|
+
|
|
1483
|
+
def check_feature_size(self, min_feature_size: int, strel: str = "disk"):
|
|
1484
|
+
"""
|
|
1485
|
+
Check and visualize the effect of enforcing a minimum feature size on the device
|
|
1486
|
+
geometry.
|
|
1487
|
+
|
|
1488
|
+
This method enforces a minimum feature size on the device geometry using the
|
|
1489
|
+
specified structuring element, compares the modified geometry with the original,
|
|
1490
|
+
and plots the differences. It also calculates and prints the Hamming distance
|
|
1491
|
+
between the original and modified geometries, providing a measure of the changes
|
|
1492
|
+
introduced by the feature size enforcement.
|
|
1493
|
+
|
|
1494
|
+
Parameters
|
|
1495
|
+
----------
|
|
1496
|
+
min_feature_size : int
|
|
1497
|
+
The minimum feature size to enforce, in nanometers.
|
|
1498
|
+
strel : str, optional
|
|
1499
|
+
The type of structuring element to use. Can be either "disk" or "square".
|
|
1500
|
+
Defaults to "disk".
|
|
1501
|
+
|
|
1502
|
+
Raises
|
|
1503
|
+
------
|
|
1504
|
+
ValueError
|
|
1505
|
+
If an invalid structuring element type is specified or if min_feature_size
|
|
1506
|
+
is not a positive integer.
|
|
1507
|
+
"""
|
|
1508
|
+
if min_feature_size <= 0:
|
|
1509
|
+
raise ValueError("min_feature_size must be a positive integer.")
|
|
1510
|
+
|
|
1511
|
+
enforced_device = self.enforce_feature_size(min_feature_size, strel)
|
|
1512
|
+
|
|
1513
|
+
difference = np.abs(
|
|
1514
|
+
enforced_device.device_array[:, :, 0] - self.device_array[:, :, 0]
|
|
1515
|
+
)
|
|
1516
|
+
_, ax = self._plot_base(
|
|
1517
|
+
plot_array=difference,
|
|
1518
|
+
show_buffer=False,
|
|
1519
|
+
ax=None,
|
|
1520
|
+
bounds=None,
|
|
1521
|
+
cmap="jet",
|
|
1522
|
+
)
|
|
1523
|
+
|
|
1524
|
+
hamming_distance = compare.hamming_distance(self, enforced_device)
|
|
1525
|
+
print(
|
|
1526
|
+
f"Feature size check with minimum size {min_feature_size} "
|
|
1527
|
+
f"using '{strel}' structuring element resulted in a Hamming "
|
|
1528
|
+
f"distance of: {hamming_distance}"
|
|
1529
|
+
)
|
prefab/read.py
CHANGED
|
@@ -219,6 +219,73 @@ def _gdstk_to_device_array(
|
|
|
219
219
|
return device_array
|
|
220
220
|
|
|
221
221
|
|
|
222
|
+
def from_gdsfactory(
|
|
223
|
+
component: "gf.Component", # noqa: F821
|
|
224
|
+
**kwargs,
|
|
225
|
+
) -> Device:
|
|
226
|
+
"""
|
|
227
|
+
Create a Device from a gdsfactory component.
|
|
228
|
+
|
|
229
|
+
Parameters
|
|
230
|
+
----------
|
|
231
|
+
component : gf.Component
|
|
232
|
+
The gdsfactory component to be converted into a Device object.
|
|
233
|
+
**kwargs
|
|
234
|
+
Additional keyword arguments to be passed to the Device constructor.
|
|
235
|
+
|
|
236
|
+
Returns
|
|
237
|
+
-------
|
|
238
|
+
Device
|
|
239
|
+
A Device object representing the gdsfactory component.
|
|
240
|
+
|
|
241
|
+
Raises
|
|
242
|
+
------
|
|
243
|
+
ImportError
|
|
244
|
+
If the gdsfactory package is not installed.
|
|
245
|
+
"""
|
|
246
|
+
try:
|
|
247
|
+
import gdsfactory as gf
|
|
248
|
+
except ImportError:
|
|
249
|
+
raise ImportError(
|
|
250
|
+
"The gdsfactory package is required to use this function; "
|
|
251
|
+
"try `pip install gdsfactory`."
|
|
252
|
+
) from None
|
|
253
|
+
|
|
254
|
+
bounds = (
|
|
255
|
+
(component.xmin * 1000, component.ymin * 1000),
|
|
256
|
+
(component.xmax * 1000, component.ymax * 1000),
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
polygons = [
|
|
260
|
+
polygon
|
|
261
|
+
for polygons_list in component.get_polygons_points().values()
|
|
262
|
+
for polygon in polygons_list
|
|
263
|
+
]
|
|
264
|
+
|
|
265
|
+
contours = [
|
|
266
|
+
np.array(
|
|
267
|
+
[
|
|
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
|
|
275
|
+
]
|
|
276
|
+
)
|
|
277
|
+
for polygon in polygons
|
|
278
|
+
]
|
|
279
|
+
|
|
280
|
+
device_array = np.zeros(
|
|
281
|
+
(int(bounds[1][1] - bounds[0][1]), int(bounds[1][0] - bounds[0][0])),
|
|
282
|
+
dtype=np.uint8,
|
|
283
|
+
)
|
|
284
|
+
cv2.fillPoly(img=device_array, pts=contours, color=(1, 1, 1))
|
|
285
|
+
device_array = np.flipud(device_array)
|
|
286
|
+
return Device(device_array=device_array, **kwargs)
|
|
287
|
+
|
|
288
|
+
|
|
222
289
|
def from_sem(
|
|
223
290
|
sem_path: str,
|
|
224
291
|
sem_resolution: float = None,
|
|
@@ -314,3 +381,73 @@ def get_sem_resolution(sem_path: str, sem_resolution_key: str) -> float:
|
|
|
314
381
|
value /= 1000
|
|
315
382
|
return value
|
|
316
383
|
raise ValueError(f"Resolution key '{sem_resolution_key}' not found in {sem_path}.")
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def from_tidy3d(
|
|
387
|
+
tidy3d_sim: "tidy3d.Simulation", # noqa: F821
|
|
388
|
+
eps_threshold: float,
|
|
389
|
+
z: float,
|
|
390
|
+
**kwargs,
|
|
391
|
+
) -> Device:
|
|
392
|
+
"""
|
|
393
|
+
Create a Device from a Tidy3D simulation.
|
|
394
|
+
|
|
395
|
+
Parameters
|
|
396
|
+
----------
|
|
397
|
+
tidy3d_sim : tidy3d.Simulation
|
|
398
|
+
The Tidy3D simulation object.
|
|
399
|
+
eps_threshold : float
|
|
400
|
+
The threshold value for the permittivity to binarize the device array.
|
|
401
|
+
z : float
|
|
402
|
+
The z-coordinate at which to extract the permittivity.
|
|
403
|
+
**kwargs
|
|
404
|
+
Additional keyword arguments to be passed to the Device constructor.
|
|
405
|
+
|
|
406
|
+
Returns
|
|
407
|
+
-------
|
|
408
|
+
Device
|
|
409
|
+
A Device object representing the permittivity cross-section at the specified
|
|
410
|
+
z-coordinate for the Tidy3D simulation.
|
|
411
|
+
|
|
412
|
+
Raises
|
|
413
|
+
------
|
|
414
|
+
ValueError
|
|
415
|
+
If the z-coordinate is outside the bounds of the simulation size in the
|
|
416
|
+
z-direction.
|
|
417
|
+
ImportError
|
|
418
|
+
If the tidy3d package is not installed.
|
|
419
|
+
"""
|
|
420
|
+
try:
|
|
421
|
+
from tidy3d import Coords, Grid
|
|
422
|
+
except ImportError:
|
|
423
|
+
raise ImportError(
|
|
424
|
+
"The tidy3d package is required to use this function; "
|
|
425
|
+
"try `pip install tidy3d`."
|
|
426
|
+
) from None
|
|
427
|
+
|
|
428
|
+
if not (
|
|
429
|
+
tidy3d_sim.center[2] - tidy3d_sim.size[2] / 2
|
|
430
|
+
<= z
|
|
431
|
+
<= tidy3d_sim.center[2] + tidy3d_sim.size[2] / 2
|
|
432
|
+
):
|
|
433
|
+
raise ValueError(
|
|
434
|
+
f"z={z} is outside the bounds of the simulation size in the z-direction."
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
x = np.arange(
|
|
438
|
+
tidy3d_sim.center[0] - tidy3d_sim.size[0] / 2,
|
|
439
|
+
tidy3d_sim.center[0] + tidy3d_sim.size[0] / 2,
|
|
440
|
+
0.001,
|
|
441
|
+
)
|
|
442
|
+
y = np.arange(
|
|
443
|
+
tidy3d_sim.center[1] - tidy3d_sim.size[1] / 2,
|
|
444
|
+
tidy3d_sim.center[1] + tidy3d_sim.size[1] / 2,
|
|
445
|
+
0.001,
|
|
446
|
+
)
|
|
447
|
+
z = np.array([z])
|
|
448
|
+
|
|
449
|
+
grid = Grid(boundaries=Coords(x=x, y=y, z=z))
|
|
450
|
+
eps = np.real(tidy3d_sim.epsilon_on_grid(grid=grid, coord_key="boundaries").values)
|
|
451
|
+
device_array = geometry.binarize_hard(device_array=eps, eta=eps_threshold)[:, :, 0]
|
|
452
|
+
device_array = np.fliplr(np.rot90(device_array, k=-1))
|
|
453
|
+
return Device(device_array=device_array, **kwargs)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: prefab
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Artificial nanofabrication of integrated photonic circuits using deep learning
|
|
5
5
|
Project-URL: Homepage, https://prefabphotonics.com
|
|
6
6
|
Project-URL: Repository, https://github.com/PreFab-Photonics/PreFab
|
|
@@ -524,6 +524,7 @@ Requires-Dist: pillow
|
|
|
524
524
|
Requires-Dist: pydantic
|
|
525
525
|
Requires-Dist: requests
|
|
526
526
|
Requires-Dist: scikit-image
|
|
527
|
+
Requires-Dist: scipy
|
|
527
528
|
Requires-Dist: toml
|
|
528
529
|
Requires-Dist: tqdm
|
|
529
530
|
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
prefab/__init__.py,sha256=Tx4t6ODZeL-V-ZPuQXjEHHtwb7rjqCb2ApLFDniQ5fA,401
|
|
2
|
+
prefab/__main__.py,sha256=aAgt1WXa44k1nJqsiSD3uAfNeGpwtjWqMUYCHN5_Qrw,2759
|
|
3
|
+
prefab/compare.py,sha256=AfLJ69DTqvt0mkMqNCTdn2KWKoclt_0htGVXvarEhkY,2709
|
|
4
|
+
prefab/device.py,sha256=lklENpvqROifkEgNCT4BwU9xnsLt1nwdLksh5VDgpKU,58507
|
|
5
|
+
prefab/geometry.py,sha256=pXsVeu4Ycnq60bG6WqFNoUWnyiNStIaYQgbMIrEyndM,9614
|
|
6
|
+
prefab/models.py,sha256=UMzYZzKouroxlwkXCMKIYozmQCMhNhvt8kQrZmwmZB4,3671
|
|
7
|
+
prefab/read.py,sha256=Rzj9GGrszsKWdY8GxtmpfYcA4nsiuxgwSdEDVvfHN80,14624
|
|
8
|
+
prefab/shapes.py,sha256=Hc6dc2Y5Wmb2mZAxhdjBNEJF7C7tF2o460HNvcqcQdo,24856
|
|
9
|
+
prefab-1.1.0.dist-info/METADATA,sha256=tHahGccx-TKDbeo2ZMKTjC_JYPWesvovFyUuTONF_YA,34817
|
|
10
|
+
prefab-1.1.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
11
|
+
prefab-1.1.0.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
|
|
12
|
+
prefab-1.1.0.dist-info/RECORD,,
|
prefab-1.0.3.dist-info/RECORD
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
prefab/__init__.py,sha256=54JuP2jMRBoA7uWb9uoQ_VXXsi_V1ld_LM2A9wXiLkA,401
|
|
2
|
-
prefab/__main__.py,sha256=aAgt1WXa44k1nJqsiSD3uAfNeGpwtjWqMUYCHN5_Qrw,2759
|
|
3
|
-
prefab/compare.py,sha256=AfLJ69DTqvt0mkMqNCTdn2KWKoclt_0htGVXvarEhkY,2709
|
|
4
|
-
prefab/device.py,sha256=sWDL9xXjDs0LzL91oGu8JFvYCQcMF8msz7fjbxwDWEg,48397
|
|
5
|
-
prefab/geometry.py,sha256=pXsVeu4Ycnq60bG6WqFNoUWnyiNStIaYQgbMIrEyndM,9614
|
|
6
|
-
prefab/models.py,sha256=UMzYZzKouroxlwkXCMKIYozmQCMhNhvt8kQrZmwmZB4,3671
|
|
7
|
-
prefab/read.py,sha256=HNuovvQx0Vg0X-z5-gUlL-T1M1RQf81Bmy0wDAdqO28,10782
|
|
8
|
-
prefab/shapes.py,sha256=Hc6dc2Y5Wmb2mZAxhdjBNEJF7C7tF2o460HNvcqcQdo,24856
|
|
9
|
-
prefab-1.0.3.dist-info/METADATA,sha256=yQgJ6qqL8WWfcZmVxRpmAvucNq1XSBYLQra4fmpH3T0,34796
|
|
10
|
-
prefab-1.0.3.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
11
|
-
prefab-1.0.3.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
|
|
12
|
-
prefab-1.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|