prefab 1.0.4__py3-none-any.whl → 1.1.1__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 CHANGED
@@ -5,7 +5,7 @@ Usage:
5
5
  import prefab as pf
6
6
  """
7
7
 
8
- __version__ = "1.0.4"
8
+ __version__ = "1.1.1"
9
9
 
10
10
  from . import compare, geometry, read, shapes
11
11
  from .device import BufferSpec, Device
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
@@ -490,6 +493,7 @@ class Device(BaseModel):
490
493
  binarize=False,
491
494
  gpu=gpu,
492
495
  )
496
+ semulated_array += np.random.normal(0, 0.03, semulated_array.shape)
493
497
  return self.model_copy(update={"device_array": semulated_array})
494
498
 
495
499
  def to_ndarray(self) -> np.ndarray:
@@ -670,6 +674,152 @@ class Device(BaseModel):
670
674
 
671
675
  return cell
672
676
 
677
+ def to_gdsfactory(self) -> "gf.Component": # noqa: F821
678
+ """
679
+ Convert the device geometry to a gdsfactory Component.
680
+
681
+ Returns
682
+ -------
683
+ gf.Component
684
+ A gdsfactory Component object representing the device geometry.
685
+
686
+ Raises
687
+ ------
688
+ ImportError
689
+ If the gdsfactory package is not installed.
690
+ """
691
+ try:
692
+ import gdsfactory as gf
693
+ except ImportError:
694
+ raise ImportError(
695
+ "The gdsfactory package is required to use this function; "
696
+ "try `pip install gdsfactory`."
697
+ ) from None
698
+
699
+ device_array = np.rot90(self.to_ndarray(), k=-1)
700
+ return gf.read.from_np(device_array, nm_per_pixel=1)
701
+
702
+ def to_tidy3d(
703
+ self,
704
+ eps0: float,
705
+ thickness: float,
706
+ ) -> "td.Structure": # noqa: F821
707
+ """
708
+ Convert the device geometry to a Tidy3D Structure.
709
+
710
+ Parameters
711
+ ----------
712
+ eps0 : float
713
+ The permittivity value to assign to the device array.
714
+ thickness : float
715
+ The thickness of the device in the z-direction.
716
+
717
+ Returns
718
+ -------
719
+ td.Structure
720
+ A Tidy3D Structure object representing the device geometry.
721
+
722
+ Raises
723
+ ------
724
+ ImportError
725
+ If the tidy3d package is not installed.
726
+ """
727
+ try:
728
+ from tidy3d import Box, CustomMedium, SpatialDataArray, Structure, inf
729
+ except ImportError:
730
+ raise ImportError(
731
+ "The tidy3d package is required to use this function; "
732
+ "try `pip install tidy3d`."
733
+ ) from None
734
+
735
+ X = np.linspace(-self.shape[1] / 2000, self.shape[1] / 2000, self.shape[1])
736
+ Y = np.linspace(-self.shape[0] / 2000, self.shape[0] / 2000, self.shape[0])
737
+ Z = np.array([0])
738
+
739
+ device_array = np.rot90(np.fliplr(self.device_array), k=1)
740
+ eps_array = np.where(device_array >= 1.0, eps0, device_array)
741
+ eps_array = np.where(eps_array < 1.0, 1.0, eps_array)
742
+ eps_dataset = SpatialDataArray(eps_array, coords=dict(x=X, y=Y, z=Z))
743
+ medium = CustomMedium.from_eps_raw(eps_dataset)
744
+ return Structure(
745
+ geometry=Box(center=(0, 0, 0), size=(inf, inf, thickness)), medium=medium
746
+ )
747
+
748
+ def to_3d(self, thickness_nm: int) -> np.ndarray:
749
+ """
750
+ Convert the 2D device geometry into a 3D representation.
751
+
752
+ This method creates a 3D array by interpolating between the bottom and top
753
+ layers of the device geometry. The interpolation is linear.
754
+
755
+ Parameters
756
+ ----------
757
+ thickness_nm : int
758
+ The thickness of the 3D representation in nanometers.
759
+
760
+ Returns
761
+ -------
762
+ np.ndarray
763
+ A 3D narray representing the device geometry with the specified thickness.
764
+ """
765
+ bottom_layer = self.device_array[:, :, 0]
766
+ top_layer = self.device_array[:, :, -1]
767
+ dt_bottom = distance_transform_edt(bottom_layer) - distance_transform_edt(
768
+ 1 - bottom_layer
769
+ )
770
+ dt_top = distance_transform_edt(top_layer) - distance_transform_edt(
771
+ 1 - top_layer
772
+ )
773
+ weights = np.linspace(0, 1, thickness_nm)
774
+ layered_array = np.zeros(
775
+ (bottom_layer.shape[0], bottom_layer.shape[1], thickness_nm)
776
+ )
777
+ for i, w in enumerate(weights):
778
+ dt_interp = (1 - w) * dt_bottom + w * dt_top
779
+ layered_array[:, :, i] = dt_interp >= 0
780
+ return layered_array
781
+
782
+ def to_stl(self, thickness_nm: int, filename: str = "prefab_device.stl"):
783
+ """
784
+ Export the device geometry as an STL file.
785
+
786
+ Parameters
787
+ ----------
788
+ thickness_nm : int
789
+ The thickness of the 3D representation in nanometers.
790
+ filename : str, optional
791
+ The name of the STL file to save. Defaults to "prefab_device.stl".
792
+
793
+ Raises
794
+ ------
795
+ ValueError
796
+ If the thickness is not a positive integer.
797
+ ImportError
798
+ If the numpy-stl package is not installed.
799
+ """
800
+ try:
801
+ from stl import mesh
802
+ except ImportError:
803
+ raise ImportError(
804
+ "The stl package is required to use this function; "
805
+ "try `pip install numpy-stl`."
806
+ ) from None
807
+
808
+ if thickness_nm <= 0:
809
+ raise ValueError("Thickness must be a positive integer.")
810
+
811
+ layered_array = self.to_3d(thickness_nm)
812
+ layered_array = np.pad(
813
+ layered_array, ((0, 0), (0, 0), (10, 10)), mode="constant"
814
+ )
815
+ verts, faces, _, _ = measure.marching_cubes(layered_array, level=0.5)
816
+ cube = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype))
817
+ for i, f in enumerate(faces):
818
+ for j in range(3):
819
+ cube.vectors[i][j] = verts[f[j], :]
820
+ cube.save(filename)
821
+ print(f"Saved Device to '{filename}'")
822
+
673
823
  def _plot_base(
674
824
  self,
675
825
  plot_array: np.ndarray,
@@ -1294,4 +1444,90 @@ class Device(BaseModel):
1294
1444
  """
1295
1445
  return 1 - 2 * np.abs(0.5 - self.device_array)
1296
1446
 
1297
- return 1 - 2 * np.abs(0.5 - self.device_array)
1447
+ def enforce_feature_size(
1448
+ self, min_feature_size: int, strel: str = "disk"
1449
+ ) -> "Device":
1450
+ """
1451
+ Enforce a minimum feature size on the device geometry.
1452
+
1453
+ This method applies morphological operations to ensure that all features in the
1454
+ device geometry are at least the specified minimum size. It uses either a disk
1455
+ or square structuring element for the operations.
1456
+
1457
+ Parameters
1458
+ ----------
1459
+ min_feature_size : int
1460
+ The minimum feature size to enforce, in nanometers.
1461
+ strel : str, optional
1462
+ The type of structuring element to use. Can be either "disk" or "square".
1463
+ Defaults to "disk".
1464
+
1465
+ Returns
1466
+ -------
1467
+ Device
1468
+ A new instance of the Device with the modified geometry.
1469
+
1470
+ Raises
1471
+ ------
1472
+ ValueError
1473
+ If an invalid structuring element type is specified.
1474
+ """
1475
+ if strel == "disk":
1476
+ structuring_element = disk(radius=min_feature_size / 2)
1477
+ elif strel == "square":
1478
+ structuring_element = square(width=min_feature_size)
1479
+ else:
1480
+ raise ValueError(f"Invalid structuring element: {strel}")
1481
+
1482
+ modified_geometry = closing(self.device_array[:, :, 0], structuring_element)
1483
+ modified_geometry = opening(modified_geometry, structuring_element)
1484
+ modified_geometry = np.expand_dims(modified_geometry, axis=-1)
1485
+ return self.model_copy(update={"device_array": modified_geometry})
1486
+
1487
+ def check_feature_size(self, min_feature_size: int, strel: str = "disk"):
1488
+ """
1489
+ Check and visualize the effect of enforcing a minimum feature size on the device
1490
+ geometry.
1491
+
1492
+ This method enforces a minimum feature size on the device geometry using the
1493
+ specified structuring element, compares the modified geometry with the original,
1494
+ and plots the differences. It also calculates and prints the Hamming distance
1495
+ between the original and modified geometries, providing a measure of the changes
1496
+ introduced by the feature size enforcement.
1497
+
1498
+ Parameters
1499
+ ----------
1500
+ min_feature_size : int
1501
+ The minimum feature size to enforce, in nanometers.
1502
+ strel : str, optional
1503
+ The type of structuring element to use. Can be either "disk" or "square".
1504
+ Defaults to "disk".
1505
+
1506
+ Raises
1507
+ ------
1508
+ ValueError
1509
+ If an invalid structuring element type is specified or if min_feature_size
1510
+ is not a positive integer.
1511
+ """
1512
+ if min_feature_size <= 0:
1513
+ raise ValueError("min_feature_size must be a positive integer.")
1514
+
1515
+ enforced_device = self.enforce_feature_size(min_feature_size, strel)
1516
+
1517
+ difference = np.abs(
1518
+ enforced_device.device_array[:, :, 0] - self.device_array[:, :, 0]
1519
+ )
1520
+ _, ax = self._plot_base(
1521
+ plot_array=difference,
1522
+ show_buffer=False,
1523
+ ax=None,
1524
+ bounds=None,
1525
+ cmap="jet",
1526
+ )
1527
+
1528
+ hamming_distance = compare.hamming_distance(self, enforced_device)
1529
+ print(
1530
+ f"Feature size check with minimum size {min_feature_size} "
1531
+ f"using '{strel}' structuring element resulted in a Hamming "
1532
+ f"distance of: {hamming_distance}"
1533
+ )
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.4
3
+ Version: 1.1.1
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=v7MDKK-aRRrdT77IJ5jUcfBc3k6zmGUQnn_fqzNoj_A,401
2
+ prefab/__main__.py,sha256=aAgt1WXa44k1nJqsiSD3uAfNeGpwtjWqMUYCHN5_Qrw,2759
3
+ prefab/compare.py,sha256=AfLJ69DTqvt0mkMqNCTdn2KWKoclt_0htGVXvarEhkY,2709
4
+ prefab/device.py,sha256=Fxgl1Yhc-5KQyrt80NlLsmsr47kz3fShjZdoXw9zZbc,58687
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.1.dist-info/METADATA,sha256=ZHKTRE8c39t2itPHkTWJ8wZUJ4Wk0BQcDgmkjLIYRGQ,34817
10
+ prefab-1.1.1.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
11
+ prefab-1.1.1.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
12
+ prefab-1.1.1.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- prefab/__init__.py,sha256=z7di6Rcufbv_haAVfAam3Q_EZaBWjv-DWpidsqLlBmc,401
2
- prefab/__main__.py,sha256=aAgt1WXa44k1nJqsiSD3uAfNeGpwtjWqMUYCHN5_Qrw,2759
3
- prefab/compare.py,sha256=AfLJ69DTqvt0mkMqNCTdn2KWKoclt_0htGVXvarEhkY,2709
4
- prefab/device.py,sha256=qIPQNt3nqIpeZasBWHNFxdHC6EBt1_fgWdmeUVWDPlE,50109
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.4.dist-info/METADATA,sha256=DI5SMrZH42zLQnaJlzzKkU5BU9KEpAJsSayAvIxIAk0,34796
10
- prefab-1.0.4.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
11
- prefab-1.0.4.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
12
- prefab-1.0.4.dist-info/RECORD,,
File without changes