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 +5 -1
- prefab/compare.py +22 -20
- prefab/device.py +64 -36
- prefab/geometry.py +21 -15
- prefab/models.py +51 -1
- prefab/read.py +45 -22
- prefab/shapes.py +765 -0
- {prefab-1.0.1.dist-info → prefab-1.0.3.dist-info}/METADATA +7 -5
- prefab-1.0.3.dist-info/RECORD +12 -0
- {prefab-1.0.1.dist-info → prefab-1.0.3.dist-info}/WHEEL +1 -1
- prefab-1.0.1.dist-info/RECORD +0 -11
- {prefab-1.0.1.dist-info → prefab-1.0.3.dist-info}/licenses/LICENSE +0 -0
prefab/__init__.py
CHANGED
|
@@ -5,7 +5,9 @@ Usage:
|
|
|
5
5
|
import prefab as pf
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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=
|
|
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) =
|
|
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 =
|
|
467
|
-
|
|
468
|
-
|
|
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] -
|
|
940
|
-
plot_array.shape[0] -
|
|
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] -
|
|
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] -
|
|
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(
|
|
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(
|
|
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 =
|
|
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 :
|
|
1108
|
-
|
|
1109
|
-
|
|
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.
|
|
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:
|
|
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 :
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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=
|
|
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
|
|
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:
|
|
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 :
|
|
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
|
|
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
|
-
|
|
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
|
|
59
|
-
|
|
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
|
|
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
|
-
|
|
77
|
-
device_array = cv2.resize(
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
248
|
-
device_array =
|
|
249
|
-
|
|
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] :
|
|
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)
|