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 CHANGED
@@ -5,7 +5,7 @@ Usage:
5
5
  import prefab as pf
6
6
  """
7
7
 
8
- __version__ = "1.0.3"
8
+ __version__ = "1.1.0"
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
@@ -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 = "https://prefab-photonics--predict-v1.modal.run"
262
-
263
- with requests.post(
264
- endpoint_url, data=json_data, headers=headers, stream=True
265
- ) as response:
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
- for line in response.iter_lines():
278
- if line:
279
- decoded_line = line.decode("utf-8").strip()
280
- if decoded_line.startswith("event:"):
281
- event_type = decoded_line.split(":")[1].strip()
282
- elif decoded_line.startswith("data:"):
283
- try:
284
- data_content = json.loads(decoded_line.split("data: ")[1])
285
- if event_type == "progress":
286
- progress = round(100 * data_content["progress"])
287
- progress_bar.update(progress - progress_bar.n)
288
- elif event_type == "result":
289
- results = []
290
- for key in sorted(data_content.keys()):
291
- if key.startswith("result"):
292
- decoded_image = self._decode_array(
293
- data_content[key]
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
- if results:
298
- prediction = np.stack(results, axis=-1)
299
- if binarize:
300
- prediction = geometry.binarize_hard(prediction)
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
- return prediction
303
- elif event_type == "end":
304
- print("Stream ended.")
305
- progress_bar.close()
306
- break
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
- elif event_type == "error":
327
- print(f"Error: {data_content['error']}")
328
- progress_bar.close()
329
- except json.JSONDecodeError:
330
- print(
331
- "Failed to decode JSON:",
332
- decoded_line.split("data: ")[1],
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
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,,
@@ -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