prefab 1.0.1__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
@@ -21,6 +21,8 @@ from tqdm import tqdm
21
21
  from . import geometry
22
22
  from .models import Model
23
23
 
24
+ Image.MAX_IMAGE_PIXELS = None
25
+
24
26
 
25
27
  class BufferSpec(BaseModel):
26
28
  """
@@ -38,14 +40,16 @@ class BufferSpec(BaseModel):
38
40
  ('top', 'bottom', 'left', 'right'), where 'constant' is used for isolated
39
41
  structures and 'edge' is utilized for preserving the edge, such as for waveguide
40
42
  connections.
41
- thickness : conint(gt=0)
42
- 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.
43
46
 
44
47
  Raises
45
48
  ------
46
49
  ValueError
47
50
  If any of the modes specified in the 'mode' dictionary are not one of the
48
- 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.
49
53
 
50
54
  Example
51
55
  -------
@@ -58,7 +62,12 @@ class BufferSpec(BaseModel):
58
62
  "left": "constant",
59
63
  "right": "edge",
60
64
  },
61
- thickness=150,
65
+ thickness={
66
+ "top": 150,
67
+ "bottom": 100,
68
+ "left": 200,
69
+ "right": 250,
70
+ },
62
71
  )
63
72
  """
64
73
 
@@ -70,7 +79,14 @@ class BufferSpec(BaseModel):
70
79
  "right": "constant",
71
80
  }
72
81
  )
73
- 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
+ )
74
90
 
75
91
  @validator("mode", pre=True)
76
92
  def check_mode(cls, v):
@@ -144,28 +160,26 @@ class Device(BaseModel):
144
160
 
145
161
  self.device_array = np.pad(
146
162
  self.device_array,
147
- pad_width=((buffer_thickness, 0), (0, 0)),
163
+ pad_width=((buffer_thickness["top"], 0), (0, 0)),
148
164
  mode=buffer_mode["top"],
149
165
  )
150
166
  self.device_array = np.pad(
151
167
  self.device_array,
152
- pad_width=((0, buffer_thickness), (0, 0)),
168
+ pad_width=((0, buffer_thickness["bottom"]), (0, 0)),
153
169
  mode=buffer_mode["bottom"],
154
170
  )
155
171
  self.device_array = np.pad(
156
172
  self.device_array,
157
- pad_width=((0, 0), (buffer_thickness, 0)),
173
+ pad_width=((0, 0), (buffer_thickness["left"], 0)),
158
174
  mode=buffer_mode["left"],
159
175
  )
160
176
  self.device_array = np.pad(
161
177
  self.device_array,
162
- pad_width=((0, 0), (0, buffer_thickness)),
178
+ pad_width=((0, 0), (0, buffer_thickness["right"])),
163
179
  mode=buffer_mode["right"],
164
180
  )
165
181
 
166
- self.device_array = np.expand_dims(
167
- self.device_array.astype(np.float32), axis=-1
168
- )
182
+ self.device_array = np.expand_dims(self.device_array, axis=-1)
169
183
 
170
184
  @root_validator(pre=True)
171
185
  def check_device_array(cls, values):
@@ -462,10 +476,12 @@ class Device(BaseModel):
462
476
  buffer_thickness = self.buffer_spec.thickness
463
477
  buffer_mode = self.buffer_spec.mode
464
478
 
465
- crop_top = buffer_thickness if buffer_mode["top"] == "edge" else 0
466
- crop_bottom = buffer_thickness if buffer_mode["bottom"] == "edge" else 0
467
- crop_left = buffer_thickness if buffer_mode["left"] == "edge" else 0
468
- 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
469
485
 
470
486
  ndarray = device_array[
471
487
  crop_top : device_array.shape[0] - crop_bottom,
@@ -627,7 +643,7 @@ class Device(BaseModel):
627
643
  bounds: Optional[tuple[tuple[int, int], tuple[int, int]]],
628
644
  ax: Optional[Axes],
629
645
  **kwargs,
630
- ) -> Axes:
646
+ ) -> tuple[plt.cm.ScalarMappable, Axes]:
631
647
  if ax is None:
632
648
  _, ax = plt.subplots()
633
649
  ax.set_ylabel("y (nm)")
@@ -671,6 +687,13 @@ class Device(BaseModel):
671
687
  if show_buffer:
672
688
  self._add_buffer_visualization(ax)
673
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
+
674
697
  return mappable, ax
675
698
 
676
699
  def plot(
@@ -935,9 +958,9 @@ class Device(BaseModel):
935
958
  buffer_hatch = "/"
936
959
 
937
960
  mid_rect = Rectangle(
938
- (buffer_thickness, buffer_thickness),
939
- plot_array.shape[1] - 2 * buffer_thickness,
940
- 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"],
941
964
  facecolor="none",
942
965
  edgecolor="black",
943
966
  linewidth=1,
@@ -947,25 +970,25 @@ class Device(BaseModel):
947
970
  top_rect = Rectangle(
948
971
  (0, 0),
949
972
  plot_array.shape[1],
950
- buffer_thickness,
973
+ buffer_thickness["top"],
951
974
  facecolor=buffer_fill,
952
975
  hatch=buffer_hatch,
953
976
  )
954
977
  ax.add_patch(top_rect)
955
978
 
956
979
  bottom_rect = Rectangle(
957
- (0, plot_array.shape[0] - buffer_thickness),
980
+ (0, plot_array.shape[0] - buffer_thickness["bottom"]),
958
981
  plot_array.shape[1],
959
- buffer_thickness,
982
+ buffer_thickness["bottom"],
960
983
  facecolor=buffer_fill,
961
984
  hatch=buffer_hatch,
962
985
  )
963
986
  ax.add_patch(bottom_rect)
964
987
 
965
988
  left_rect = Rectangle(
966
- (0, buffer_thickness),
967
- buffer_thickness,
968
- 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"],
969
992
  facecolor=buffer_fill,
970
993
  hatch=buffer_hatch,
971
994
  )
@@ -973,11 +996,11 @@ class Device(BaseModel):
973
996
 
974
997
  right_rect = Rectangle(
975
998
  (
976
- plot_array.shape[1] - buffer_thickness,
977
- buffer_thickness,
999
+ plot_array.shape[1] - buffer_thickness["right"],
1000
+ buffer_thickness["top"],
978
1001
  ),
979
- buffer_thickness,
980
- plot_array.shape[0] - 2 * buffer_thickness,
1002
+ buffer_thickness["right"],
1003
+ plot_array.shape[0] - buffer_thickness["top"] - buffer_thickness["bottom"],
981
1004
  facecolor=buffer_fill,
982
1005
  hatch=buffer_hatch,
983
1006
  )
@@ -1015,7 +1038,9 @@ class Device(BaseModel):
1015
1038
  binarized_device_array = geometry.binarize(
1016
1039
  device_array=self.device_array, eta=eta, beta=beta
1017
1040
  )
1018
- 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
+ )
1019
1044
 
1020
1045
  def binarize_hard(self, eta: float = 0.5) -> "Device":
1021
1046
  """
@@ -1036,12 +1061,14 @@ class Device(BaseModel):
1036
1061
  binarized_device_array = geometry.binarize_hard(
1037
1062
  device_array=self.device_array, eta=eta
1038
1063
  )
1039
- 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
+ )
1040
1067
 
1041
1068
  def binarize_monte_carlo(
1042
1069
  self,
1043
1070
  threshold_noise_std: float = 2.0,
1044
- threshold_blur_std: float = 9.0,
1071
+ threshold_blur_std: float = 8.0,
1045
1072
  ) -> "Device":
1046
1073
  """
1047
1074
  Binarize the device geometry using a Monte Carlo approach with Gaussian
@@ -1104,9 +1131,10 @@ class Device(BaseModel):
1104
1131
 
1105
1132
  Parameters
1106
1133
  ----------
1107
- buffer_thickness : int, optional
1108
- The thickness of the buffer to leave around the empty space. Defaults to 0,
1109
- 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.
1110
1138
 
1111
1139
  Returns
1112
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
@@ -88,6 +88,33 @@ ANT_SiN = Fab(
88
88
  has_sidewall=True,
89
89
  )
90
90
 
91
+ generic_DUV_SOI = Fab(
92
+ foundry="generic",
93
+ process="DUV-SOI",
94
+ material="SOI",
95
+ technology="DUV",
96
+ thickness=220,
97
+ has_sidewall=True,
98
+ )
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
+
91
118
  ANT_NanoSOI_ANF1_d9 = Model(
92
119
  fab=ANT_NanoSOI,
93
120
  version="ANF1",
@@ -97,6 +124,15 @@ ANT_NanoSOI_ANF1_d9 = Model(
97
124
  tag="",
98
125
  )
99
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
+
100
136
  ANT_SiN_ANF1_d1 = Model(
101
137
  fab=ANT_SiN,
102
138
  version="ANF1",
@@ -106,9 +142,23 @@ ANT_SiN_ANF1_d1 = Model(
106
142
  tag="",
107
143
  )
108
144
 
145
+ generic_DUV_SOI_ANF1_d0 = Model(
146
+ fab=generic_DUV_SOI,
147
+ version="ANF1",
148
+ version_date=date(2024, 5, 6),
149
+ dataset="d0",
150
+ dataset_date=date(2024, 6, 30),
151
+ tag="",
152
+ )
153
+
109
154
  models = dict(
110
- 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,
111
158
  ANT_NanoSOI_ANF1_d9=ANT_NanoSOI_ANF1_d9,
159
+ ANT_NanoSOI_ANF1_d10=ANT_NanoSOI_ANF1_d10,
112
160
  ANT_SiN=ANT_SiN_ANF1_d1,
113
161
  ANT_SiN_ANF1_d1=ANT_SiN_ANF1_d1,
162
+ generic_DUV_SOI=generic_DUV_SOI_ANF1_d0,
163
+ generic_DUV_SOI_ANF1_d0=generic_DUV_SOI_ANF1_d0,
114
164
  )
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)