prefab 1.2.0__py3-none-any.whl → 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prefab/__init__.py +1 -1
- prefab/__main__.py +29 -24
- prefab/compare.py +49 -61
- prefab/device.py +163 -418
- prefab/geometry.py +102 -136
- prefab/models.py +19 -48
- prefab/predict.py +281 -249
- prefab/py.typed +0 -0
- prefab/read.py +57 -303
- prefab/shapes.py +357 -187
- {prefab-1.2.0.dist-info → prefab-1.4.0.dist-info}/METADATA +21 -35
- prefab-1.4.0.dist-info/RECORD +15 -0
- prefab-1.2.0.dist-info/RECORD +0 -14
- {prefab-1.2.0.dist-info → prefab-1.4.0.dist-info}/WHEEL +0 -0
- {prefab-1.2.0.dist-info → prefab-1.4.0.dist-info}/entry_points.txt +0 -0
- {prefab-1.2.0.dist-info → prefab-1.4.0.dist-info}/licenses/LICENSE +0 -0
prefab/device.py
CHANGED
|
@@ -1,26 +1,31 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
Core device representation and manipulation for photonic geometries.
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
This module provides the Device class for representing planar photonic device
|
|
5
|
+
geometries and performing operations on them. It includes buffer zone management,
|
|
6
|
+
prediction and correction of nanofabrication outcomes, visualization capabilities,
|
|
7
|
+
and export functions to various formats. The module also supports geometric
|
|
8
|
+
transformations (rotation, dilation, erosion), image processing operations (blur,
|
|
9
|
+
binarization), and comparison tools for analyzing fabrication deviations.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Any, Literal
|
|
4
13
|
|
|
5
14
|
import cv2
|
|
6
15
|
import gdstk
|
|
7
16
|
import matplotlib.pyplot as plt
|
|
8
17
|
import numpy as np
|
|
18
|
+
import numpy.typing as npt
|
|
9
19
|
from matplotlib.axes import Axes
|
|
10
20
|
from matplotlib.patches import Rectangle
|
|
11
21
|
from PIL import Image
|
|
12
|
-
from pydantic import BaseModel, Field,
|
|
22
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
13
23
|
from scipy.ndimage import distance_transform_edt
|
|
14
|
-
from skimage import measure
|
|
15
24
|
|
|
16
|
-
from . import
|
|
25
|
+
from . import geometry
|
|
17
26
|
from .models import Model
|
|
18
27
|
from .predict import predict_array
|
|
19
28
|
|
|
20
|
-
if TYPE_CHECKING:
|
|
21
|
-
import gdsfactory as gf
|
|
22
|
-
import tidy3d as td
|
|
23
|
-
|
|
24
29
|
Image.MAX_IMAGE_PIXELS = None
|
|
25
30
|
|
|
26
31
|
|
|
@@ -33,7 +38,7 @@ class BufferSpec(BaseModel):
|
|
|
33
38
|
providing extra space for device fabrication processes or for ensuring that the
|
|
34
39
|
device is isolated from surrounding structures.
|
|
35
40
|
|
|
36
|
-
|
|
41
|
+
Attributes
|
|
37
42
|
----------
|
|
38
43
|
mode : dict[str, str]
|
|
39
44
|
A dictionary that defines the buffer mode for each side of the device
|
|
@@ -53,8 +58,8 @@ class BufferSpec(BaseModel):
|
|
|
53
58
|
allowed values ('constant', 'edge', 'none'). Or if any of the thickness values
|
|
54
59
|
are negative.
|
|
55
60
|
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
Examples
|
|
62
|
+
--------
|
|
58
63
|
import prefab as pf
|
|
59
64
|
|
|
60
65
|
buffer_spec = pf.BufferSpec(
|
|
@@ -90,23 +95,25 @@ class BufferSpec(BaseModel):
|
|
|
90
95
|
}
|
|
91
96
|
)
|
|
92
97
|
|
|
93
|
-
@
|
|
94
|
-
|
|
98
|
+
@field_validator("mode", mode="before")
|
|
99
|
+
@classmethod
|
|
100
|
+
def check_mode(cls, v: dict[str, str]) -> dict[str, str]:
|
|
95
101
|
allowed_modes = ["constant", "edge", "none"]
|
|
96
102
|
if not all(mode in allowed_modes for mode in v.values()):
|
|
97
103
|
raise ValueError(f"Buffer mode must be one of {allowed_modes}, got '{v}'.")
|
|
98
104
|
return v
|
|
99
105
|
|
|
100
|
-
@
|
|
101
|
-
|
|
106
|
+
@field_validator("thickness")
|
|
107
|
+
@classmethod
|
|
108
|
+
def check_thickness(cls, v: dict[str, int]) -> dict[str, int]:
|
|
102
109
|
if not all(t >= 0 for t in v.values()):
|
|
103
110
|
raise ValueError("All thickness values must be greater than or equal to 0.")
|
|
104
111
|
return v
|
|
105
112
|
|
|
106
113
|
@model_validator(mode="after")
|
|
107
|
-
def check_none_thickness(
|
|
108
|
-
mode =
|
|
109
|
-
thickness =
|
|
114
|
+
def check_none_thickness(self) -> "BufferSpec":
|
|
115
|
+
mode = self.mode
|
|
116
|
+
thickness = self.thickness
|
|
110
117
|
for side in mode:
|
|
111
118
|
if mode[side] == "none" and thickness[side] != 0:
|
|
112
119
|
raise ValueError(
|
|
@@ -116,22 +123,22 @@ class BufferSpec(BaseModel):
|
|
|
116
123
|
raise ValueError(
|
|
117
124
|
f"Mode must be 'none' when thickness is 0 for {side} side"
|
|
118
125
|
)
|
|
119
|
-
return
|
|
126
|
+
return self
|
|
120
127
|
|
|
121
128
|
|
|
122
129
|
class Device(BaseModel):
|
|
123
|
-
device_array:
|
|
130
|
+
device_array: npt.NDArray[Any] = Field(...)
|
|
124
131
|
buffer_spec: BufferSpec = Field(default_factory=BufferSpec)
|
|
125
132
|
|
|
126
133
|
class Config:
|
|
127
|
-
arbitrary_types_allowed = True
|
|
134
|
+
arbitrary_types_allowed: bool = True
|
|
128
135
|
|
|
129
136
|
@property
|
|
130
137
|
def shape(self) -> tuple[int, ...]:
|
|
131
138
|
return tuple(self.device_array.shape)
|
|
132
139
|
|
|
133
140
|
def __init__(
|
|
134
|
-
self, device_array:
|
|
141
|
+
self, device_array: npt.NDArray[Any], buffer_spec: BufferSpec | None = None
|
|
135
142
|
):
|
|
136
143
|
"""
|
|
137
144
|
Represents the planar geometry of a photonic device design that will have its
|
|
@@ -174,10 +181,10 @@ class Device(BaseModel):
|
|
|
174
181
|
)
|
|
175
182
|
self._initial_processing()
|
|
176
183
|
|
|
177
|
-
def __call__(self, *args, **kwargs):
|
|
184
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Axes:
|
|
178
185
|
return self.plot(*args, **kwargs)
|
|
179
186
|
|
|
180
|
-
def _initial_processing(self):
|
|
187
|
+
def _initial_processing(self) -> None:
|
|
181
188
|
buffer_thickness = self.buffer_spec.thickness
|
|
182
189
|
buffer_mode = self.buffer_spec.mode
|
|
183
190
|
|
|
@@ -212,7 +219,8 @@ class Device(BaseModel):
|
|
|
212
219
|
self.device_array = np.expand_dims(self.device_array, axis=-1)
|
|
213
220
|
|
|
214
221
|
@model_validator(mode="before")
|
|
215
|
-
|
|
222
|
+
@classmethod
|
|
223
|
+
def check_device_array(cls, values: dict[str, Any]) -> dict[str, Any]:
|
|
216
224
|
device_array = values.get("device_array")
|
|
217
225
|
if not isinstance(device_array, np.ndarray):
|
|
218
226
|
raise ValueError("device_array must be a numpy ndarray.")
|
|
@@ -242,7 +250,6 @@ class Device(BaseModel):
|
|
|
242
250
|
self,
|
|
243
251
|
model: Model,
|
|
244
252
|
binarize: bool = False,
|
|
245
|
-
gpu: bool = False,
|
|
246
253
|
) -> "Device":
|
|
247
254
|
"""
|
|
248
255
|
Predict the nanofabrication outcome of the device using a specified model.
|
|
@@ -255,19 +262,14 @@ class Device(BaseModel):
|
|
|
255
262
|
----------
|
|
256
263
|
model : Model
|
|
257
264
|
The model to use for prediction, representing a specific fabrication process
|
|
258
|
-
and dataset. This model encapsulates details about the fabrication foundry
|
|
259
|
-
process,
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
is tailored to specific fabrication parameters.
|
|
265
|
+
and dataset. This model encapsulates details about the fabrication foundry
|
|
266
|
+
and process, as defined in `models.py`. Each model is associated with a
|
|
267
|
+
version and dataset that detail its creation and the data it was trained on,
|
|
268
|
+
ensuring the prediction is tailored to specific fabrication parameters.
|
|
263
269
|
binarize : bool
|
|
264
270
|
If True, the predicted device geometry will be binarized using a threshold
|
|
265
271
|
method. This is useful for converting probabilistic predictions into binary
|
|
266
272
|
geometries. Defaults to False.
|
|
267
|
-
gpu : bool
|
|
268
|
-
If True, the prediction will be performed on a GPU. Defaults to False.
|
|
269
|
-
Note: The GPU option has more overhead and will take longer for small
|
|
270
|
-
devices, but will be faster for larger devices.
|
|
271
273
|
|
|
272
274
|
Returns
|
|
273
275
|
-------
|
|
@@ -285,7 +287,6 @@ class Device(BaseModel):
|
|
|
285
287
|
model=model,
|
|
286
288
|
model_type="p",
|
|
287
289
|
binarize=binarize,
|
|
288
|
-
gpu=gpu,
|
|
289
290
|
)
|
|
290
291
|
return self.model_copy(update={"device_array": prediction_array})
|
|
291
292
|
|
|
@@ -293,7 +294,6 @@ class Device(BaseModel):
|
|
|
293
294
|
self,
|
|
294
295
|
model: Model,
|
|
295
296
|
binarize: bool = True,
|
|
296
|
-
gpu: bool = False,
|
|
297
297
|
) -> "Device":
|
|
298
298
|
"""
|
|
299
299
|
Correct the nanofabrication outcome of the device using a specified model.
|
|
@@ -308,19 +308,14 @@ class Device(BaseModel):
|
|
|
308
308
|
----------
|
|
309
309
|
model : Model
|
|
310
310
|
The model to use for correction, representing a specific fabrication process
|
|
311
|
-
and dataset. This model encapsulates details about the fabrication foundry
|
|
312
|
-
process,
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
is tailored to specific fabrication parameters.
|
|
311
|
+
and dataset. This model encapsulates details about the fabrication foundry
|
|
312
|
+
and process, as defined in `models.py`. Each model is associated with a
|
|
313
|
+
version and dataset that detail its creation and the data it was trained on,
|
|
314
|
+
ensuring the correction is tailored to specific fabrication parameters.
|
|
316
315
|
binarize : bool
|
|
317
316
|
If True, the corrected device geometry will be binarized using a threshold
|
|
318
317
|
method. This is useful for converting probabilistic corrections into binary
|
|
319
318
|
geometries. Defaults to True.
|
|
320
|
-
gpu : bool
|
|
321
|
-
If True, the prediction will be performed on a GPU. Defaults to False.
|
|
322
|
-
Note: The GPU option has more overhead and will take longer for small
|
|
323
|
-
devices, but will be faster for larger devices.
|
|
324
319
|
|
|
325
320
|
Returns
|
|
326
321
|
-------
|
|
@@ -338,114 +333,10 @@ class Device(BaseModel):
|
|
|
338
333
|
model=model,
|
|
339
334
|
model_type="c",
|
|
340
335
|
binarize=binarize,
|
|
341
|
-
gpu=gpu,
|
|
342
336
|
)
|
|
343
337
|
return self.model_copy(update={"device_array": correction_array})
|
|
344
338
|
|
|
345
|
-
def
|
|
346
|
-
self,
|
|
347
|
-
model: Model,
|
|
348
|
-
gpu: bool = False,
|
|
349
|
-
) -> "Device":
|
|
350
|
-
"""
|
|
351
|
-
Simulate the appearance of the device as if viewed under a scanning electron
|
|
352
|
-
microscope (SEM).
|
|
353
|
-
|
|
354
|
-
This method applies a specified machine learning model to transform the device
|
|
355
|
-
geometry into a style that resembles an SEM image. This can be useful for
|
|
356
|
-
visualizing how the device might appear under an SEM, which is often used for
|
|
357
|
-
inspecting the surface and composition of materials at high magnification.
|
|
358
|
-
|
|
359
|
-
Parameters
|
|
360
|
-
----------
|
|
361
|
-
model : Model
|
|
362
|
-
The model to use for SEMulation, representing a specific fabrication process
|
|
363
|
-
and dataset. This model encapsulates details about the fabrication foundry,
|
|
364
|
-
process, material, technology, thickness, and sidewall presence, as defined
|
|
365
|
-
in `models.py`. Each model is associated with a version and dataset that
|
|
366
|
-
detail its creation and the data it was trained on, ensuring the SEMulation
|
|
367
|
-
is tailored to specific fabrication parameters.
|
|
368
|
-
gpu : bool
|
|
369
|
-
If True, the prediction will be performed on a GPU. Defaults to False.
|
|
370
|
-
Note: The GPU option has more overhead and will take longer for small
|
|
371
|
-
devices, but will be faster for larger devices.
|
|
372
|
-
|
|
373
|
-
Notes
|
|
374
|
-
-----
|
|
375
|
-
The salt-and-pepper noise is added manually until the model is trained to
|
|
376
|
-
generate this noise (not a big priority).
|
|
377
|
-
|
|
378
|
-
Returns
|
|
379
|
-
-------
|
|
380
|
-
Device
|
|
381
|
-
A new instance of the Device class with its geometry transformed to simulate
|
|
382
|
-
an SEM image style.
|
|
383
|
-
|
|
384
|
-
Raises
|
|
385
|
-
------
|
|
386
|
-
RuntimeError
|
|
387
|
-
If the prediction service returns an error or if the response from the
|
|
388
|
-
service cannot be processed correctly.
|
|
389
|
-
"""
|
|
390
|
-
semulated_array = predict_array(
|
|
391
|
-
device_array=self.device_array,
|
|
392
|
-
model=model,
|
|
393
|
-
model_type="s",
|
|
394
|
-
binarize=False,
|
|
395
|
-
gpu=gpu,
|
|
396
|
-
)
|
|
397
|
-
semulated_array += np.random.normal(0, 0.03, semulated_array.shape)
|
|
398
|
-
return self.model_copy(update={"device_array": semulated_array})
|
|
399
|
-
|
|
400
|
-
def segment(
|
|
401
|
-
self,
|
|
402
|
-
model: Model,
|
|
403
|
-
gpu: bool = False,
|
|
404
|
-
) -> "Device":
|
|
405
|
-
"""
|
|
406
|
-
Segment a scanning electron microscope (SEM) image into a binary mask.
|
|
407
|
-
|
|
408
|
-
This method applies a specified machine learning model to transform a grayscale
|
|
409
|
-
SEM image into a binary mask, where 1 represents the device structure and 0
|
|
410
|
-
represents the background. This is useful for extracting the device geometry
|
|
411
|
-
from experimental SEM images for analysis or comparison with design intent.
|
|
412
|
-
|
|
413
|
-
Parameters
|
|
414
|
-
----------
|
|
415
|
-
model : Model
|
|
416
|
-
The model to use for segmentation, representing a specific fabrication
|
|
417
|
-
process and dataset. This model encapsulates details about the fabrication
|
|
418
|
-
foundry, process, material, technology, thickness, and sidewall presence, as
|
|
419
|
-
defined in `models.py`. Each model is associated with a version and dataset
|
|
420
|
-
that detail its creation and the data it was trained on, ensuring the
|
|
421
|
-
segmentation is tailored to specific fabrication parameters.
|
|
422
|
-
gpu : bool
|
|
423
|
-
If True, the prediction will be performed on a GPU. Defaults to False.
|
|
424
|
-
Note: The GPU option has more overhead and will take longer for small
|
|
425
|
-
devices, but will be faster for larger devices.
|
|
426
|
-
|
|
427
|
-
Returns
|
|
428
|
-
-------
|
|
429
|
-
Device
|
|
430
|
-
A new instance of the Device class with its geometry transformed into a
|
|
431
|
-
binary mask.
|
|
432
|
-
|
|
433
|
-
Raises
|
|
434
|
-
------
|
|
435
|
-
RuntimeError
|
|
436
|
-
If the prediction service returns an error or if the response from the
|
|
437
|
-
service cannot be processed correctly.
|
|
438
|
-
"""
|
|
439
|
-
segmented_array = predict_array(
|
|
440
|
-
device_array=self.normalize().device_array,
|
|
441
|
-
model=model,
|
|
442
|
-
model_type="b",
|
|
443
|
-
binarize=False,
|
|
444
|
-
gpu=gpu,
|
|
445
|
-
)
|
|
446
|
-
return self.model_copy(update={"device_array": segmented_array})
|
|
447
|
-
|
|
448
|
-
def to_ndarray(self) -> np.ndarray:
|
|
339
|
+
def to_ndarray(self) -> npt.NDArray[Any]:
|
|
449
340
|
"""
|
|
450
341
|
Converts the device geometry to an ndarray.
|
|
451
342
|
|
|
@@ -473,7 +364,11 @@ class Device(BaseModel):
|
|
|
473
364
|
]
|
|
474
365
|
return ndarray
|
|
475
366
|
|
|
476
|
-
def to_img(
|
|
367
|
+
def to_img(
|
|
368
|
+
self,
|
|
369
|
+
img_path: str = "prefab_device.png",
|
|
370
|
+
bounds: tuple[tuple[int, int], tuple[int, int]] | None = None,
|
|
371
|
+
) -> None:
|
|
477
372
|
"""
|
|
478
373
|
Exports the device geometry as an image file.
|
|
479
374
|
|
|
@@ -486,8 +381,49 @@ class Device(BaseModel):
|
|
|
486
381
|
img_path : str
|
|
487
382
|
The path where the image file will be saved. If not specified, the image is
|
|
488
383
|
saved as "prefab_device.png" in the current directory.
|
|
489
|
-
|
|
490
|
-
|
|
384
|
+
bounds : Optional[tuple[tuple[int, int], tuple[int, int]]]
|
|
385
|
+
Specifies the bounds for cropping the device geometry, formatted as
|
|
386
|
+
((min_x, min_y), (max_x, max_y)). Negative values count from the end
|
|
387
|
+
(e.g., -50 means 50 pixels from the edge). If 'max_x' or 'max_y' is set
|
|
388
|
+
to "end", it will be replaced with the corresponding dimension size of the
|
|
389
|
+
device array. If None, the entire device geometry is exported.
|
|
390
|
+
"""
|
|
391
|
+
device_array = self.flatten().to_ndarray()
|
|
392
|
+
|
|
393
|
+
if bounds is not None:
|
|
394
|
+
min_x, min_y = bounds[0]
|
|
395
|
+
max_x, max_y = bounds[1]
|
|
396
|
+
|
|
397
|
+
# Handle negative indices (count from end)
|
|
398
|
+
if min_x < 0:
|
|
399
|
+
min_x = device_array.shape[1] + min_x
|
|
400
|
+
if min_y < 0:
|
|
401
|
+
min_y = device_array.shape[0] + min_y
|
|
402
|
+
if max_x != "end" and max_x < 0:
|
|
403
|
+
max_x = device_array.shape[1] + max_x
|
|
404
|
+
if max_y != "end" and max_y < 0:
|
|
405
|
+
max_y = device_array.shape[0] + max_y
|
|
406
|
+
|
|
407
|
+
# Clamp to valid range
|
|
408
|
+
min_x = max(min_x, 0)
|
|
409
|
+
min_y = max(min_y, 0)
|
|
410
|
+
max_x = "end" if max_x == "end" else min(max_x, device_array.shape[1])
|
|
411
|
+
max_y = "end" if max_y == "end" else min(max_y, device_array.shape[0])
|
|
412
|
+
max_x = device_array.shape[1] if max_x == "end" else max_x
|
|
413
|
+
max_y = device_array.shape[0] if max_y == "end" else max_y
|
|
414
|
+
|
|
415
|
+
if min_x >= max_x or min_y >= max_y:
|
|
416
|
+
raise ValueError(
|
|
417
|
+
"Invalid bounds: min values must be less than max values. "
|
|
418
|
+
+ f"Got min_x={min_x}, max_x={max_x}, min_y={min_y}, max_y={max_y}"
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
device_array = device_array[
|
|
422
|
+
device_array.shape[0] - max_y : device_array.shape[0] - min_y,
|
|
423
|
+
min_x:max_x,
|
|
424
|
+
]
|
|
425
|
+
|
|
426
|
+
_ = cv2.imwrite(img_path, (255 * device_array).astype(np.uint8))
|
|
491
427
|
print(f"Saved Device image to '{img_path}'")
|
|
492
428
|
|
|
493
429
|
def to_gds(
|
|
@@ -497,7 +433,7 @@ class Device(BaseModel):
|
|
|
497
433
|
gds_layer: tuple[int, int] = (1, 0),
|
|
498
434
|
contour_approx_mode: int = 2,
|
|
499
435
|
origin: tuple[float, float] = (0.0, 0.0),
|
|
500
|
-
):
|
|
436
|
+
) -> None:
|
|
501
437
|
"""
|
|
502
438
|
Exports the device geometry as a GDSII file.
|
|
503
439
|
|
|
@@ -531,7 +467,7 @@ class Device(BaseModel):
|
|
|
531
467
|
)
|
|
532
468
|
print(f"Saving GDS to '{gds_path}'...")
|
|
533
469
|
gdstk_library = gdstk.Library()
|
|
534
|
-
gdstk_library.add(gdstk_cell)
|
|
470
|
+
_ = gdstk_library.add(gdstk_cell)
|
|
535
471
|
gdstk_library.write_gds(outfile=gds_path, max_points=8190)
|
|
536
472
|
|
|
537
473
|
def to_gdstk(
|
|
@@ -540,7 +476,7 @@ class Device(BaseModel):
|
|
|
540
476
|
gds_layer: tuple[int, int] = (1, 0),
|
|
541
477
|
contour_approx_mode: int = 2,
|
|
542
478
|
origin: tuple[float, float] = (0.0, 0.0),
|
|
543
|
-
):
|
|
479
|
+
) -> gdstk.Cell:
|
|
544
480
|
"""
|
|
545
481
|
Converts the device geometry to a GDSTK cell object.
|
|
546
482
|
|
|
@@ -652,85 +588,11 @@ class Device(BaseModel):
|
|
|
652
588
|
datatype=gds_layer[1],
|
|
653
589
|
)
|
|
654
590
|
for polygon in processed_polygons:
|
|
655
|
-
cell.add(polygon)
|
|
591
|
+
_ = cell.add(polygon)
|
|
656
592
|
|
|
657
593
|
return cell
|
|
658
594
|
|
|
659
|
-
def
|
|
660
|
-
"""
|
|
661
|
-
Convert the device geometry to a gdsfactory Component.
|
|
662
|
-
|
|
663
|
-
Returns
|
|
664
|
-
-------
|
|
665
|
-
gf.Component
|
|
666
|
-
A gdsfactory Component object representing the device geometry.
|
|
667
|
-
|
|
668
|
-
Raises
|
|
669
|
-
------
|
|
670
|
-
ImportError
|
|
671
|
-
If the gdsfactory package is not installed.
|
|
672
|
-
"""
|
|
673
|
-
try:
|
|
674
|
-
import gdsfactory as gf
|
|
675
|
-
except ImportError:
|
|
676
|
-
raise ImportError(
|
|
677
|
-
"The gdsfactory package is required to use this function; "
|
|
678
|
-
"try `pip install gdsfactory`."
|
|
679
|
-
) from None
|
|
680
|
-
|
|
681
|
-
device_array = np.rot90(self.to_ndarray(), k=-1)
|
|
682
|
-
return gf.read.from_np(device_array, nm_per_pixel=1)
|
|
683
|
-
|
|
684
|
-
def to_tidy3d(
|
|
685
|
-
self,
|
|
686
|
-
eps0: float,
|
|
687
|
-
thickness: float,
|
|
688
|
-
) -> "td.Structure":
|
|
689
|
-
"""
|
|
690
|
-
Convert the device geometry to a Tidy3D Structure.
|
|
691
|
-
|
|
692
|
-
Parameters
|
|
693
|
-
----------
|
|
694
|
-
eps0 : float
|
|
695
|
-
The permittivity value to assign to the device array.
|
|
696
|
-
thickness : float
|
|
697
|
-
The thickness of the device in the z-direction.
|
|
698
|
-
|
|
699
|
-
Returns
|
|
700
|
-
-------
|
|
701
|
-
td.Structure
|
|
702
|
-
A Tidy3D Structure object representing the device geometry.
|
|
703
|
-
|
|
704
|
-
Raises
|
|
705
|
-
------
|
|
706
|
-
ImportError
|
|
707
|
-
If the tidy3d package is not installed.
|
|
708
|
-
"""
|
|
709
|
-
try:
|
|
710
|
-
from tidy3d import Box, CustomMedium, SpatialDataArray, Structure, inf
|
|
711
|
-
except ImportError:
|
|
712
|
-
raise ImportError(
|
|
713
|
-
"The tidy3d package is required to use this function; "
|
|
714
|
-
"try `pip install tidy3d`."
|
|
715
|
-
) from None
|
|
716
|
-
|
|
717
|
-
X = np.linspace(-self.shape[1] / 2000, self.shape[1] / 2000, self.shape[1])
|
|
718
|
-
Y = np.linspace(-self.shape[0] / 2000, self.shape[0] / 2000, self.shape[0])
|
|
719
|
-
Z = np.array([0])
|
|
720
|
-
|
|
721
|
-
device_array = np.rot90(np.fliplr(self.device_array), k=1)
|
|
722
|
-
eps_array = np.where(device_array >= 1.0, eps0, device_array)
|
|
723
|
-
eps_array = np.where(eps_array < 1.0, 1.0, eps_array)
|
|
724
|
-
eps_dataset = SpatialDataArray(eps_array, coords=dict(x=X, y=Y, z=Z))
|
|
725
|
-
medium = CustomMedium.from_eps_raw(eps_dataset)
|
|
726
|
-
return Structure(
|
|
727
|
-
geometry=Box(center=(0, 0, 0), size=(inf, inf, thickness), attrs={}),
|
|
728
|
-
medium=medium,
|
|
729
|
-
name="device",
|
|
730
|
-
attrs={},
|
|
731
|
-
)
|
|
732
|
-
|
|
733
|
-
def to_3d(self, thickness_nm: int) -> np.ndarray:
|
|
595
|
+
def to_3d(self, thickness_nm: int) -> npt.NDArray[Any]:
|
|
734
596
|
"""
|
|
735
597
|
Convert the 2D device geometry into a 3D representation.
|
|
736
598
|
|
|
@@ -764,62 +626,33 @@ class Device(BaseModel):
|
|
|
764
626
|
layered_array[:, :, i] = dt_interp >= 0
|
|
765
627
|
return layered_array
|
|
766
628
|
|
|
767
|
-
def to_stl(self, thickness_nm: int, filename: str = "prefab_device.stl"):
|
|
768
|
-
"""
|
|
769
|
-
Export the device geometry as an STL file.
|
|
770
|
-
|
|
771
|
-
Parameters
|
|
772
|
-
----------
|
|
773
|
-
thickness_nm : int
|
|
774
|
-
The thickness of the 3D representation in nanometers.
|
|
775
|
-
filename : str
|
|
776
|
-
The name of the STL file to save. Defaults to "prefab_device.stl".
|
|
777
|
-
|
|
778
|
-
Raises
|
|
779
|
-
------
|
|
780
|
-
ValueError
|
|
781
|
-
If the thickness is not a positive integer.
|
|
782
|
-
ImportError
|
|
783
|
-
If the numpy-stl package is not installed.
|
|
784
|
-
"""
|
|
785
|
-
try:
|
|
786
|
-
from stl import mesh # type: ignore
|
|
787
|
-
except ImportError:
|
|
788
|
-
raise ImportError(
|
|
789
|
-
"The stl package is required to use this function; "
|
|
790
|
-
"try `pip install numpy-stl`."
|
|
791
|
-
) from None
|
|
792
|
-
|
|
793
|
-
if thickness_nm <= 0:
|
|
794
|
-
raise ValueError("Thickness must be a positive integer.")
|
|
795
|
-
|
|
796
|
-
layered_array = self.to_3d(thickness_nm)
|
|
797
|
-
layered_array = np.pad(
|
|
798
|
-
layered_array, ((0, 0), (0, 0), (10, 10)), mode="constant"
|
|
799
|
-
)
|
|
800
|
-
verts, faces, _, _ = measure.marching_cubes(layered_array, level=0.5)
|
|
801
|
-
cube = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype))
|
|
802
|
-
for i, f in enumerate(faces):
|
|
803
|
-
for j in range(3):
|
|
804
|
-
cube.vectors[i][j] = verts[f[j], :]
|
|
805
|
-
cube.save(filename)
|
|
806
|
-
print(f"Saved Device to '{filename}'")
|
|
807
|
-
|
|
808
629
|
def _plot_base(
|
|
809
630
|
self,
|
|
810
|
-
plot_array:
|
|
631
|
+
plot_array: npt.NDArray[Any],
|
|
811
632
|
show_buffer: bool,
|
|
812
|
-
bounds:
|
|
813
|
-
ax:
|
|
814
|
-
**kwargs,
|
|
633
|
+
bounds: tuple[tuple[int, int], tuple[int, int]] | None,
|
|
634
|
+
ax: Axes | None,
|
|
635
|
+
**kwargs: Any,
|
|
815
636
|
) -> tuple[plt.cm.ScalarMappable, Axes]:
|
|
816
637
|
if ax is None:
|
|
817
638
|
_, ax = plt.subplots()
|
|
818
|
-
ax.set_ylabel("y (nm)")
|
|
819
|
-
ax.set_xlabel("x (nm)")
|
|
639
|
+
_ = ax.set_ylabel("y (nm)")
|
|
640
|
+
_ = ax.set_xlabel("x (nm)")
|
|
820
641
|
|
|
821
642
|
min_x, min_y = (0, 0) if bounds is None else bounds[0]
|
|
822
643
|
max_x, max_y = plot_array.shape[::-1] if bounds is None else bounds[1]
|
|
644
|
+
|
|
645
|
+
# Handle negative indices (count from end)
|
|
646
|
+
if min_x < 0:
|
|
647
|
+
min_x = plot_array.shape[1] + min_x
|
|
648
|
+
if min_y < 0:
|
|
649
|
+
min_y = plot_array.shape[0] + min_y
|
|
650
|
+
if max_x != "end" and max_x < 0:
|
|
651
|
+
max_x = plot_array.shape[1] + max_x
|
|
652
|
+
if max_y != "end" and max_y < 0:
|
|
653
|
+
max_y = plot_array.shape[0] + max_y
|
|
654
|
+
|
|
655
|
+
# Clamp to valid range
|
|
823
656
|
min_x = max(min_x, 0)
|
|
824
657
|
min_y = max(min_y, 0)
|
|
825
658
|
max_x = "end" if max_x == "end" else min(max_x, plot_array.shape[1])
|
|
@@ -872,10 +705,10 @@ class Device(BaseModel):
|
|
|
872
705
|
def plot(
|
|
873
706
|
self,
|
|
874
707
|
show_buffer: bool = True,
|
|
875
|
-
bounds:
|
|
876
|
-
level:
|
|
877
|
-
ax:
|
|
878
|
-
**kwargs,
|
|
708
|
+
bounds: tuple[tuple[int, int], tuple[int, int]] | None = None,
|
|
709
|
+
level: int | None = None,
|
|
710
|
+
ax: Axes | None = None,
|
|
711
|
+
**kwargs: Any,
|
|
879
712
|
) -> Axes:
|
|
880
713
|
"""
|
|
881
714
|
Visualizes the device geometry.
|
|
@@ -891,9 +724,10 @@ class Device(BaseModel):
|
|
|
891
724
|
If True, visualizes the buffer zones around the device. Defaults to True.
|
|
892
725
|
bounds : Optional[tuple[tuple[int, int], tuple[int, int]]], optional
|
|
893
726
|
Specifies the bounds for zooming into the device geometry, formatted as
|
|
894
|
-
((min_x, min_y), (max_x, max_y)).
|
|
895
|
-
|
|
896
|
-
|
|
727
|
+
((min_x, min_y), (max_x, max_y)). Negative values count from the end
|
|
728
|
+
(e.g., -50 means 50 pixels from the edge). If 'max_x' or 'max_y' is set
|
|
729
|
+
to "end", it will be replaced with the corresponding dimension size of the
|
|
730
|
+
device array. If None, the entire device geometry is visualized.
|
|
897
731
|
level : int
|
|
898
732
|
The vertical layer to plot. If None, the device geometry is flattened.
|
|
899
733
|
Defaults to None.
|
|
@@ -924,14 +758,14 @@ class Device(BaseModel):
|
|
|
924
758
|
|
|
925
759
|
def plot_contour(
|
|
926
760
|
self,
|
|
927
|
-
linewidth:
|
|
928
|
-
# label:
|
|
761
|
+
linewidth: int | None = None,
|
|
762
|
+
# label: str | None = "Device contour",
|
|
929
763
|
show_buffer: bool = True,
|
|
930
|
-
bounds:
|
|
931
|
-
level:
|
|
932
|
-
ax:
|
|
933
|
-
**kwargs,
|
|
934
|
-
):
|
|
764
|
+
bounds: tuple[tuple[int, int], tuple[int, int]] | None = None,
|
|
765
|
+
level: int | None = None,
|
|
766
|
+
ax: Axes | None = None,
|
|
767
|
+
**kwargs: Any,
|
|
768
|
+
) -> Axes:
|
|
935
769
|
"""
|
|
936
770
|
Visualizes the contour of the device geometry.
|
|
937
771
|
|
|
@@ -950,9 +784,10 @@ class Device(BaseModel):
|
|
|
950
784
|
it is set to True.
|
|
951
785
|
bounds : Optional[tuple[tuple[int, int], tuple[int, int]]]
|
|
952
786
|
Specifies the bounds for zooming into the device geometry, formatted as
|
|
953
|
-
((min_x, min_y), (max_x, max_y)).
|
|
954
|
-
|
|
955
|
-
|
|
787
|
+
((min_x, min_y), (max_x, max_y)). Negative values count from the end
|
|
788
|
+
(e.g., -50 means 50 pixels from the edge). If 'max_x' or 'max_y' is set
|
|
789
|
+
to "end", it will be replaced with the corresponding dimension size of the
|
|
790
|
+
device array. If None, the entire device geometry is visualized.
|
|
956
791
|
level : int
|
|
957
792
|
The vertical layer to plot. If None, the device geometry is flattened.
|
|
958
793
|
Defaults to None.
|
|
@@ -974,8 +809,9 @@ class Device(BaseModel):
|
|
|
974
809
|
device_array = self.device_array[:, :, level]
|
|
975
810
|
|
|
976
811
|
kwargs.setdefault("cmap", "spring")
|
|
977
|
-
|
|
978
|
-
linewidth
|
|
812
|
+
linewidth_value = (
|
|
813
|
+
linewidth if linewidth is not None else device_array.shape[0] // 100
|
|
814
|
+
)
|
|
979
815
|
|
|
980
816
|
contours, _ = cv2.findContours(
|
|
981
817
|
geometry.binarize_hard(device_array).astype(np.uint8),
|
|
@@ -983,7 +819,7 @@ class Device(BaseModel):
|
|
|
983
819
|
cv2.CHAIN_APPROX_SIMPLE,
|
|
984
820
|
)
|
|
985
821
|
contour_array = np.zeros_like(device_array, dtype=np.uint8)
|
|
986
|
-
cv2.drawContours(contour_array, contours, -1, (255,),
|
|
822
|
+
_ = cv2.drawContours(contour_array, contours, -1, (255,), linewidth_value)
|
|
987
823
|
contour_array = np.ma.masked_equal(contour_array, 0)
|
|
988
824
|
|
|
989
825
|
_, ax = self._plot_base(
|
|
@@ -1001,11 +837,11 @@ class Device(BaseModel):
|
|
|
1001
837
|
def plot_uncertainty(
|
|
1002
838
|
self,
|
|
1003
839
|
show_buffer: bool = True,
|
|
1004
|
-
bounds:
|
|
1005
|
-
level:
|
|
1006
|
-
ax:
|
|
1007
|
-
**kwargs,
|
|
1008
|
-
):
|
|
840
|
+
bounds: tuple[tuple[int, int], tuple[int, int]] | None = None,
|
|
841
|
+
level: int | None = None,
|
|
842
|
+
ax: Axes | None = None,
|
|
843
|
+
**kwargs: Any,
|
|
844
|
+
) -> Axes:
|
|
1009
845
|
"""
|
|
1010
846
|
Visualizes the uncertainty in the edge positions of the predicted device.
|
|
1011
847
|
|
|
@@ -1023,9 +859,10 @@ class Device(BaseModel):
|
|
|
1023
859
|
default, it is set to True.
|
|
1024
860
|
bounds : Optional[tuple[tuple[int, int], tuple[int, int]]]
|
|
1025
861
|
Specifies the bounds for zooming into the device geometry, formatted as
|
|
1026
|
-
((min_x, min_y), (max_x, max_y)).
|
|
1027
|
-
|
|
1028
|
-
|
|
862
|
+
((min_x, min_y), (max_x, max_y)). Negative values count from the end
|
|
863
|
+
(e.g., -50 means 50 pixels from the edge). If 'max_x' or 'max_y' is set
|
|
864
|
+
to "end", it will be replaced with the corresponding dimension size of the
|
|
865
|
+
device array. If None, the entire device geometry is visualized.
|
|
1029
866
|
level : int
|
|
1030
867
|
The vertical layer to plot. If None, the device geometry is flattened.
|
|
1031
868
|
Defaults to None.
|
|
@@ -1064,10 +901,10 @@ class Device(BaseModel):
|
|
|
1064
901
|
self,
|
|
1065
902
|
ref_device: "Device",
|
|
1066
903
|
show_buffer: bool = True,
|
|
1067
|
-
bounds:
|
|
1068
|
-
level:
|
|
1069
|
-
ax:
|
|
1070
|
-
**kwargs,
|
|
904
|
+
bounds: tuple[tuple[int, int], tuple[int, int]] | None = None,
|
|
905
|
+
level: int | None = None,
|
|
906
|
+
ax: Axes | None = None,
|
|
907
|
+
**kwargs: Any,
|
|
1071
908
|
) -> Axes:
|
|
1072
909
|
"""
|
|
1073
910
|
Visualizes the comparison between the current device geometry and a reference
|
|
@@ -1085,9 +922,10 @@ class Device(BaseModel):
|
|
|
1085
922
|
If True, visualizes the buffer zones around the device. Defaults to True.
|
|
1086
923
|
bounds : Optional[tuple[tuple[int, int], tuple[int, int]]]
|
|
1087
924
|
Specifies the bounds for zooming into the device geometry, formatted as
|
|
1088
|
-
((min_x, min_y), (max_x, max_y)).
|
|
1089
|
-
|
|
1090
|
-
|
|
925
|
+
((min_x, min_y), (max_x, max_y)). Negative values count from the end
|
|
926
|
+
(e.g., -50 means 50 pixels from the edge). If 'max_x' or 'max_y' is set
|
|
927
|
+
to "end", it will be replaced with the corresponding dimension size of the
|
|
928
|
+
device array. If None, the entire device geometry is visualized.
|
|
1091
929
|
level : int
|
|
1092
930
|
The vertical layer to plot. If None, the device geometry is flattened.
|
|
1093
931
|
Defaults to None.
|
|
@@ -1123,7 +961,7 @@ class Device(BaseModel):
|
|
|
1123
961
|
cbar.set_label("Added (a.u.) Removed (a.u.)")
|
|
1124
962
|
return ax
|
|
1125
963
|
|
|
1126
|
-
def _add_buffer_visualization(self, ax: Axes):
|
|
964
|
+
def _add_buffer_visualization(self, ax: Axes) -> None:
|
|
1127
965
|
plot_array = self.device_array
|
|
1128
966
|
|
|
1129
967
|
buffer_thickness = self.buffer_spec.thickness
|
|
@@ -1138,7 +976,7 @@ class Device(BaseModel):
|
|
|
1138
976
|
edgecolor="black",
|
|
1139
977
|
linewidth=1,
|
|
1140
978
|
)
|
|
1141
|
-
ax.add_patch(mid_rect)
|
|
979
|
+
_ = ax.add_patch(mid_rect)
|
|
1142
980
|
|
|
1143
981
|
top_rect = Rectangle(
|
|
1144
982
|
(0, 0),
|
|
@@ -1147,7 +985,7 @@ class Device(BaseModel):
|
|
|
1147
985
|
facecolor=buffer_fill,
|
|
1148
986
|
hatch=buffer_hatch,
|
|
1149
987
|
)
|
|
1150
|
-
ax.add_patch(top_rect)
|
|
988
|
+
_ = ax.add_patch(top_rect)
|
|
1151
989
|
|
|
1152
990
|
bottom_rect = Rectangle(
|
|
1153
991
|
(0, plot_array.shape[0] - buffer_thickness["bottom"]),
|
|
@@ -1156,7 +994,7 @@ class Device(BaseModel):
|
|
|
1156
994
|
facecolor=buffer_fill,
|
|
1157
995
|
hatch=buffer_hatch,
|
|
1158
996
|
)
|
|
1159
|
-
ax.add_patch(bottom_rect)
|
|
997
|
+
_ = ax.add_patch(bottom_rect)
|
|
1160
998
|
|
|
1161
999
|
left_rect = Rectangle(
|
|
1162
1000
|
(0, buffer_thickness["top"]),
|
|
@@ -1165,7 +1003,7 @@ class Device(BaseModel):
|
|
|
1165
1003
|
facecolor=buffer_fill,
|
|
1166
1004
|
hatch=buffer_hatch,
|
|
1167
1005
|
)
|
|
1168
|
-
ax.add_patch(left_rect)
|
|
1006
|
+
_ = ax.add_patch(left_rect)
|
|
1169
1007
|
|
|
1170
1008
|
right_rect = Rectangle(
|
|
1171
1009
|
(
|
|
@@ -1177,7 +1015,7 @@ class Device(BaseModel):
|
|
|
1177
1015
|
facecolor=buffer_fill,
|
|
1178
1016
|
hatch=buffer_hatch,
|
|
1179
1017
|
)
|
|
1180
|
-
ax.add_patch(right_rect)
|
|
1018
|
+
_ = ax.add_patch(right_rect)
|
|
1181
1019
|
|
|
1182
1020
|
def normalize(self) -> "Device":
|
|
1183
1021
|
"""
|
|
@@ -1238,7 +1076,7 @@ class Device(BaseModel):
|
|
|
1238
1076
|
update={"device_array": binarized_device_array.astype(np.uint8)}
|
|
1239
1077
|
)
|
|
1240
1078
|
|
|
1241
|
-
def
|
|
1079
|
+
def binarize_with_roughness(
|
|
1242
1080
|
self,
|
|
1243
1081
|
noise_magnitude: float = 2.0,
|
|
1244
1082
|
blur_radius: float = 8.0,
|
|
@@ -1274,7 +1112,7 @@ class Device(BaseModel):
|
|
|
1274
1112
|
Device
|
|
1275
1113
|
A new instance of the Device with the binarized geometry.
|
|
1276
1114
|
"""
|
|
1277
|
-
binarized_device_array = geometry.
|
|
1115
|
+
binarized_device_array = geometry.binarize_with_roughness(
|
|
1278
1116
|
device_array=self.device_array,
|
|
1279
1117
|
noise_magnitude=noise_magnitude,
|
|
1280
1118
|
blur_radius=blur_radius,
|
|
@@ -1418,7 +1256,7 @@ class Device(BaseModel):
|
|
|
1418
1256
|
flattened_device_array = geometry.flatten(device_array=self.device_array)
|
|
1419
1257
|
return self.model_copy(update={"device_array": flattened_device_array})
|
|
1420
1258
|
|
|
1421
|
-
def get_uncertainty(self) ->
|
|
1259
|
+
def get_uncertainty(self) -> npt.NDArray[Any]:
|
|
1422
1260
|
"""
|
|
1423
1261
|
Calculate the uncertainty in the edge positions of the predicted device.
|
|
1424
1262
|
|
|
@@ -1434,96 +1272,3 @@ class Device(BaseModel):
|
|
|
1434
1272
|
with higher values indicating greater uncertainty.
|
|
1435
1273
|
"""
|
|
1436
1274
|
return 1 - 2 * np.abs(0.5 - self.device_array)
|
|
1437
|
-
|
|
1438
|
-
def enforce_feature_size(
|
|
1439
|
-
self, min_feature_size: int, strel: str = "disk"
|
|
1440
|
-
) -> "Device":
|
|
1441
|
-
"""
|
|
1442
|
-
Enforce a minimum feature size on the device geometry.
|
|
1443
|
-
|
|
1444
|
-
This method applies morphological operations to ensure that all features in the
|
|
1445
|
-
device geometry are at least the specified minimum size. It uses either a disk
|
|
1446
|
-
or square structuring element for the operations.
|
|
1447
|
-
|
|
1448
|
-
Notes
|
|
1449
|
-
-----
|
|
1450
|
-
This function does not guarantee that the minimum feature size is enforced in
|
|
1451
|
-
all cases. A better process is needed.
|
|
1452
|
-
|
|
1453
|
-
Parameters
|
|
1454
|
-
----------
|
|
1455
|
-
min_feature_size : int
|
|
1456
|
-
The minimum feature size to enforce, in nanometers.
|
|
1457
|
-
strel : str
|
|
1458
|
-
The type of structuring element to use. Can be either "disk" or "square".
|
|
1459
|
-
Defaults to "disk".
|
|
1460
|
-
|
|
1461
|
-
Returns
|
|
1462
|
-
-------
|
|
1463
|
-
Device
|
|
1464
|
-
A new instance of the Device with the modified geometry.
|
|
1465
|
-
|
|
1466
|
-
Raises
|
|
1467
|
-
------
|
|
1468
|
-
ValueError
|
|
1469
|
-
If an invalid structuring element type is specified.
|
|
1470
|
-
"""
|
|
1471
|
-
modified_geometry = geometry.enforce_feature_size(
|
|
1472
|
-
device_array=self.device_array,
|
|
1473
|
-
min_feature_size=min_feature_size,
|
|
1474
|
-
strel=strel,
|
|
1475
|
-
)
|
|
1476
|
-
return self.model_copy(update={"device_array": modified_geometry})
|
|
1477
|
-
|
|
1478
|
-
def check_feature_size(self, min_feature_size: int, strel: str = "disk"):
|
|
1479
|
-
"""
|
|
1480
|
-
Check and visualize the effect of enforcing a minimum feature size on the device
|
|
1481
|
-
geometry.
|
|
1482
|
-
|
|
1483
|
-
This method enforces a minimum feature size on the device geometry using the
|
|
1484
|
-
specified structuring element, compares the modified geometry with the original,
|
|
1485
|
-
and plots the differences. It also calculates and prints the Hamming distance
|
|
1486
|
-
between the original and modified geometries, providing a measure of the changes
|
|
1487
|
-
introduced by the feature size enforcement.
|
|
1488
|
-
|
|
1489
|
-
Notes
|
|
1490
|
-
-----
|
|
1491
|
-
This is not a design-rule-checking function, but it can be useful for quick
|
|
1492
|
-
checks.
|
|
1493
|
-
|
|
1494
|
-
Parameters
|
|
1495
|
-
----------
|
|
1496
|
-
min_feature_size : int
|
|
1497
|
-
The minimum feature size to enforce, in nanometers.
|
|
1498
|
-
strel : str
|
|
1499
|
-
The type of structuring element to use. Can be either "disk" or "square".
|
|
1500
|
-
Defaults to "disk".
|
|
1501
|
-
|
|
1502
|
-
Raises
|
|
1503
|
-
------
|
|
1504
|
-
ValueError
|
|
1505
|
-
If an invalid structuring element type is specified or if min_feature_size
|
|
1506
|
-
is not a positive integer.
|
|
1507
|
-
"""
|
|
1508
|
-
if min_feature_size <= 0:
|
|
1509
|
-
raise ValueError("min_feature_size must be a positive integer.")
|
|
1510
|
-
|
|
1511
|
-
enforced_device = self.enforce_feature_size(min_feature_size, strel)
|
|
1512
|
-
|
|
1513
|
-
difference = np.abs(
|
|
1514
|
-
enforced_device.device_array[:, :, 0] - self.device_array[:, :, 0]
|
|
1515
|
-
)
|
|
1516
|
-
_, ax = self._plot_base(
|
|
1517
|
-
plot_array=difference,
|
|
1518
|
-
show_buffer=False,
|
|
1519
|
-
ax=None,
|
|
1520
|
-
bounds=None,
|
|
1521
|
-
cmap="jet",
|
|
1522
|
-
)
|
|
1523
|
-
|
|
1524
|
-
hamming_distance = compare.hamming_distance(self, enforced_device)
|
|
1525
|
-
print(
|
|
1526
|
-
f"Feature size check with minimum size {min_feature_size} "
|
|
1527
|
-
f"using '{strel}' structuring element resulted in a Hamming "
|
|
1528
|
-
f"distance of: {hamming_distance}"
|
|
1529
|
-
)
|