prefab 1.0.2__py3-none-any.whl → 1.0.3__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,9 @@ Usage:
5
5
  import prefab as pf
6
6
  """
7
7
 
8
- from . import compare, geometry, read
8
+ __version__ = "1.0.3"
9
+
10
+ from . import compare, geometry, read, shapes
9
11
  from .device import BufferSpec, Device
10
12
  from .models import models
11
13
 
@@ -14,6 +16,8 @@ __all__ = [
14
16
  "BufferSpec",
15
17
  "geometry",
16
18
  "read",
19
+ "shapes",
17
20
  "compare",
18
21
  "models",
22
+ "__version__",
19
23
  ]
prefab/compare.py CHANGED
@@ -7,40 +7,40 @@ from .device import Device
7
7
 
8
8
  def mean_squared_error(device_a: Device, device_b: Device) -> float:
9
9
  """
10
- Calculate the mean squared error (MSE) between two non-binarized devices.
10
+ Calculate the mean squared error (MSE) between two non-binarized devices. A lower
11
+ value indicates more similarity.
11
12
 
12
13
  Parameters
13
14
  ----------
14
15
  device_a : Device
15
- The first device.
16
+ The first device (non-binarized).
16
17
  device_b : Device
17
- The second device.
18
+ The second device (non-binarized).
18
19
 
19
20
  Returns
20
21
  -------
21
22
  float
22
- The mean squared error between two devices. A lower value indicates more
23
- similarity.
23
+ The mean squared error between two devices.
24
24
  """
25
25
  return np.mean((device_a.device_array - device_b.device_array) ** 2)
26
26
 
27
27
 
28
28
  def intersection_over_union(device_a: Device, device_b: Device) -> float:
29
29
  """
30
- Calculates the Intersection over Union (IoU) between two binary devices.
30
+ Calculates the Intersection over Union (IoU) between two binary devices. A value
31
+ closer to 1 indicates more similarity (more overlap).
31
32
 
32
33
  Parameters
33
34
  ----------
34
35
  device_a : Device
35
- The first device.
36
+ The first device (binarized).
36
37
  device_b : Device
37
- The second device.
38
+ The second device (binarized).
38
39
 
39
40
  Returns
40
41
  -------
41
42
  float
42
- The Intersection over Union between two devices. A value closer to 1 indicates
43
- more similarity (more overlap).
43
+ The Intersection over Union between two devices.
44
44
  """
45
45
  return np.sum(
46
46
  np.logical_and(device_a.device_array, device_b.device_array)
@@ -49,40 +49,42 @@ def intersection_over_union(device_a: Device, device_b: Device) -> float:
49
49
 
50
50
  def hamming_distance(device_a: Device, device_b: Device) -> int:
51
51
  """
52
- Calculates the Hamming distance between two binary devices.
52
+ Calculates the Hamming distance between two binary devices. A lower value indicates
53
+ more similarity. The Hamming distance is calculated as the number of positions at
54
+ which the corresponding pixels are different.
53
55
 
54
56
  Parameters
55
57
  ----------
56
58
  device_a : Device
57
- The first device.
59
+ The first device (binarized).
58
60
  device_b : Device
59
- The second device.
61
+ The second device (binarized).
60
62
 
61
63
  Returns
62
64
  -------
63
65
  int
64
- The Hamming distance between two devices. A lower value indicates more
65
- similarity.
66
+ The Hamming distance between two devices.
66
67
  """
67
68
  return np.sum(device_a.device_array != device_b.device_array)
68
69
 
69
70
 
70
71
  def dice_coefficient(device_a: Device, device_b: Device) -> float:
71
72
  """
72
- Calculates the Dice coefficient between two binary devices.
73
+ Calculates the Dice coefficient between two binary devices. A value closer to 1
74
+ indicates more similarity. The Dice coefficient is calculated as twice the number of
75
+ pixels in common divided by the total number of pixels in the two devices.
73
76
 
74
77
  Parameters
75
78
  ----------
76
79
  device_a : Device
77
- The first device.
80
+ The first device (binarized).
78
81
  device_b : Device
79
- The second device.
82
+ The second device (binarized).
80
83
 
81
84
  Returns
82
85
  -------
83
86
  float
84
- The Dice coefficient between two devices. A value closer to 1 indicates more
85
- similarity.
87
+ The Dice coefficient between two devices.
86
88
  """
87
89
  intersection = 2.0 * np.sum(
88
90
  np.logical_and(device_a.device_array, device_b.device_array)
prefab/device.py CHANGED
@@ -40,14 +40,16 @@ class BufferSpec(BaseModel):
40
40
  ('top', 'bottom', 'left', 'right'), where 'constant' is used for isolated
41
41
  structures and 'edge' is utilized for preserving the edge, such as for waveguide
42
42
  connections.
43
- thickness : conint(gt=0)
44
- The thickness of the buffer zone around the device. Must be greater than 0.
43
+ thickness : dict[str, conint(gt=0)]
44
+ A dictionary that defines the thickness of the buffer zone for each side of the
45
+ device ('top', 'bottom', 'left', 'right'). Each value must be greater than 0.
45
46
 
46
47
  Raises
47
48
  ------
48
49
  ValueError
49
50
  If any of the modes specified in the 'mode' dictionary are not one of the
50
- allowed values ('constant', 'edge'). Or if the thickness is not greater than 0.
51
+ allowed values ('constant', 'edge'). Or if any of the thickness values are not
52
+ greater than 0.
51
53
 
52
54
  Example
53
55
  -------
@@ -60,7 +62,12 @@ class BufferSpec(BaseModel):
60
62
  "left": "constant",
61
63
  "right": "edge",
62
64
  },
63
- thickness=150,
65
+ thickness={
66
+ "top": 150,
67
+ "bottom": 100,
68
+ "left": 200,
69
+ "right": 250,
70
+ },
64
71
  )
65
72
  """
66
73
 
@@ -72,7 +79,14 @@ class BufferSpec(BaseModel):
72
79
  "right": "constant",
73
80
  }
74
81
  )
75
- thickness: conint(gt=0) = 128
82
+ thickness: dict[str, conint(gt=0)] = Field(
83
+ default_factory=lambda: {
84
+ "top": 128,
85
+ "bottom": 128,
86
+ "left": 128,
87
+ "right": 128,
88
+ }
89
+ )
76
90
 
77
91
  @validator("mode", pre=True)
78
92
  def check_mode(cls, v):
@@ -146,28 +160,26 @@ class Device(BaseModel):
146
160
 
147
161
  self.device_array = np.pad(
148
162
  self.device_array,
149
- pad_width=((buffer_thickness, 0), (0, 0)),
163
+ pad_width=((buffer_thickness["top"], 0), (0, 0)),
150
164
  mode=buffer_mode["top"],
151
165
  )
152
166
  self.device_array = np.pad(
153
167
  self.device_array,
154
- pad_width=((0, buffer_thickness), (0, 0)),
168
+ pad_width=((0, buffer_thickness["bottom"]), (0, 0)),
155
169
  mode=buffer_mode["bottom"],
156
170
  )
157
171
  self.device_array = np.pad(
158
172
  self.device_array,
159
- pad_width=((0, 0), (buffer_thickness, 0)),
173
+ pad_width=((0, 0), (buffer_thickness["left"], 0)),
160
174
  mode=buffer_mode["left"],
161
175
  )
162
176
  self.device_array = np.pad(
163
177
  self.device_array,
164
- pad_width=((0, 0), (0, buffer_thickness)),
178
+ pad_width=((0, 0), (0, buffer_thickness["right"])),
165
179
  mode=buffer_mode["right"],
166
180
  )
167
181
 
168
- self.device_array = np.expand_dims(
169
- self.device_array.astype(np.float32), axis=-1
170
- )
182
+ self.device_array = np.expand_dims(self.device_array, axis=-1)
171
183
 
172
184
  @root_validator(pre=True)
173
185
  def check_device_array(cls, values):
@@ -464,10 +476,12 @@ class Device(BaseModel):
464
476
  buffer_thickness = self.buffer_spec.thickness
465
477
  buffer_mode = self.buffer_spec.mode
466
478
 
467
- crop_top = buffer_thickness if buffer_mode["top"] == "edge" else 0
468
- crop_bottom = buffer_thickness if buffer_mode["bottom"] == "edge" else 0
469
- crop_left = buffer_thickness if buffer_mode["left"] == "edge" else 0
470
- crop_right = buffer_thickness if buffer_mode["right"] == "edge" else 0
479
+ crop_top = buffer_thickness["top"] if buffer_mode["top"] == "edge" else 0
480
+ crop_bottom = (
481
+ buffer_thickness["bottom"] if buffer_mode["bottom"] == "edge" else 0
482
+ )
483
+ crop_left = buffer_thickness["left"] if buffer_mode["left"] == "edge" else 0
484
+ crop_right = buffer_thickness["right"] if buffer_mode["right"] == "edge" else 0
471
485
 
472
486
  ndarray = device_array[
473
487
  crop_top : device_array.shape[0] - crop_bottom,
@@ -629,7 +643,7 @@ class Device(BaseModel):
629
643
  bounds: Optional[tuple[tuple[int, int], tuple[int, int]]],
630
644
  ax: Optional[Axes],
631
645
  **kwargs,
632
- ) -> Axes:
646
+ ) -> tuple[plt.cm.ScalarMappable, Axes]:
633
647
  if ax is None:
634
648
  _, ax = plt.subplots()
635
649
  ax.set_ylabel("y (nm)")
@@ -673,6 +687,13 @@ class Device(BaseModel):
673
687
  if show_buffer:
674
688
  self._add_buffer_visualization(ax)
675
689
 
690
+ # # Adjust colorbar font size if a colorbar is added
691
+ # if "cmap" in kwargs:
692
+ # cbar = plt.colorbar(mappable, ax=ax)
693
+ # cbar.ax.tick_params(labelsize=14)
694
+ # if "label" in kwargs:
695
+ # cbar.set_label(kwargs["label"], fontsize=16)
696
+
676
697
  return mappable, ax
677
698
 
678
699
  def plot(
@@ -937,9 +958,9 @@ class Device(BaseModel):
937
958
  buffer_hatch = "/"
938
959
 
939
960
  mid_rect = Rectangle(
940
- (buffer_thickness, buffer_thickness),
941
- plot_array.shape[1] - 2 * buffer_thickness,
942
- plot_array.shape[0] - 2 * buffer_thickness,
961
+ (buffer_thickness["left"], buffer_thickness["top"]),
962
+ plot_array.shape[1] - buffer_thickness["left"] - buffer_thickness["right"],
963
+ plot_array.shape[0] - buffer_thickness["top"] - buffer_thickness["bottom"],
943
964
  facecolor="none",
944
965
  edgecolor="black",
945
966
  linewidth=1,
@@ -949,25 +970,25 @@ class Device(BaseModel):
949
970
  top_rect = Rectangle(
950
971
  (0, 0),
951
972
  plot_array.shape[1],
952
- buffer_thickness,
973
+ buffer_thickness["top"],
953
974
  facecolor=buffer_fill,
954
975
  hatch=buffer_hatch,
955
976
  )
956
977
  ax.add_patch(top_rect)
957
978
 
958
979
  bottom_rect = Rectangle(
959
- (0, plot_array.shape[0] - buffer_thickness),
980
+ (0, plot_array.shape[0] - buffer_thickness["bottom"]),
960
981
  plot_array.shape[1],
961
- buffer_thickness,
982
+ buffer_thickness["bottom"],
962
983
  facecolor=buffer_fill,
963
984
  hatch=buffer_hatch,
964
985
  )
965
986
  ax.add_patch(bottom_rect)
966
987
 
967
988
  left_rect = Rectangle(
968
- (0, buffer_thickness),
969
- buffer_thickness,
970
- plot_array.shape[0] - 2 * buffer_thickness,
989
+ (0, buffer_thickness["top"]),
990
+ buffer_thickness["left"],
991
+ plot_array.shape[0] - buffer_thickness["top"] - buffer_thickness["bottom"],
971
992
  facecolor=buffer_fill,
972
993
  hatch=buffer_hatch,
973
994
  )
@@ -975,11 +996,11 @@ class Device(BaseModel):
975
996
 
976
997
  right_rect = Rectangle(
977
998
  (
978
- plot_array.shape[1] - buffer_thickness,
979
- buffer_thickness,
999
+ plot_array.shape[1] - buffer_thickness["right"],
1000
+ buffer_thickness["top"],
980
1001
  ),
981
- buffer_thickness,
982
- plot_array.shape[0] - 2 * buffer_thickness,
1002
+ buffer_thickness["right"],
1003
+ plot_array.shape[0] - buffer_thickness["top"] - buffer_thickness["bottom"],
983
1004
  facecolor=buffer_fill,
984
1005
  hatch=buffer_hatch,
985
1006
  )
@@ -1017,7 +1038,9 @@ class Device(BaseModel):
1017
1038
  binarized_device_array = geometry.binarize(
1018
1039
  device_array=self.device_array, eta=eta, beta=beta
1019
1040
  )
1020
- return self.model_copy(update={"device_array": binarized_device_array})
1041
+ return self.model_copy(
1042
+ update={"device_array": binarized_device_array.astype(np.uint8)}
1043
+ )
1021
1044
 
1022
1045
  def binarize_hard(self, eta: float = 0.5) -> "Device":
1023
1046
  """
@@ -1038,12 +1061,14 @@ class Device(BaseModel):
1038
1061
  binarized_device_array = geometry.binarize_hard(
1039
1062
  device_array=self.device_array, eta=eta
1040
1063
  )
1041
- return self.model_copy(update={"device_array": binarized_device_array})
1064
+ return self.model_copy(
1065
+ update={"device_array": binarized_device_array.astype(np.uint8)}
1066
+ )
1042
1067
 
1043
1068
  def binarize_monte_carlo(
1044
1069
  self,
1045
1070
  threshold_noise_std: float = 2.0,
1046
- threshold_blur_std: float = 9.0,
1071
+ threshold_blur_std: float = 8.0,
1047
1072
  ) -> "Device":
1048
1073
  """
1049
1074
  Binarize the device geometry using a Monte Carlo approach with Gaussian
@@ -1106,9 +1131,10 @@ class Device(BaseModel):
1106
1131
 
1107
1132
  Parameters
1108
1133
  ----------
1109
- buffer_thickness : int, optional
1110
- The thickness of the buffer to leave around the empty space. Defaults to 0,
1111
- which means no buffer is added.
1134
+ buffer_thickness : dict, optional
1135
+ A dictionary specifying the thickness of the buffer to leave around the
1136
+ non-zero elements of the array. Should contain keys 'top', 'bottom', 'left',
1137
+ 'right'. Defaults to None, which means no buffer is added.
1112
1138
 
1113
1139
  Returns
1114
1140
  -------
prefab/geometry.py CHANGED
@@ -124,7 +124,7 @@ def binarize_monte_carlo(
124
124
  generated threshold.
125
125
  """
126
126
  device_array = np.squeeze(device_array)
127
- base_threshold = np.clip(np.random.normal(loc=0.5, scale=0.5 / 2), 0.4, 0.6)
127
+ base_threshold = np.random.normal(loc=0.5, scale=0.1)
128
128
  threshold_noise = np.random.normal(
129
129
  loc=0, scale=threshold_noise_std, size=device_array.shape
130
130
  )
@@ -161,7 +161,7 @@ def ternarize(
161
161
  return np.where(device_array < eta1, 0.0, np.where(device_array >= eta2, 1.0, 0.5))
162
162
 
163
163
 
164
- def trim(device_array: np.ndarray, buffer_thickness: int = 0) -> np.ndarray:
164
+ def trim(device_array: np.ndarray, buffer_thickness: dict = None) -> np.ndarray:
165
165
  """
166
166
  Trim the input ndarray by removing rows and columns that are completely zero.
167
167
 
@@ -169,25 +169,28 @@ def trim(device_array: np.ndarray, buffer_thickness: int = 0) -> np.ndarray:
169
169
  ----------
170
170
  device_array : np.ndarray
171
171
  The input array to be trimmed.
172
- buffer_thickness : int, optional
173
- The thickness of the buffer to leave around the non-zero elements of the array.
174
- Defaults to 0, which means no buffer is added.
172
+ buffer_thickness : dict, optional
173
+ A dictionary specifying the thickness of the buffer to leave around the non-zero
174
+ elements of the array. Should contain keys 'top', 'bottom', 'left', 'right'.
175
+ Defaults to None, which means no buffer is added.
175
176
 
176
177
  Returns
177
178
  -------
178
179
  np.ndarray
179
180
  The trimmed array, potentially with a buffer around the non-zero elements.
180
181
  """
181
- flattened_device_array = np.squeeze(flatten(device_array))
182
- nonzero_rows, nonzero_cols = np.nonzero(flattened_device_array)
183
- row_min = max(nonzero_rows.min() - buffer_thickness, 0)
182
+ if buffer_thickness is None:
183
+ buffer_thickness = {"top": 0, "bottom": 0, "left": 0, "right": 0}
184
+
185
+ nonzero_rows, nonzero_cols = np.nonzero(np.squeeze(device_array))
186
+ row_min = max(nonzero_rows.min() - buffer_thickness.get("top", 0), 0)
184
187
  row_max = min(
185
- nonzero_rows.max() + buffer_thickness + 1,
188
+ nonzero_rows.max() + buffer_thickness.get("bottom", 0) + 1,
186
189
  device_array.shape[0],
187
190
  )
188
- col_min = max(nonzero_cols.min() - buffer_thickness, 0)
191
+ col_min = max(nonzero_cols.min() - buffer_thickness.get("left", 0), 0)
189
192
  col_max = min(
190
- nonzero_cols.max() + buffer_thickness + 1,
193
+ nonzero_cols.max() + buffer_thickness.get("right", 0) + 1,
191
194
  device_array.shape[1],
192
195
  )
193
196
  return device_array[
@@ -237,10 +240,13 @@ def rotate(device_array: np.ndarray, angle: float) -> np.ndarray:
237
240
  """
238
241
  center = (device_array.shape[1] / 2, device_array.shape[0] / 2)
239
242
  rotation_matrix = cv2.getRotationMatrix2D(center=center, angle=angle, scale=1)
240
- rotated_device_array = cv2.warpAffine(
241
- flatten(device_array),
242
- M=rotation_matrix,
243
- dsize=(device_array.shape[1], device_array.shape[0]),
243
+ return np.expand_dims(
244
+ cv2.warpAffine(
245
+ device_array,
246
+ M=rotation_matrix,
247
+ dsize=(device_array.shape[1], device_array.shape[0]),
248
+ ),
249
+ axis=-1,
244
250
  )
245
251
  return np.expand_dims(rotated_device_array, axis=-1)
246
252
 
prefab/models.py CHANGED
@@ -97,6 +97,24 @@ generic_DUV_SOI = Fab(
97
97
  has_sidewall=True,
98
98
  )
99
99
 
100
+ ANT_NanoSOI_ANF0_d8 = Model(
101
+ fab=ANT_NanoSOI,
102
+ version="ANF0",
103
+ version_date=date(2024, 1, 1),
104
+ dataset="d8",
105
+ dataset_date=date(2024, 1, 1),
106
+ tag="",
107
+ )
108
+
109
+ ANT_NanoSOI_ANF1_d8 = Model(
110
+ fab=ANT_NanoSOI,
111
+ version="ANF1",
112
+ version_date=date(2024, 5, 6),
113
+ dataset="d8",
114
+ dataset_date=date(2024, 1, 1),
115
+ tag="",
116
+ )
117
+
100
118
  ANT_NanoSOI_ANF1_d9 = Model(
101
119
  fab=ANT_NanoSOI,
102
120
  version="ANF1",
@@ -106,6 +124,15 @@ ANT_NanoSOI_ANF1_d9 = Model(
106
124
  tag="",
107
125
  )
108
126
 
127
+ ANT_NanoSOI_ANF1_d10 = Model(
128
+ fab=ANT_NanoSOI,
129
+ version="ANF1",
130
+ version_date=date(2024, 5, 6),
131
+ dataset="d10",
132
+ dataset_date=date(2024, 6, 8),
133
+ tag="",
134
+ )
135
+
109
136
  ANT_SiN_ANF1_d1 = Model(
110
137
  fab=ANT_SiN,
111
138
  version="ANF1",
@@ -125,8 +152,11 @@ generic_DUV_SOI_ANF1_d0 = Model(
125
152
  )
126
153
 
127
154
  models = dict(
128
- ANT_NanoSOI=ANT_NanoSOI_ANF1_d9,
155
+ ANT_NanoSOI=ANT_NanoSOI_ANF1_d10,
156
+ ANT_NanoSOI_ANF0_d8=ANT_NanoSOI_ANF0_d8,
157
+ ANT_NanoSOI_ANF1_d8=ANT_NanoSOI_ANF1_d8,
129
158
  ANT_NanoSOI_ANF1_d9=ANT_NanoSOI_ANF1_d9,
159
+ ANT_NanoSOI_ANF1_d10=ANT_NanoSOI_ANF1_d10,
130
160
  ANT_SiN=ANT_SiN_ANF1_d1,
131
161
  ANT_SiN_ANF1_d1=ANT_SiN_ANF1_d1,
132
162
  generic_DUV_SOI=generic_DUV_SOI_ANF1_d0,
prefab/read.py CHANGED
@@ -1,4 +1,4 @@
1
- """Provides functions to create Devices from various data sources."""
1
+ """Provides functions to create a Device from various data sources."""
2
2
 
3
3
  import re
4
4
 
@@ -11,7 +11,7 @@ from .device import Device
11
11
 
12
12
 
13
13
  def from_ndarray(
14
- ndarray: np.ndarray, resolution: int = 1, binarize: bool = True, **kwargs
14
+ ndarray: np.ndarray, resolution: float = 1.0, binarize: bool = True, **kwargs
15
15
  ) -> Device:
16
16
  """
17
17
  Create a Device from an ndarray.
@@ -20,13 +20,13 @@ def from_ndarray(
20
20
  ----------
21
21
  ndarray : np.ndarray
22
22
  The input array representing the device layout.
23
- resolution : int, optional
24
- The resolution of the ndarray in nanometers per pixel, defaulting to 1 nm per
23
+ resolution : float, optional
24
+ The resolution of the ndarray in nanometers per pixel, defaulting to 1.0 nm per
25
25
  pixel. If specified, the input array will be resized based on this resolution to
26
26
  match the desired physical size.
27
27
  binarize : bool, optional
28
28
  If True, the input array will be binarized (converted to binary values) before
29
- conversion to a Device object. This is useful for processing grayscale images
29
+ conversion to a Device object. This is useful for processing grayscale arrays
30
30
  into binary masks. Defaults to True.
31
31
  **kwargs
32
32
  Additional keyword arguments to be passed to the Device constructor.
@@ -38,7 +38,10 @@ def from_ndarray(
38
38
  binarization.
39
39
  """
40
40
  device_array = ndarray
41
- device_array = cv2.resize(device_array, dsize=(0, 0), fx=resolution, fy=resolution)
41
+ if resolution != 1.0:
42
+ device_array = cv2.resize(
43
+ device_array, dsize=(0, 0), fx=resolution, fy=resolution
44
+ )
42
45
  if binarize:
43
46
  device_array = geometry.binarize_hard(device_array)
44
47
  return Device(device_array=device_array, **kwargs)
@@ -55,12 +58,11 @@ def from_img(
55
58
  img_path : str
56
59
  The path to the image file to be converted into a Device object.
57
60
  img_width_nm : int, optional
58
- The desired width of the device in nanometers. If specified, the image will be
59
- resized to this width while maintaining aspect ratio. If None, no resizing is
60
- performed.
61
+ The width of the image in nanometers. If specified, the Device will be resized
62
+ to this width while maintaining aspect ratio. If None, no resizing is performed.
61
63
  binarize : bool, optional
62
64
  If True, the image will be binarized (converted to binary values) before
63
- conversion to a Device object. This is useful for converting grayscale images
65
+ conversion to a Device object. This is useful for processing grayscale images
64
66
  into binary masks. Defaults to True.
65
67
  **kwargs
66
68
  Additional keyword arguments to be passed to the Device constructor.
@@ -73,8 +75,10 @@ def from_img(
73
75
  """
74
76
  device_array = cv2.imread(img_path, flags=cv2.IMREAD_GRAYSCALE) / 255
75
77
  if img_width_nm is not None:
76
- scale = img_width_nm / device_array.shape[1]
77
- device_array = cv2.resize(device_array, dsize=(0, 0), fx=scale, fy=scale)
78
+ resolution = img_width_nm / device_array.shape[1]
79
+ device_array = cv2.resize(
80
+ device_array, dsize=(0, 0), fx=resolution, fy=resolution
81
+ )
78
82
  if binarize:
79
83
  device_array = geometry.binarize_hard(device_array)
80
84
  return Device(device_array=device_array, **kwargs)
@@ -134,7 +138,8 @@ def from_gdstk(
134
138
  gdstk_cell : gdstk.Cell
135
139
  The gdstk.Cell object to be converted into a Device object.
136
140
  gds_layer : tuple[int, int], optional
137
- A tuple specifying the layer and datatype to be used. Defaults to (1, 0).
141
+ A tuple specifying the layer and datatype to be used from the cell. Defaults to
142
+ (1, 0).
138
143
  bounds : tuple[tuple[int, int], tuple[int, int]], optional
139
144
  A tuple specifying the bounds for cropping the cell before conversion, formatted
140
145
  as ((min_x, min_y), (max_x, max_y)), in units of the GDS file. If None, the
@@ -159,6 +164,24 @@ def _gdstk_to_device_array(
159
164
  gds_layer: tuple[int, int] = (1, 0),
160
165
  bounds: tuple[tuple[int, int], tuple[int, int]] = None,
161
166
  ) -> np.ndarray:
167
+ """
168
+ Convert a gdstk.Cell to a device array.
169
+
170
+ Parameters
171
+ ----------
172
+ gdstk_cell : gdstk.Cell
173
+ The gdstk.Cell object to be converted.
174
+ gds_layer : tuple[int, int], optional
175
+ The layer and datatype to be used from the cell. Defaults to (1, 0).
176
+ bounds : tuple[tuple[int, int], tuple[int, int]], optional
177
+ Bounds for cropping the cell, formatted as ((min_x, min_y), (max_x, max_y)).
178
+ If None, the entire cell is used.
179
+
180
+ Returns
181
+ -------
182
+ np.ndarray
183
+ The resulting device array.
184
+ """
162
185
  polygons = gdstk_cell.get_polygons(layer=gds_layer[0], datatype=gds_layer[1])
163
186
  if bounds:
164
187
  polygons = gdstk.slice(
@@ -184,12 +207,12 @@ def _gdstk_to_device_array(
184
207
  ]
185
208
  for vertex in polygon.points
186
209
  ],
187
- dtype=np.int32,
188
210
  )
189
211
  for polygon in polygons
190
212
  ]
191
213
  device_array = np.zeros(
192
- (int(bounds[1][1] - bounds[0][1]), int(bounds[1][0] - bounds[0][0]))
214
+ (int(bounds[1][1] - bounds[0][1]), int(bounds[1][0] - bounds[0][0])),
215
+ dtype=np.uint8,
193
216
  )
194
217
  cv2.fillPoly(img=device_array, pts=contours, color=(1, 1, 1))
195
218
  device_array = np.flipud(device_array)
@@ -200,7 +223,7 @@ def from_sem(
200
223
  sem_path: str,
201
224
  sem_resolution: float = None,
202
225
  sem_resolution_key: str = None,
203
- binarize: bool = True,
226
+ binarize: bool = False,
204
227
  bounds: tuple[tuple[int, int], tuple[int, int]] = None,
205
228
  **kwargs,
206
229
  ) -> Device:
@@ -220,7 +243,7 @@ def from_sem(
220
243
  binarize : bool, optional
221
244
  If True, the SEM image will be binarized (converted to binary values) before
222
245
  conversion to a Device object. This is needed for processing grayscale images
223
- into binary masks. Defaults to True.
246
+ into binary masks. Defaults to False.
224
247
  bounds : tuple[tuple[int, int], tuple[int, int]], optional
225
248
  A tuple specifying the bounds for cropping the image before conversion,
226
249
  formatted as ((min_x, min_y), (max_x, max_y)). If None, the entire image is
@@ -244,13 +267,13 @@ def from_sem(
244
267
  raise ValueError("Either sem_resolution or resolution_key must be provided.")
245
268
 
246
269
  device_array = cv2.imread(sem_path, flags=cv2.IMREAD_GRAYSCALE)
247
- if sem_resolution is not None:
248
- device_array = cv2.resize(
249
- device_array, dsize=(0, 0), fx=sem_resolution, fy=sem_resolution
250
- )
270
+ device_array = cv2.resize(
271
+ device_array, dsize=(0, 0), fx=sem_resolution, fy=sem_resolution
272
+ )
251
273
  if bounds is not None:
252
274
  device_array = device_array[
253
- -bounds[1][1] : -bounds[0][1], bounds[0][0] : bounds[1][0]
275
+ device_array.shape[0] - bounds[1][1] : device_array.shape[0] - bounds[0][1],
276
+ bounds[0][0] : bounds[1][0],
254
277
  ]
255
278
  if binarize:
256
279
  device_array = geometry.binarize_sem(device_array)