prefab 1.3.0__py3-none-any.whl → 1.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prefab/__init__.py +1 -1
- prefab/__main__.py +29 -24
- prefab/compare.py +49 -61
- prefab/device.py +163 -394
- prefab/geometry.py +102 -137
- prefab/models/__init__.py +57 -0
- prefab/models/base.py +63 -0
- prefab/models/evaluation.py +29 -0
- prefab/predict.py +68 -55
- prefab/py.typed +0 -0
- prefab/read.py +57 -303
- prefab/shapes.py +357 -187
- {prefab-1.3.0.dist-info → prefab-1.4.1.dist-info}/METADATA +21 -35
- prefab-1.4.1.dist-info/RECORD +17 -0
- {prefab-1.3.0.dist-info → prefab-1.4.1.dist-info}/WHEEL +1 -1
- prefab/models.py +0 -114
- prefab-1.3.0.dist-info/RECORD +0 -14
- {prefab-1.3.0.dist-info → prefab-1.4.1.dist-info}/entry_points.txt +0 -0
- {prefab-1.3.0.dist-info → prefab-1.4.1.dist-info}/licenses/LICENSE +0 -0
prefab/geometry.py
CHANGED
|
@@ -1,23 +1,31 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
Functions for manipulating and transforming device geometry arrays.
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
This module provides utilities for common geometric operations on numpy arrays
|
|
5
|
+
representing device geometries, including normalization, binarization, trimming,
|
|
6
|
+
padding, blurring, rotation, morphological operations (erosion/dilation), and
|
|
7
|
+
flattening. All functions operate on npt.NDArray[np.float64] arrays.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import cast
|
|
4
11
|
|
|
5
12
|
import cv2
|
|
6
13
|
import numpy as np
|
|
14
|
+
import numpy.typing as npt
|
|
7
15
|
|
|
8
16
|
|
|
9
|
-
def normalize(device_array: np.
|
|
17
|
+
def normalize(device_array: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
|
|
10
18
|
"""
|
|
11
19
|
Normalize the input ndarray to have values between 0 and 1.
|
|
12
20
|
|
|
13
21
|
Parameters
|
|
14
22
|
----------
|
|
15
|
-
device_array : np.
|
|
23
|
+
device_array : npt.NDArray[np.float64]
|
|
16
24
|
The input array to be normalized.
|
|
17
25
|
|
|
18
26
|
Returns
|
|
19
27
|
-------
|
|
20
|
-
np.
|
|
28
|
+
npt.NDArray[np.float64]
|
|
21
29
|
The normalized array with values scaled between 0 and 1.
|
|
22
30
|
"""
|
|
23
31
|
return (device_array - np.min(device_array)) / (
|
|
@@ -26,14 +34,14 @@ def normalize(device_array: np.ndarray) -> np.ndarray:
|
|
|
26
34
|
|
|
27
35
|
|
|
28
36
|
def binarize(
|
|
29
|
-
device_array: np.
|
|
30
|
-
) -> np.
|
|
37
|
+
device_array: npt.NDArray[np.float64], eta: float = 0.5, beta: float = np.inf
|
|
38
|
+
) -> npt.NDArray[np.float64]:
|
|
31
39
|
"""
|
|
32
40
|
Binarize the input ndarray based on a threshold and a scaling factor.
|
|
33
41
|
|
|
34
42
|
Parameters
|
|
35
43
|
----------
|
|
36
|
-
device_array : np.
|
|
44
|
+
device_array : npt.NDArray[np.float64]
|
|
37
45
|
The input array to be binarized.
|
|
38
46
|
eta : float
|
|
39
47
|
The threshold value for binarization. Defaults to 0.5.
|
|
@@ -43,15 +51,19 @@ def binarize(
|
|
|
43
51
|
|
|
44
52
|
Returns
|
|
45
53
|
-------
|
|
46
|
-
np.
|
|
54
|
+
npt.NDArray[np.float64]
|
|
47
55
|
The binarized array with elements scaled to 0 or 1.
|
|
48
56
|
"""
|
|
49
|
-
return (
|
|
50
|
-
|
|
57
|
+
return cast(
|
|
58
|
+
npt.NDArray[np.float64],
|
|
59
|
+
(np.tanh(beta * eta) + np.tanh(beta * (device_array - eta)))
|
|
60
|
+
/ (np.tanh(beta * eta) + np.tanh(beta * (1 - eta))),
|
|
51
61
|
)
|
|
52
62
|
|
|
53
63
|
|
|
54
|
-
def binarize_hard(
|
|
64
|
+
def binarize_hard(
|
|
65
|
+
device_array: npt.NDArray[np.float64], eta: float = 0.5
|
|
66
|
+
) -> npt.NDArray[np.float64]:
|
|
55
67
|
"""
|
|
56
68
|
Apply a hard threshold to binarize the input ndarray. The `binarize` function is
|
|
57
69
|
generally preferred for most use cases, but it can create numerical artifacts for
|
|
@@ -59,46 +71,24 @@ def binarize_hard(device_array: np.ndarray, eta: float = 0.5) -> np.ndarray:
|
|
|
59
71
|
|
|
60
72
|
Parameters
|
|
61
73
|
----------
|
|
62
|
-
device_array : np.
|
|
74
|
+
device_array : npt.NDArray[np.float64]
|
|
63
75
|
The input array to be binarized.
|
|
64
76
|
eta : float
|
|
65
77
|
The threshold value for binarization. Defaults to 0.5.
|
|
66
78
|
|
|
67
79
|
Returns
|
|
68
80
|
-------
|
|
69
|
-
np.
|
|
81
|
+
npt.NDArray[np.float64]
|
|
70
82
|
The binarized array with elements set to 0 or 1 based on the threshold.
|
|
71
83
|
"""
|
|
72
84
|
return np.where(device_array < eta, 0.0, 1.0)
|
|
73
85
|
|
|
74
86
|
|
|
75
|
-
def
|
|
76
|
-
|
|
77
|
-
Binarize a grayscale scanning electron microscope (SEM) image.
|
|
78
|
-
|
|
79
|
-
This function applies Otsu's method to automatically determine the optimal threshold
|
|
80
|
-
value for binarization of a grayscale SEM image.
|
|
81
|
-
|
|
82
|
-
Parameters
|
|
83
|
-
----------
|
|
84
|
-
sem_array : np.ndarray
|
|
85
|
-
The input SEM image array to be binarized.
|
|
86
|
-
|
|
87
|
-
Returns
|
|
88
|
-
-------
|
|
89
|
-
np.ndarray
|
|
90
|
-
The binarized SEM image array with elements scaled to 0 or 1.
|
|
91
|
-
"""
|
|
92
|
-
return cv2.threshold(
|
|
93
|
-
sem_array.astype("uint8"), 0, 1, cv2.THRESH_BINARY + cv2.THRESH_OTSU
|
|
94
|
-
)[1]
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def binarize_monte_carlo(
|
|
98
|
-
device_array: np.ndarray,
|
|
87
|
+
def binarize_with_roughness(
|
|
88
|
+
device_array: npt.NDArray[np.float64],
|
|
99
89
|
noise_magnitude: float,
|
|
100
90
|
blur_radius: float,
|
|
101
|
-
) -> np.
|
|
91
|
+
) -> npt.NDArray[np.float64]:
|
|
102
92
|
"""
|
|
103
93
|
Binarize the input ndarray using a dynamic thresholding approach to simulate surface
|
|
104
94
|
roughness.
|
|
@@ -116,7 +106,7 @@ def binarize_monte_carlo(
|
|
|
116
106
|
|
|
117
107
|
Parameters
|
|
118
108
|
----------
|
|
119
|
-
device_array : np.
|
|
109
|
+
device_array : npt.NDArray[np.float64]
|
|
120
110
|
The input array to be binarized.
|
|
121
111
|
noise_magnitude : float
|
|
122
112
|
The standard deviation of the Gaussian distribution used to generate noise for
|
|
@@ -127,35 +117,35 @@ def binarize_monte_carlo(
|
|
|
127
117
|
|
|
128
118
|
Returns
|
|
129
119
|
-------
|
|
130
|
-
np.
|
|
120
|
+
npt.NDArray[np.float64]
|
|
131
121
|
The binarized array with elements set to 0 or 1 based on the dynamically
|
|
132
122
|
generated threshold.
|
|
133
123
|
"""
|
|
134
124
|
device_array = np.squeeze(device_array)
|
|
135
|
-
|
|
136
|
-
base_threshold =
|
|
125
|
+
base_threshold_raw = float(np.random.normal(loc=0.5, scale=0.1))
|
|
126
|
+
base_threshold = max(0.2, min(base_threshold_raw, 0.8))
|
|
137
127
|
threshold_noise = np.random.normal(
|
|
138
128
|
loc=0, scale=noise_magnitude, size=device_array.shape
|
|
139
129
|
)
|
|
140
|
-
spatial_threshold = cv2.GaussianBlur(
|
|
130
|
+
spatial_threshold: npt.NDArray[np.float64] = cv2.GaussianBlur(
|
|
141
131
|
threshold_noise, ksize=(0, 0), sigmaX=blur_radius
|
|
142
|
-
)
|
|
143
|
-
dynamic_threshold = base_threshold + spatial_threshold
|
|
132
|
+
).astype(np.float64)
|
|
133
|
+
dynamic_threshold: npt.NDArray[np.float64] = base_threshold + spatial_threshold
|
|
144
134
|
binarized_array = np.where(device_array < dynamic_threshold, 0.0, 1.0)
|
|
145
135
|
binarized_array = np.expand_dims(binarized_array, axis=-1)
|
|
146
136
|
return binarized_array
|
|
147
137
|
|
|
148
138
|
|
|
149
139
|
def ternarize(
|
|
150
|
-
device_array: np.
|
|
151
|
-
) -> np.
|
|
140
|
+
device_array: npt.NDArray[np.float64], eta1: float = 1 / 3, eta2: float = 2 / 3
|
|
141
|
+
) -> npt.NDArray[np.float64]:
|
|
152
142
|
"""
|
|
153
143
|
Ternarize the input ndarray based on two thresholds. This function is useful for
|
|
154
144
|
flattened devices with angled sidewalls (i.e., three segments).
|
|
155
145
|
|
|
156
146
|
Parameters
|
|
157
147
|
----------
|
|
158
|
-
device_array : np.
|
|
148
|
+
device_array : npt.NDArray[np.float64]
|
|
159
149
|
The input array to be ternarized.
|
|
160
150
|
eta1 : float
|
|
161
151
|
The first threshold value for ternarization. Defaults to 1/3.
|
|
@@ -164,21 +154,22 @@ def ternarize(
|
|
|
164
154
|
|
|
165
155
|
Returns
|
|
166
156
|
-------
|
|
167
|
-
np.
|
|
157
|
+
npt.NDArray[np.float64]
|
|
168
158
|
The ternarized array with elements set to 0, 0.5, or 1 based on the thresholds.
|
|
169
159
|
"""
|
|
170
160
|
return np.where(device_array < eta1, 0.0, np.where(device_array >= eta2, 1.0, 0.5))
|
|
171
161
|
|
|
172
162
|
|
|
173
163
|
def trim(
|
|
174
|
-
device_array: np.
|
|
175
|
-
|
|
164
|
+
device_array: npt.NDArray[np.float64],
|
|
165
|
+
buffer_thickness: dict[str, int] | None = None,
|
|
166
|
+
) -> npt.NDArray[np.float64]:
|
|
176
167
|
"""
|
|
177
168
|
Trim the input ndarray by removing rows and columns that are completely zero.
|
|
178
169
|
|
|
179
170
|
Parameters
|
|
180
171
|
----------
|
|
181
|
-
device_array : np.
|
|
172
|
+
device_array : npt.NDArray[np.float64]
|
|
182
173
|
The input array to be trimmed.
|
|
183
174
|
buffer_thickness : Optional[dict[str, int]]
|
|
184
175
|
A dictionary specifying the thickness of the buffer to leave around the non-zero
|
|
@@ -187,22 +178,28 @@ def trim(
|
|
|
187
178
|
|
|
188
179
|
Returns
|
|
189
180
|
-------
|
|
190
|
-
np.
|
|
181
|
+
npt.NDArray[np.float64]
|
|
191
182
|
The trimmed array, potentially with a buffer around the non-zero elements.
|
|
192
183
|
"""
|
|
193
184
|
if buffer_thickness is None:
|
|
194
185
|
buffer_thickness = {"top": 0, "bottom": 0, "left": 0, "right": 0}
|
|
195
186
|
|
|
196
|
-
|
|
197
|
-
|
|
187
|
+
nonzero_indices = np.nonzero(np.squeeze(device_array))
|
|
188
|
+
nonzero_rows = nonzero_indices[0]
|
|
189
|
+
nonzero_cols = nonzero_indices[1]
|
|
190
|
+
|
|
191
|
+
row_min_val = int(nonzero_rows.min())
|
|
192
|
+
row_max_val = int(nonzero_rows.max())
|
|
193
|
+
col_min_val = int(nonzero_cols.min())
|
|
194
|
+
col_max_val = int(nonzero_cols.max())
|
|
195
|
+
|
|
196
|
+
row_min = max(row_min_val - buffer_thickness.get("top", 0), 0)
|
|
198
197
|
row_max = min(
|
|
199
|
-
|
|
200
|
-
device_array.shape[0],
|
|
198
|
+
row_max_val + buffer_thickness.get("bottom", 0) + 1, device_array.shape[0]
|
|
201
199
|
)
|
|
202
|
-
col_min = max(
|
|
200
|
+
col_min = max(col_min_val - buffer_thickness.get("left", 0), 0)
|
|
203
201
|
col_max = min(
|
|
204
|
-
|
|
205
|
-
device_array.shape[1],
|
|
202
|
+
col_max_val + buffer_thickness.get("right", 0) + 1, device_array.shape[1]
|
|
206
203
|
)
|
|
207
204
|
return device_array[
|
|
208
205
|
row_min:row_max,
|
|
@@ -210,20 +207,22 @@ def trim(
|
|
|
210
207
|
]
|
|
211
208
|
|
|
212
209
|
|
|
213
|
-
def pad(
|
|
210
|
+
def pad(
|
|
211
|
+
device_array: npt.NDArray[np.float64], pad_width: int
|
|
212
|
+
) -> npt.NDArray[np.float64]:
|
|
214
213
|
"""
|
|
215
214
|
Pad the input ndarray uniformly with a specified width on all sides.
|
|
216
215
|
|
|
217
216
|
Parameters
|
|
218
217
|
----------
|
|
219
|
-
device_array : np.
|
|
218
|
+
device_array : npt.NDArray[np.float64]
|
|
220
219
|
The input array to be padded.
|
|
221
220
|
pad_width : int
|
|
222
221
|
The number of pixels to pad on each side.
|
|
223
222
|
|
|
224
223
|
Returns
|
|
225
224
|
-------
|
|
226
|
-
np.
|
|
225
|
+
npt.NDArray[np.float64]
|
|
227
226
|
The padded array.
|
|
228
227
|
"""
|
|
229
228
|
return np.pad(
|
|
@@ -234,13 +233,15 @@ def pad(device_array: np.ndarray, pad_width: int) -> np.ndarray:
|
|
|
234
233
|
)
|
|
235
234
|
|
|
236
235
|
|
|
237
|
-
def blur(
|
|
236
|
+
def blur(
|
|
237
|
+
device_array: npt.NDArray[np.float64], sigma: float = 1.0
|
|
238
|
+
) -> npt.NDArray[np.float64]:
|
|
238
239
|
"""
|
|
239
240
|
Apply Gaussian blur to the input ndarray and normalize the result.
|
|
240
241
|
|
|
241
242
|
Parameters
|
|
242
243
|
----------
|
|
243
|
-
device_array : np.
|
|
244
|
+
device_array : npt.NDArray[np.float64]
|
|
244
245
|
The input array to be blurred.
|
|
245
246
|
sigma : float
|
|
246
247
|
The standard deviation for the Gaussian kernel. This controls the amount of
|
|
@@ -248,21 +249,28 @@ def blur(device_array: np.ndarray, sigma: float = 1.0) -> np.ndarray:
|
|
|
248
249
|
|
|
249
250
|
Returns
|
|
250
251
|
-------
|
|
251
|
-
np.
|
|
252
|
+
npt.NDArray[np.float64]
|
|
252
253
|
The blurred and normalized array with values scaled between 0 and 1.
|
|
253
254
|
"""
|
|
254
255
|
return np.expand_dims(
|
|
255
|
-
normalize(
|
|
256
|
+
normalize(
|
|
257
|
+
cv2.GaussianBlur(device_array, ksize=(0, 0), sigmaX=sigma).astype(
|
|
258
|
+
np.float64
|
|
259
|
+
)
|
|
260
|
+
),
|
|
261
|
+
axis=-1,
|
|
256
262
|
)
|
|
257
263
|
|
|
258
264
|
|
|
259
|
-
def rotate(
|
|
265
|
+
def rotate(
|
|
266
|
+
device_array: npt.NDArray[np.float64], angle: float
|
|
267
|
+
) -> npt.NDArray[np.float64]:
|
|
260
268
|
"""
|
|
261
269
|
Rotate the input ndarray by a given angle.
|
|
262
270
|
|
|
263
271
|
Parameters
|
|
264
272
|
----------
|
|
265
|
-
device_array : np.
|
|
273
|
+
device_array : npt.NDArray[np.float64]
|
|
266
274
|
The input array to be rotated.
|
|
267
275
|
angle : float
|
|
268
276
|
The angle of rotation in degrees. Positive values mean counter-clockwise
|
|
@@ -270,7 +278,7 @@ def rotate(device_array: np.ndarray, angle: float) -> np.ndarray:
|
|
|
270
278
|
|
|
271
279
|
Returns
|
|
272
280
|
-------
|
|
273
|
-
np.
|
|
281
|
+
npt.NDArray[np.float64]
|
|
274
282
|
The rotated array.
|
|
275
283
|
"""
|
|
276
284
|
center = (device_array.shape[1] / 2, device_array.shape[0] / 2)
|
|
@@ -280,116 +288,73 @@ def rotate(device_array: np.ndarray, angle: float) -> np.ndarray:
|
|
|
280
288
|
device_array,
|
|
281
289
|
M=rotation_matrix,
|
|
282
290
|
dsize=(device_array.shape[1], device_array.shape[0]),
|
|
283
|
-
),
|
|
291
|
+
).astype(np.float64),
|
|
284
292
|
axis=-1,
|
|
285
293
|
)
|
|
286
294
|
|
|
287
295
|
|
|
288
|
-
def erode(
|
|
296
|
+
def erode(
|
|
297
|
+
device_array: npt.NDArray[np.float64], kernel_size: int
|
|
298
|
+
) -> npt.NDArray[np.float64]:
|
|
289
299
|
"""
|
|
290
300
|
Erode the input ndarray using a specified kernel size and number of iterations.
|
|
291
301
|
|
|
292
302
|
Parameters
|
|
293
303
|
----------
|
|
294
|
-
device_array : np.
|
|
304
|
+
device_array : npt.NDArray[np.float64]
|
|
295
305
|
The input array representing the device geometry to be eroded.
|
|
296
306
|
kernel_size : int
|
|
297
307
|
The size of the kernel used for erosion.
|
|
298
308
|
|
|
299
309
|
Returns
|
|
300
310
|
-------
|
|
301
|
-
np.
|
|
311
|
+
npt.NDArray[np.float64]
|
|
302
312
|
The eroded array.
|
|
303
313
|
"""
|
|
304
314
|
kernel = np.ones((kernel_size, kernel_size), dtype=np.uint8)
|
|
305
|
-
return np.expand_dims(
|
|
315
|
+
return np.expand_dims(
|
|
316
|
+
cv2.erode(device_array, kernel=kernel).astype(np.float64), axis=-1
|
|
317
|
+
)
|
|
306
318
|
|
|
307
319
|
|
|
308
|
-
def dilate(
|
|
320
|
+
def dilate(
|
|
321
|
+
device_array: npt.NDArray[np.float64], kernel_size: int
|
|
322
|
+
) -> npt.NDArray[np.float64]:
|
|
309
323
|
"""
|
|
310
324
|
Dilate the input ndarray using a specified kernel size.
|
|
311
325
|
|
|
312
326
|
Parameters
|
|
313
327
|
----------
|
|
314
|
-
device_array : np.
|
|
328
|
+
device_array : npt.NDArray[np.float64]
|
|
315
329
|
The input array representing the device geometry to be dilated.
|
|
316
330
|
kernel_size : int
|
|
317
331
|
The size of the kernel used for dilation.
|
|
318
332
|
|
|
319
333
|
Returns
|
|
320
334
|
-------
|
|
321
|
-
np.
|
|
335
|
+
npt.NDArray[np.float64]
|
|
322
336
|
The dilated array.
|
|
323
337
|
"""
|
|
324
338
|
kernel = np.ones((kernel_size, kernel_size), dtype=np.uint8)
|
|
325
|
-
return np.expand_dims(
|
|
339
|
+
return np.expand_dims(
|
|
340
|
+
cv2.dilate(device_array, kernel=kernel).astype(np.float64), axis=-1
|
|
341
|
+
)
|
|
326
342
|
|
|
327
343
|
|
|
328
|
-
def flatten(device_array: np.
|
|
344
|
+
def flatten(device_array: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
|
|
329
345
|
"""
|
|
330
346
|
Flatten the input ndarray by summing the vertical layers and normalizing the result.
|
|
331
347
|
|
|
332
348
|
Parameters
|
|
333
349
|
----------
|
|
334
|
-
device_array : np.
|
|
350
|
+
device_array : npt.NDArray[np.float64]
|
|
335
351
|
The input array to be flattened.
|
|
336
352
|
|
|
337
353
|
Returns
|
|
338
354
|
-------
|
|
339
|
-
np.
|
|
355
|
+
npt.NDArray[np.float64]
|
|
340
356
|
The flattened array with values scaled between 0 and 1.
|
|
341
357
|
"""
|
|
342
|
-
return normalize(
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
def enforce_feature_size(
|
|
346
|
-
device_array: np.ndarray, min_feature_size: int, strel: str = "disk"
|
|
347
|
-
) -> np.ndarray:
|
|
348
|
-
"""
|
|
349
|
-
Enforce a minimum feature size on the device geometry.
|
|
350
|
-
|
|
351
|
-
This function applies morphological operations to ensure that all features in the
|
|
352
|
-
device geometry are at least the specified minimum size. It uses either a disk
|
|
353
|
-
or square structuring element for the operations.
|
|
354
|
-
|
|
355
|
-
Notes
|
|
356
|
-
-----
|
|
357
|
-
This function does not guarantee that the minimum feature size is enforced in all
|
|
358
|
-
cases. A better process is needed.
|
|
359
|
-
|
|
360
|
-
Parameters
|
|
361
|
-
----------
|
|
362
|
-
device_array : np.ndarray
|
|
363
|
-
The input array representing the device geometry.
|
|
364
|
-
min_feature_size : int
|
|
365
|
-
The minimum feature size to enforce, in nanometers.
|
|
366
|
-
strel : str
|
|
367
|
-
The type of structuring element to use. Can be either "disk" or "square".
|
|
368
|
-
Defaults to "disk".
|
|
369
|
-
|
|
370
|
-
Returns
|
|
371
|
-
-------
|
|
372
|
-
np.ndarray
|
|
373
|
-
The modified device array with enforced feature size.
|
|
374
|
-
|
|
375
|
-
Raises
|
|
376
|
-
------
|
|
377
|
-
ValueError
|
|
378
|
-
If an invalid structuring element type is specified.
|
|
379
|
-
"""
|
|
380
|
-
if strel == "disk":
|
|
381
|
-
kernel = cv2.getStructuringElement(
|
|
382
|
-
cv2.MORPH_ELLIPSE, (min_feature_size, min_feature_size)
|
|
383
|
-
)
|
|
384
|
-
elif strel == "square":
|
|
385
|
-
kernel = cv2.getStructuringElement(
|
|
386
|
-
cv2.MORPH_RECT, (min_feature_size, min_feature_size)
|
|
387
|
-
)
|
|
388
|
-
else:
|
|
389
|
-
raise ValueError(f"Invalid structuring element: {strel}")
|
|
390
|
-
|
|
391
|
-
device_array_2d = (device_array[:, :, 0] * 255).astype(np.uint8)
|
|
392
|
-
modified_geometry = cv2.morphologyEx(device_array_2d, cv2.MORPH_CLOSE, kernel)
|
|
393
|
-
modified_geometry = cv2.morphologyEx(modified_geometry, cv2.MORPH_OPEN, kernel)
|
|
394
|
-
|
|
395
|
-
return np.expand_dims(modified_geometry.astype(float) / 255, axis=-1)
|
|
358
|
+
return normalize(
|
|
359
|
+
cast(npt.NDArray[np.float64], np.sum(device_array, axis=-1, keepdims=True))
|
|
360
|
+
)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Fabrication process model definitions and configurations.
|
|
3
|
+
|
|
4
|
+
This module automatically discovers and loads all model definitions from
|
|
5
|
+
Python files in the models/ directory.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import importlib
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from .base import Fab, Model
|
|
12
|
+
|
|
13
|
+
models = {}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _load_models_from_module(module):
|
|
17
|
+
"""
|
|
18
|
+
Load all Model instances from a module into the models dict.
|
|
19
|
+
|
|
20
|
+
If the module defines a __models__ dict, use that for registration.
|
|
21
|
+
Otherwise, auto-register all Model instances using their variable names.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
module : module
|
|
26
|
+
Python module containing Model instances to register.
|
|
27
|
+
"""
|
|
28
|
+
if hasattr(module, "__models__"):
|
|
29
|
+
models_dict = getattr(module, "__models__")
|
|
30
|
+
for name, obj in models_dict.items():
|
|
31
|
+
if isinstance(obj, Model):
|
|
32
|
+
models[name] = obj
|
|
33
|
+
else:
|
|
34
|
+
for name, obj in vars(module).items():
|
|
35
|
+
if isinstance(obj, Model):
|
|
36
|
+
models[name] = obj
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
models_dir = Path(__file__).parent
|
|
40
|
+
|
|
41
|
+
for model_file in models_dir.glob("*.py"):
|
|
42
|
+
if model_file.stem in ("__init__", "base"):
|
|
43
|
+
continue
|
|
44
|
+
|
|
45
|
+
module_name = f".{model_file.stem}"
|
|
46
|
+
try:
|
|
47
|
+
module = importlib.import_module(module_name, package=__package__)
|
|
48
|
+
_load_models_from_module(module)
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"Fab",
|
|
55
|
+
"Model",
|
|
56
|
+
"models",
|
|
57
|
+
]
|
prefab/models/base.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base model definitions for fabrication processes.
|
|
3
|
+
|
|
4
|
+
This module defines the core data structures for representing nanofabrication
|
|
5
|
+
processes and their associated models.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from datetime import date
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Fab(BaseModel):
|
|
15
|
+
"""
|
|
16
|
+
Represents a fabrication process in the PreFab model library.
|
|
17
|
+
|
|
18
|
+
Attributes
|
|
19
|
+
----------
|
|
20
|
+
foundry : str
|
|
21
|
+
The name of the foundry where the fabrication process takes place.
|
|
22
|
+
process : str
|
|
23
|
+
The specific process used in the fabrication.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
foundry: str
|
|
27
|
+
process: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Model(BaseModel):
|
|
31
|
+
"""
|
|
32
|
+
Represents a model of a fabrication process including versioning and dataset detail.
|
|
33
|
+
|
|
34
|
+
Attributes
|
|
35
|
+
----------
|
|
36
|
+
fab : Fab
|
|
37
|
+
An instance of the Fab class representing the fabrication details.
|
|
38
|
+
version : str
|
|
39
|
+
The version identifier of the model.
|
|
40
|
+
version_date : date
|
|
41
|
+
The release date of this version of the model.
|
|
42
|
+
dataset : str
|
|
43
|
+
The identifier for the dataset used in this model.
|
|
44
|
+
dataset_date : date
|
|
45
|
+
The date when the dataset was last updated or released.
|
|
46
|
+
tag : str
|
|
47
|
+
An optional tag for additional categorization or notes.
|
|
48
|
+
|
|
49
|
+
Methods
|
|
50
|
+
-------
|
|
51
|
+
to_json()
|
|
52
|
+
Serializes the model instance to a JSON formatted string.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
fab: Fab
|
|
56
|
+
version: str
|
|
57
|
+
version_date: date
|
|
58
|
+
dataset: str
|
|
59
|
+
dataset_date: date
|
|
60
|
+
tag: str
|
|
61
|
+
|
|
62
|
+
def to_json(self):
|
|
63
|
+
return json.dumps(self.model_dump(), default=str)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base evaluation models.
|
|
3
|
+
|
|
4
|
+
Pre-configured model instances for common fabrication processes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import date
|
|
8
|
+
|
|
9
|
+
from .base import Fab, Model
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
Generic = Fab(
|
|
13
|
+
foundry="Generic",
|
|
14
|
+
process="SOI",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
Generic_SOI_ANF1_d0 = Model(
|
|
18
|
+
fab=Generic,
|
|
19
|
+
version="ANF1",
|
|
20
|
+
version_date=date(2025, 11, 7),
|
|
21
|
+
dataset="d0",
|
|
22
|
+
dataset_date=date(2025, 11, 7),
|
|
23
|
+
tag="",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Export models with user-facing names
|
|
27
|
+
__models__ = {
|
|
28
|
+
"Generic_SOI": Generic_SOI_ANF1_d0,
|
|
29
|
+
}
|