prefab 1.3.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 -394
- prefab/geometry.py +102 -137
- prefab/models.py +19 -48
- 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.0.dist-info}/METADATA +21 -35
- prefab-1.4.0.dist-info/RECORD +15 -0
- prefab-1.3.0.dist-info/RECORD +0 -14
- {prefab-1.3.0.dist-info → prefab-1.4.0.dist-info}/WHEEL +0 -0
- {prefab-1.3.0.dist-info → prefab-1.4.0.dist-info}/entry_points.txt +0 -0
- {prefab-1.3.0.dist-info → prefab-1.4.0.dist-info}/licenses/LICENSE +0 -0
prefab/shapes.py
CHANGED
|
@@ -1,14 +1,98 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
Shape generation functions for creating test device geometries.
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
Provides functions for creating common shapes including rectangles, circles,
|
|
5
|
+
gratings, polygons, and grid patterns. All functions return Device objects.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
4
9
|
|
|
5
10
|
import numpy as np
|
|
11
|
+
import numpy.typing as npt
|
|
6
12
|
from skimage.draw import polygon
|
|
7
13
|
|
|
8
14
|
from .device import Device
|
|
9
15
|
|
|
10
16
|
|
|
11
|
-
def
|
|
17
|
+
def _default_height(height: int | None, width: int) -> int:
|
|
18
|
+
"""Return height if provided, otherwise default to width for square shapes."""
|
|
19
|
+
return height if height is not None else width
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _create_ellipse_mask(
|
|
23
|
+
width: int, height: int
|
|
24
|
+
) -> tuple[npt.NDArray[np.bool_], int, int]:
|
|
25
|
+
"""
|
|
26
|
+
Create an ellipse mask for the given width and height.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
width : int
|
|
31
|
+
Width of the ellipse.
|
|
32
|
+
height : int
|
|
33
|
+
Height of the ellipse.
|
|
34
|
+
|
|
35
|
+
Returns
|
|
36
|
+
-------
|
|
37
|
+
tuple[NDArray[np.bool_], int, int]
|
|
38
|
+
Boolean mask array, radius_x, and radius_y.
|
|
39
|
+
"""
|
|
40
|
+
radius_x = width // 2
|
|
41
|
+
radius_y = height // 2
|
|
42
|
+
y, x = np.ogrid[-radius_y:radius_y, -radius_x:radius_x]
|
|
43
|
+
mask = (x**2 / radius_x**2) + (y**2 / radius_y**2) <= 1
|
|
44
|
+
return mask, radius_x, radius_y
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _create_circular_mask(radius: int) -> npt.NDArray[np.bool_]:
|
|
48
|
+
"""
|
|
49
|
+
Create a circular mask for the given radius.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
radius : int
|
|
54
|
+
Radius of the circle.
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
NDArray[np.bool_]
|
|
59
|
+
Boolean mask array with circular region.
|
|
60
|
+
"""
|
|
61
|
+
y, x = np.ogrid[-radius:radius, -radius:radius]
|
|
62
|
+
return x**2 + y**2 <= radius**2
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _place_disk_in_grid(
|
|
66
|
+
grid: npt.NDArray[np.floating[Any]],
|
|
67
|
+
center_y: int,
|
|
68
|
+
center_x: int,
|
|
69
|
+
radius: int,
|
|
70
|
+
value: float = 1.0,
|
|
71
|
+
) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Place a disk in a grid at the specified center position.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
grid : NDArray
|
|
78
|
+
The grid array to modify in-place.
|
|
79
|
+
center_y : int
|
|
80
|
+
Y-coordinate of disk center.
|
|
81
|
+
center_x : int
|
|
82
|
+
X-coordinate of disk center.
|
|
83
|
+
radius : int
|
|
84
|
+
Radius of the disk.
|
|
85
|
+
value : float
|
|
86
|
+
Value to set for the disk pixels (1.0 for disks, 0.0 for holes).
|
|
87
|
+
"""
|
|
88
|
+
mask = _create_circular_mask(radius)
|
|
89
|
+
grid[
|
|
90
|
+
center_y - radius : center_y + radius,
|
|
91
|
+
center_x - radius : center_x + radius,
|
|
92
|
+
][mask] = value
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def rectangle(width: int = 200, height: int | None = None, **kwargs: Any) -> Device:
|
|
12
96
|
"""
|
|
13
97
|
Create a Device object with a rectangular shape.
|
|
14
98
|
|
|
@@ -16,7 +100,7 @@ def rectangle(width: int = 200, height: Optional[int] = None, **kwargs) -> Devic
|
|
|
16
100
|
----------
|
|
17
101
|
width : int
|
|
18
102
|
The width of the rectangle. Defaults to 200.
|
|
19
|
-
height :
|
|
103
|
+
height : int | None
|
|
20
104
|
The height of the rectangle. Defaults to the value of width if None.
|
|
21
105
|
**kwargs : dict
|
|
22
106
|
Additional keyword arguments to be passed to the Device constructor.
|
|
@@ -26,14 +110,13 @@ def rectangle(width: int = 200, height: Optional[int] = None, **kwargs) -> Devic
|
|
|
26
110
|
Device
|
|
27
111
|
A Device object containing the rectangular shape.
|
|
28
112
|
"""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return Device(device_array=rectangle, **kwargs)
|
|
113
|
+
height = _default_height(height, width)
|
|
114
|
+
shape_array = np.ones((height, width))
|
|
115
|
+
return Device(device_array=shape_array, **kwargs)
|
|
33
116
|
|
|
34
117
|
|
|
35
118
|
def window(
|
|
36
|
-
width: int = 200, height:
|
|
119
|
+
width: int = 200, height: int | None = None, border_width: int = 60, **kwargs: Any
|
|
37
120
|
) -> Device:
|
|
38
121
|
"""
|
|
39
122
|
Create a Device object with a window shape (hollow rectangle).
|
|
@@ -42,7 +125,7 @@ def window(
|
|
|
42
125
|
----------
|
|
43
126
|
width : int
|
|
44
127
|
The overall width of the window. Defaults to 200.
|
|
45
|
-
height :
|
|
128
|
+
height : int | None
|
|
46
129
|
The overall height of the window. Defaults to the value of width.
|
|
47
130
|
border_width : int
|
|
48
131
|
The width of the window border. Defaults to 60.
|
|
@@ -54,18 +137,17 @@ def window(
|
|
|
54
137
|
Device
|
|
55
138
|
A Device object containing the window shape.
|
|
56
139
|
"""
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return Device(device_array=window, **kwargs)
|
|
140
|
+
height = _default_height(height, width)
|
|
141
|
+
shape_array = np.zeros((height, width))
|
|
142
|
+
shape_array[:border_width, :] = 1
|
|
143
|
+
shape_array[-border_width:, :] = 1
|
|
144
|
+
shape_array[:, :border_width] = 1
|
|
145
|
+
shape_array[:, -border_width:] = 1
|
|
146
|
+
return Device(device_array=shape_array, **kwargs)
|
|
65
147
|
|
|
66
148
|
|
|
67
149
|
def cross(
|
|
68
|
-
width: int = 200, height:
|
|
150
|
+
width: int = 200, height: int | None = None, arm_width: int = 60, **kwargs: Any
|
|
69
151
|
) -> Device:
|
|
70
152
|
"""
|
|
71
153
|
Create a Device object with a cross shape.
|
|
@@ -74,7 +156,7 @@ def cross(
|
|
|
74
156
|
----------
|
|
75
157
|
width : int
|
|
76
158
|
The overall width of the cross. Defaults to 200.
|
|
77
|
-
height :
|
|
159
|
+
height : int | None
|
|
78
160
|
The overall height of the cross. Defaults to the value of width.
|
|
79
161
|
arm_width : int
|
|
80
162
|
The width of the cross arms. Defaults to 60.
|
|
@@ -86,19 +168,18 @@ def cross(
|
|
|
86
168
|
Device
|
|
87
169
|
A Device object containing the cross shape.
|
|
88
170
|
"""
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
cross = np.zeros((height, width))
|
|
171
|
+
height = _default_height(height, width)
|
|
172
|
+
shape_array = np.zeros((height, width))
|
|
92
173
|
center_x = width // 2
|
|
93
174
|
center_y = height // 2
|
|
94
175
|
half_arm_width = arm_width // 2
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return Device(device_array=
|
|
176
|
+
shape_array[center_y - half_arm_width : center_y + half_arm_width + 1, :] = 1
|
|
177
|
+
shape_array[:, center_x - half_arm_width : center_x + half_arm_width + 1] = 1
|
|
178
|
+
return Device(device_array=shape_array, **kwargs)
|
|
98
179
|
|
|
99
180
|
|
|
100
181
|
def target(
|
|
101
|
-
width: int = 200, height:
|
|
182
|
+
width: int = 200, height: int | None = None, arm_width: int = 60, **kwargs: Any
|
|
102
183
|
) -> Device:
|
|
103
184
|
"""
|
|
104
185
|
Create a Device object with a target shape (cross with center removed).
|
|
@@ -107,7 +188,7 @@ def target(
|
|
|
107
188
|
----------
|
|
108
189
|
width : int
|
|
109
190
|
The overall width of the target. Defaults to 200.
|
|
110
|
-
height :
|
|
191
|
+
height : int | None
|
|
111
192
|
The overall height of the target. Defaults to the value of width.
|
|
112
193
|
arm_width : int
|
|
113
194
|
The width of the target arms. Defaults to 60.
|
|
@@ -119,22 +200,21 @@ def target(
|
|
|
119
200
|
Device
|
|
120
201
|
A Device object containing the target shape.
|
|
121
202
|
"""
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
target = np.zeros((height, width))
|
|
203
|
+
height = _default_height(height, width)
|
|
204
|
+
shape_array = np.zeros((height, width))
|
|
125
205
|
center_x = width // 2
|
|
126
206
|
center_y = height // 2
|
|
127
207
|
half_arm_width = arm_width // 2
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
208
|
+
shape_array[center_y - half_arm_width : center_y + half_arm_width + 1, :] = 1
|
|
209
|
+
shape_array[:, center_x - half_arm_width : center_x + half_arm_width + 1] = 1
|
|
210
|
+
shape_array[
|
|
131
211
|
center_y - half_arm_width : center_y + half_arm_width + 1,
|
|
132
212
|
center_x - half_arm_width : center_x + half_arm_width + 1,
|
|
133
213
|
] = 0
|
|
134
|
-
return Device(device_array=
|
|
214
|
+
return Device(device_array=shape_array, **kwargs)
|
|
135
215
|
|
|
136
216
|
|
|
137
|
-
def disk(width: int = 200, height:
|
|
217
|
+
def disk(width: int = 200, height: int | None = None, **kwargs: Any) -> Device:
|
|
138
218
|
"""
|
|
139
219
|
Create a Device object with an elliptical shape.
|
|
140
220
|
|
|
@@ -142,7 +222,7 @@ def disk(width: int = 200, height: Optional[int] = None, **kwargs) -> Device:
|
|
|
142
222
|
----------
|
|
143
223
|
width : int
|
|
144
224
|
The width of the ellipse. Defaults to 200.
|
|
145
|
-
height :
|
|
225
|
+
height : int | None
|
|
146
226
|
The height of the ellipse. Defaults to the value of width.
|
|
147
227
|
**kwargs : dict
|
|
148
228
|
Additional keyword arguments to be passed to the Device constructor.
|
|
@@ -152,19 +232,15 @@ def disk(width: int = 200, height: Optional[int] = None, **kwargs) -> Device:
|
|
|
152
232
|
Device
|
|
153
233
|
A Device object containing the elliptical shape.
|
|
154
234
|
"""
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
mask = (x**2 / radius_x**2) + (y**2 / radius_y**2) <= 1
|
|
161
|
-
ellipse = np.zeros((height, width))
|
|
162
|
-
ellipse[mask] = 1
|
|
163
|
-
return Device(device_array=ellipse, **kwargs)
|
|
235
|
+
height = _default_height(height, width)
|
|
236
|
+
mask, _, _ = _create_ellipse_mask(width, height)
|
|
237
|
+
shape_array = np.zeros((height, width))
|
|
238
|
+
shape_array[mask] = 1
|
|
239
|
+
return Device(device_array=shape_array, **kwargs)
|
|
164
240
|
|
|
165
241
|
|
|
166
242
|
def ring(
|
|
167
|
-
width: int = 200, height:
|
|
243
|
+
width: int = 200, height: int | None = None, border_width: int = 60, **kwargs: Any
|
|
168
244
|
) -> Device:
|
|
169
245
|
"""
|
|
170
246
|
Create a Device object with a ring shape (hollow ellipse).
|
|
@@ -173,7 +249,7 @@ def ring(
|
|
|
173
249
|
----------
|
|
174
250
|
width : int
|
|
175
251
|
The overall width of the ring. Defaults to 200.
|
|
176
|
-
height :
|
|
252
|
+
height : int | None
|
|
177
253
|
The overall height of the ring. Defaults to the value of width.
|
|
178
254
|
border_width : int
|
|
179
255
|
The width of the ring border. Defaults to 60.
|
|
@@ -185,22 +261,26 @@ def ring(
|
|
|
185
261
|
Device
|
|
186
262
|
A Device object containing the ring shape.
|
|
187
263
|
"""
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
264
|
+
height = _default_height(height, width)
|
|
265
|
+
outer_mask, radius_x, radius_y = _create_ellipse_mask(width, height)
|
|
266
|
+
|
|
267
|
+
# Create inner ellipse mask
|
|
192
268
|
inner_radius_x = radius_x - border_width
|
|
193
269
|
inner_radius_y = radius_y - border_width
|
|
194
270
|
y, x = np.ogrid[-radius_y:radius_y, -radius_x:radius_x]
|
|
195
|
-
outer_mask = x**2 / radius_x**2 + y**2 / radius_y**2 <= 1
|
|
196
271
|
inner_mask = x**2 / inner_radius_x**2 + y**2 / inner_radius_y**2 <= 1
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
272
|
+
|
|
273
|
+
shape_array = np.zeros((height, width))
|
|
274
|
+
shape_array[outer_mask & ~inner_mask] = 1
|
|
275
|
+
return Device(device_array=shape_array, **kwargs)
|
|
200
276
|
|
|
201
277
|
|
|
202
278
|
def disk_wavy(
|
|
203
|
-
width: int = 200,
|
|
279
|
+
width: int = 200,
|
|
280
|
+
height: int | None = None,
|
|
281
|
+
wave_amplitude: float = 10,
|
|
282
|
+
wave_frequency: float = 10,
|
|
283
|
+
**kwargs: Any,
|
|
204
284
|
) -> Device:
|
|
205
285
|
"""
|
|
206
286
|
Create a Device object with a circular shape with wavy edges.
|
|
@@ -208,7 +288,9 @@ def disk_wavy(
|
|
|
208
288
|
Parameters
|
|
209
289
|
----------
|
|
210
290
|
width : int
|
|
211
|
-
The overall width
|
|
291
|
+
The overall width of the wavy circle. Defaults to 200.
|
|
292
|
+
height : int | None
|
|
293
|
+
The overall height of the wavy circle. Defaults to the value of width.
|
|
212
294
|
wave_amplitude : float
|
|
213
295
|
The amplitude of the waves. Defaults to 10.
|
|
214
296
|
wave_frequency : float
|
|
@@ -220,21 +302,28 @@ def disk_wavy(
|
|
|
220
302
|
-------
|
|
221
303
|
Device
|
|
222
304
|
A Device object containing the wavy circular shape.
|
|
305
|
+
|
|
306
|
+
Notes
|
|
307
|
+
-----
|
|
308
|
+
The effective radius is reduced by wave_amplitude to ensure the wavy
|
|
309
|
+
edges stay within the specified dimensions.
|
|
223
310
|
"""
|
|
224
|
-
|
|
225
|
-
|
|
311
|
+
height = _default_height(height, width)
|
|
312
|
+
size = min(width, height)
|
|
313
|
+
effective_radius = (size // 2) - wave_amplitude
|
|
314
|
+
y, x = np.ogrid[-size // 2 : size // 2, -size // 2 : size // 2]
|
|
226
315
|
distance_from_center = np.sqrt(x**2 + y**2)
|
|
227
316
|
sinusoidal_boundary = effective_radius + wave_amplitude * np.sin(
|
|
228
317
|
wave_frequency * np.arctan2(y, x)
|
|
229
318
|
)
|
|
230
319
|
mask = distance_from_center <= sinusoidal_boundary
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return Device(device_array=
|
|
320
|
+
shape_array = np.zeros((size, size))
|
|
321
|
+
shape_array[mask] = 1
|
|
322
|
+
return Device(device_array=shape_array, **kwargs)
|
|
234
323
|
|
|
235
324
|
|
|
236
325
|
def pie(
|
|
237
|
-
width: int = 200, height:
|
|
326
|
+
width: int = 200, height: int | None = None, arc_angle: float = 270, **kwargs: Any
|
|
238
327
|
) -> Device:
|
|
239
328
|
"""
|
|
240
329
|
Create a Device object with a pie shape.
|
|
@@ -243,7 +332,7 @@ def pie(
|
|
|
243
332
|
----------
|
|
244
333
|
width : int
|
|
245
334
|
The width of the pie. Defaults to 200.
|
|
246
|
-
height :
|
|
335
|
+
height : int | None
|
|
247
336
|
The height of the pie. Defaults to the value of width.
|
|
248
337
|
arc_angle : float
|
|
249
338
|
The angle of the pie slice in degrees. Defaults to 270.
|
|
@@ -254,18 +343,24 @@ def pie(
|
|
|
254
343
|
-------
|
|
255
344
|
Device
|
|
256
345
|
A Device object containing the pie shape.
|
|
346
|
+
|
|
347
|
+
Notes
|
|
348
|
+
-----
|
|
349
|
+
The arc angle starts from the positive x-axis (right) and sweeps
|
|
350
|
+
counter-clockwise. Angle is measured in degrees.
|
|
257
351
|
"""
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
352
|
+
height = _default_height(height, width)
|
|
353
|
+
ellipse_mask, radius_x, radius_y = _create_ellipse_mask(width, height)
|
|
354
|
+
|
|
355
|
+
# Calculate angle mask
|
|
262
356
|
y, x = np.ogrid[-radius_y:radius_y, -radius_x:radius_x]
|
|
263
357
|
angle = np.arctan2(y, x) * 180 / np.pi
|
|
264
358
|
angle = (angle + 360) % 360
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
359
|
+
angle_mask = angle <= arc_angle
|
|
360
|
+
|
|
361
|
+
shape_array = np.zeros((height, width))
|
|
362
|
+
shape_array[ellipse_mask & angle_mask] = 1
|
|
363
|
+
return Device(device_array=shape_array, **kwargs)
|
|
269
364
|
|
|
270
365
|
|
|
271
366
|
def grating(
|
|
@@ -273,7 +368,7 @@ def grating(
|
|
|
273
368
|
pitch: int = 120,
|
|
274
369
|
duty_cycle: float = 0.5,
|
|
275
370
|
num_gratings: int = 3,
|
|
276
|
-
**kwargs,
|
|
371
|
+
**kwargs: Any,
|
|
277
372
|
) -> Device:
|
|
278
373
|
"""
|
|
279
374
|
Create a Device object with a grating pattern.
|
|
@@ -285,7 +380,8 @@ def grating(
|
|
|
285
380
|
pitch : int
|
|
286
381
|
The pitch (period) of the grating. Defaults to 120.
|
|
287
382
|
duty_cycle : float
|
|
288
|
-
The duty cycle of the grating. Defaults to
|
|
383
|
+
The duty cycle of the grating (fraction of pitch that is filled). Defaults to
|
|
384
|
+
0.5.
|
|
289
385
|
num_gratings : int
|
|
290
386
|
The number of grating periods. Defaults to 3.
|
|
291
387
|
**kwargs : dict
|
|
@@ -295,24 +391,33 @@ def grating(
|
|
|
295
391
|
-------
|
|
296
392
|
Device
|
|
297
393
|
A Device object containing the grating pattern.
|
|
394
|
+
|
|
395
|
+
Notes
|
|
396
|
+
-----
|
|
397
|
+
The total width is calculated as pitch * num_gratings.
|
|
398
|
+
Each grating line has width = pitch * duty_cycle.
|
|
298
399
|
"""
|
|
299
|
-
width = pitch * num_gratings
|
|
300
|
-
|
|
400
|
+
width = pitch * num_gratings
|
|
401
|
+
shape_array = np.zeros((height, width))
|
|
301
402
|
grating_width = int(pitch * duty_cycle)
|
|
302
403
|
for i in range(num_gratings):
|
|
303
404
|
start = i * pitch
|
|
304
|
-
|
|
305
|
-
return Device(device_array=
|
|
405
|
+
shape_array[:, start : start + grating_width] = 1
|
|
406
|
+
return Device(device_array=shape_array, **kwargs)
|
|
306
407
|
|
|
307
408
|
|
|
308
|
-
def star(
|
|
409
|
+
def star(
|
|
410
|
+
width: int = 200, height: int | None = None, num_points: int = 5, **kwargs: Any
|
|
411
|
+
) -> Device:
|
|
309
412
|
"""
|
|
310
413
|
Create a Device object with a star shape.
|
|
311
414
|
|
|
312
415
|
Parameters
|
|
313
416
|
----------
|
|
314
417
|
width : int
|
|
315
|
-
The overall width
|
|
418
|
+
The overall width of the star. Defaults to 200.
|
|
419
|
+
height : int | None
|
|
420
|
+
The overall height of the star. Defaults to the value of width.
|
|
316
421
|
num_points : int
|
|
317
422
|
The number of points on the star. Defaults to 5.
|
|
318
423
|
**kwargs : dict
|
|
@@ -322,37 +427,51 @@ def star(width: int = 200, num_points: int = 5, **kwargs) -> Device:
|
|
|
322
427
|
-------
|
|
323
428
|
Device
|
|
324
429
|
A Device object containing the star shape.
|
|
430
|
+
|
|
431
|
+
Notes
|
|
432
|
+
-----
|
|
433
|
+
The inner radius is set to 50% of the outer radius by default.
|
|
325
434
|
"""
|
|
326
|
-
|
|
327
|
-
|
|
435
|
+
height = _default_height(height, width)
|
|
436
|
+
size = min(width, height)
|
|
437
|
+
radius_outer = size // 2
|
|
438
|
+
radius_inner = radius_outer // 2 # Inner radius is 50% of outer radius
|
|
439
|
+
|
|
328
440
|
angles_outer = np.linspace(0, 2 * np.pi, num_points, endpoint=False) - np.pi / 2
|
|
329
441
|
angles_inner = angles_outer + np.pi / num_points
|
|
442
|
+
|
|
330
443
|
x_outer = (radius_outer * np.cos(angles_outer) + radius_outer).astype(int)
|
|
331
444
|
y_outer = (radius_outer * np.sin(angles_outer) + radius_outer).astype(int)
|
|
332
445
|
x_inner = (radius_inner * np.cos(angles_inner) + radius_outer).astype(int)
|
|
333
446
|
y_inner = (radius_inner * np.sin(angles_inner) + radius_outer).astype(int)
|
|
447
|
+
|
|
334
448
|
x = np.empty(2 * num_points, dtype=int)
|
|
335
449
|
y = np.empty(2 * num_points, dtype=int)
|
|
336
450
|
x[0::2] = x_outer
|
|
337
451
|
x[1::2] = x_inner
|
|
338
452
|
y[0::2] = y_outer
|
|
339
453
|
y[1::2] = y_inner
|
|
340
|
-
|
|
454
|
+
|
|
455
|
+
shape_array = np.zeros((size, size))
|
|
341
456
|
rr, cc = polygon(y, x)
|
|
342
|
-
rr = np.clip(rr, 0,
|
|
343
|
-
cc = np.clip(cc, 0,
|
|
344
|
-
|
|
345
|
-
return Device(device_array=
|
|
457
|
+
rr = np.clip(rr, 0, size - 1)
|
|
458
|
+
cc = np.clip(cc, 0, size - 1)
|
|
459
|
+
shape_array[rr, cc] = 1
|
|
460
|
+
return Device(device_array=shape_array, **kwargs)
|
|
346
461
|
|
|
347
462
|
|
|
348
|
-
def poly(
|
|
463
|
+
def poly(
|
|
464
|
+
width: int = 200, height: int | None = None, num_points: int = 5, **kwargs: Any
|
|
465
|
+
) -> Device:
|
|
349
466
|
"""
|
|
350
467
|
Create a Device object with a regular polygon shape.
|
|
351
468
|
|
|
352
469
|
Parameters
|
|
353
470
|
----------
|
|
354
471
|
width : int
|
|
355
|
-
The overall width
|
|
472
|
+
The overall width of the polygon. Defaults to 200.
|
|
473
|
+
height : int | None
|
|
474
|
+
The overall height of the polygon. Defaults to the value of width.
|
|
356
475
|
num_points : int
|
|
357
476
|
The number of sides of the polygon. Defaults to 5.
|
|
358
477
|
**kwargs : dict
|
|
@@ -363,20 +482,28 @@ def poly(width: int = 200, num_points: int = 5, **kwargs) -> Device:
|
|
|
363
482
|
Device
|
|
364
483
|
A Device object containing the regular polygon shape.
|
|
365
484
|
"""
|
|
366
|
-
|
|
485
|
+
height = _default_height(height, width)
|
|
486
|
+
size = min(width, height)
|
|
487
|
+
radius = size // 2
|
|
488
|
+
|
|
367
489
|
angles = np.linspace(0, 2 * np.pi, num_points, endpoint=False) - np.pi / 2
|
|
368
490
|
x = (radius * np.cos(angles) + radius).astype(int)
|
|
369
491
|
y = (radius * np.sin(angles) + radius).astype(int)
|
|
370
|
-
|
|
492
|
+
|
|
493
|
+
shape_array = np.zeros((size, size))
|
|
371
494
|
rr, cc = polygon(y, x)
|
|
372
|
-
rr = np.clip(rr, 0,
|
|
373
|
-
cc = np.clip(cc, 0,
|
|
374
|
-
|
|
375
|
-
return Device(device_array=
|
|
495
|
+
rr = np.clip(rr, 0, size - 1)
|
|
496
|
+
cc = np.clip(cc, 0, size - 1)
|
|
497
|
+
shape_array[rr, cc] = 1
|
|
498
|
+
return Device(device_array=shape_array, **kwargs)
|
|
376
499
|
|
|
377
500
|
|
|
378
501
|
def radial_grating(
|
|
379
|
-
width: int = 200,
|
|
502
|
+
width: int = 200,
|
|
503
|
+
height: int | None = None,
|
|
504
|
+
grating_skew: int = 0,
|
|
505
|
+
num_gratings: int = 6,
|
|
506
|
+
**kwargs: Any,
|
|
380
507
|
) -> Device:
|
|
381
508
|
"""
|
|
382
509
|
Create a Device object with a radial grating pattern.
|
|
@@ -384,7 +511,9 @@ def radial_grating(
|
|
|
384
511
|
Parameters
|
|
385
512
|
----------
|
|
386
513
|
width : int
|
|
387
|
-
The overall width
|
|
514
|
+
The overall width of the radial grating. Defaults to 200.
|
|
515
|
+
height : int | None
|
|
516
|
+
The overall height of the radial grating. Defaults to the value of width.
|
|
388
517
|
grating_skew : int
|
|
389
518
|
The skew angle of the grating arms. Defaults to 0.
|
|
390
519
|
num_gratings : int
|
|
@@ -396,11 +525,18 @@ def radial_grating(
|
|
|
396
525
|
-------
|
|
397
526
|
Device
|
|
398
527
|
A Device object containing the radial grating pattern.
|
|
528
|
+
|
|
529
|
+
Notes
|
|
530
|
+
-----
|
|
531
|
+
The grating_skew parameter controls the angular width of each arm.
|
|
399
532
|
"""
|
|
400
|
-
|
|
401
|
-
|
|
533
|
+
height = _default_height(height, width)
|
|
534
|
+
size = min(width, height)
|
|
535
|
+
shape_array = np.zeros((size, size))
|
|
536
|
+
center = size // 2
|
|
402
537
|
radius = center
|
|
403
538
|
theta = np.linspace(0, 2 * np.pi, num_gratings, endpoint=False)
|
|
539
|
+
|
|
404
540
|
for angle in theta:
|
|
405
541
|
x0, y0 = center, center
|
|
406
542
|
x1 = int(center + radius * np.cos(angle))
|
|
@@ -412,10 +548,11 @@ def radial_grating(
|
|
|
412
548
|
center + (radius - grating_skew) * np.sin(angle + np.pi / num_gratings)
|
|
413
549
|
)
|
|
414
550
|
rr, cc = polygon([y0, y1, y2], [x0, x1, x2])
|
|
415
|
-
rr = np.clip(rr, 0,
|
|
416
|
-
cc = np.clip(cc, 0,
|
|
417
|
-
|
|
418
|
-
|
|
551
|
+
rr = np.clip(rr, 0, size - 1)
|
|
552
|
+
cc = np.clip(cc, 0, size - 1)
|
|
553
|
+
shape_array[rr, cc] = 1
|
|
554
|
+
|
|
555
|
+
return Device(device_array=shape_array, **kwargs)
|
|
419
556
|
|
|
420
557
|
|
|
421
558
|
def offset_grating(
|
|
@@ -423,7 +560,7 @@ def offset_grating(
|
|
|
423
560
|
pitch: int = 120,
|
|
424
561
|
duty_cycle: float = 0.5,
|
|
425
562
|
num_gratings: int = 3,
|
|
426
|
-
**kwargs,
|
|
563
|
+
**kwargs: Any,
|
|
427
564
|
) -> Device:
|
|
428
565
|
"""
|
|
429
566
|
Create a Device object with an offset grating pattern (alternating rows).
|
|
@@ -435,7 +572,8 @@ def offset_grating(
|
|
|
435
572
|
pitch : int
|
|
436
573
|
The pitch (period) of the grating. Defaults to 120.
|
|
437
574
|
duty_cycle : float
|
|
438
|
-
The duty cycle of the grating. Defaults to
|
|
575
|
+
The duty cycle of the grating (fraction of pitch that is filled). Defaults to
|
|
576
|
+
0.5.
|
|
439
577
|
num_gratings : int
|
|
440
578
|
The number of grating periods. Defaults to 3.
|
|
441
579
|
**kwargs : dict
|
|
@@ -445,26 +583,36 @@ def offset_grating(
|
|
|
445
583
|
-------
|
|
446
584
|
Device
|
|
447
585
|
A Device object containing the offset grating pattern.
|
|
586
|
+
|
|
587
|
+
Notes
|
|
588
|
+
-----
|
|
589
|
+
The top half of the grating is offset by pitch // 2 relative to the bottom half,
|
|
590
|
+
creating an alternating pattern useful for certain optical applications.
|
|
448
591
|
"""
|
|
449
592
|
width = pitch * num_gratings
|
|
450
|
-
|
|
593
|
+
shape_array = np.zeros((height, width))
|
|
451
594
|
grating_width = int(pitch * duty_cycle)
|
|
452
595
|
half_height = height // 2
|
|
596
|
+
|
|
597
|
+
# Bottom half - standard alignment
|
|
453
598
|
for i in range(num_gratings):
|
|
454
599
|
start = i * pitch
|
|
455
|
-
|
|
600
|
+
shape_array[half_height:, start : start + grating_width] = 1
|
|
601
|
+
|
|
602
|
+
# Top half - offset by half pitch
|
|
456
603
|
for i in range(num_gratings):
|
|
457
604
|
start = i * pitch + pitch // 2
|
|
458
|
-
|
|
459
|
-
|
|
605
|
+
shape_array[:half_height, start : start + grating_width] = 1
|
|
606
|
+
|
|
607
|
+
return Device(device_array=shape_array, **kwargs)
|
|
460
608
|
|
|
461
609
|
|
|
462
610
|
def l_grating(
|
|
463
611
|
width: int = 200,
|
|
464
|
-
height:
|
|
612
|
+
height: int | None = None,
|
|
465
613
|
pitch: int = 100,
|
|
466
614
|
duty_cycle: float = 0.5,
|
|
467
|
-
**kwargs,
|
|
615
|
+
**kwargs: Any,
|
|
468
616
|
) -> Device:
|
|
469
617
|
"""
|
|
470
618
|
Create a Device object with an L-shaped grating pattern.
|
|
@@ -473,12 +621,12 @@ def l_grating(
|
|
|
473
621
|
----------
|
|
474
622
|
width : int
|
|
475
623
|
The width of the L-grating. Defaults to 200.
|
|
476
|
-
height :
|
|
624
|
+
height : int | None
|
|
477
625
|
The height of the L-grating. Defaults to the value of width.
|
|
478
626
|
pitch : int
|
|
479
627
|
The pitch (period) of the L-shapes. Defaults to 100.
|
|
480
628
|
duty_cycle : float
|
|
481
|
-
The duty cycle of the L-shapes. Defaults to 0.5.
|
|
629
|
+
The duty cycle of the L-shapes (fraction of pitch). Defaults to 0.5.
|
|
482
630
|
**kwargs : dict
|
|
483
631
|
Additional keyword arguments to be passed to the Device constructor.
|
|
484
632
|
|
|
@@ -486,21 +634,33 @@ def l_grating(
|
|
|
486
634
|
-------
|
|
487
635
|
Device
|
|
488
636
|
A Device object containing the L-shaped grating pattern.
|
|
637
|
+
|
|
638
|
+
Notes
|
|
639
|
+
-----
|
|
640
|
+
Each L-shape consists of a horizontal and vertical line extending from
|
|
641
|
+
the diagonal, creating a stepped pattern across the device.
|
|
489
642
|
"""
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
for i in range(
|
|
643
|
+
height = _default_height(height, width)
|
|
644
|
+
shape_array = np.zeros((height, width))
|
|
645
|
+
num_l_shapes = min(height, width) // pitch
|
|
646
|
+
l_width = int(pitch * duty_cycle)
|
|
647
|
+
|
|
648
|
+
for i in range(num_l_shapes):
|
|
496
649
|
start = i * pitch
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
650
|
+
# Horizontal bar of L extending right from diagonal
|
|
651
|
+
shape_array[start : start + l_width, start:] = 1
|
|
652
|
+
# Vertical bar of L extending down from diagonal
|
|
653
|
+
shape_array[start:, start : start + l_width] = 1
|
|
654
|
+
|
|
655
|
+
return Device(device_array=shape_array, **kwargs)
|
|
500
656
|
|
|
501
657
|
|
|
502
658
|
def disks(
|
|
503
|
-
rows: int = 5,
|
|
659
|
+
rows: int = 5,
|
|
660
|
+
cols: int = 5,
|
|
661
|
+
disk_radius: int = 30,
|
|
662
|
+
spacing: int = 60,
|
|
663
|
+
**kwargs: Any,
|
|
504
664
|
) -> Device:
|
|
505
665
|
"""
|
|
506
666
|
Create a Device object with a grid of uniform disks.
|
|
@@ -525,22 +685,23 @@ def disks(
|
|
|
525
685
|
"""
|
|
526
686
|
grid_height = rows * (2 * disk_radius + spacing) - spacing
|
|
527
687
|
grid_width = cols * (2 * disk_radius + spacing) - spacing
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
mask = x**2 + y**2 <= disk_radius**2
|
|
688
|
+
shape_array = np.zeros((grid_height, grid_width))
|
|
689
|
+
|
|
531
690
|
for row in range(rows):
|
|
532
691
|
for col in range(cols):
|
|
533
692
|
center_y = row * (2 * disk_radius + spacing) + disk_radius
|
|
534
693
|
center_x = col * (2 * disk_radius + spacing) + disk_radius
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
][mask] = 1
|
|
539
|
-
return Device(device_array=disks, **kwargs)
|
|
694
|
+
_place_disk_in_grid(shape_array, center_y, center_x, disk_radius, value=1.0)
|
|
695
|
+
|
|
696
|
+
return Device(device_array=shape_array, **kwargs)
|
|
540
697
|
|
|
541
698
|
|
|
542
699
|
def disks_offset(
|
|
543
|
-
rows: int = 5,
|
|
700
|
+
rows: int = 5,
|
|
701
|
+
cols: int = 5,
|
|
702
|
+
disk_radius: int = 30,
|
|
703
|
+
spacing: int = 30,
|
|
704
|
+
**kwargs: Any,
|
|
544
705
|
) -> Device:
|
|
545
706
|
"""
|
|
546
707
|
Create a Device object with an offset grid of disks.
|
|
@@ -562,27 +723,26 @@ def disks_offset(
|
|
|
562
723
|
-------
|
|
563
724
|
Device
|
|
564
725
|
A Device object containing an offset grid of disks.
|
|
726
|
+
|
|
727
|
+
Notes
|
|
728
|
+
-----
|
|
729
|
+
Odd-numbered rows are shifted by (disk_radius + spacing // 2) to create
|
|
730
|
+
an offset hexagonal packing pattern.
|
|
565
731
|
"""
|
|
566
732
|
grid_height = rows * (2 * disk_radius + spacing) - spacing
|
|
567
733
|
grid_width = (
|
|
568
734
|
cols * (2 * disk_radius + spacing) - spacing + (disk_radius + spacing // 2)
|
|
569
735
|
)
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
mask = x**2 + y**2 <= disk_radius**2
|
|
736
|
+
shape_array = np.zeros((grid_height, grid_width))
|
|
737
|
+
|
|
573
738
|
for row in range(rows):
|
|
574
739
|
for col in range(cols):
|
|
575
740
|
center_y = row * (2 * disk_radius + spacing) + disk_radius
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
disks_offset[
|
|
582
|
-
center_y - disk_radius : center_y + disk_radius,
|
|
583
|
-
center_x - disk_radius : center_x + disk_radius,
|
|
584
|
-
][mask] = 1
|
|
585
|
-
return Device(device_array=disks_offset, **kwargs)
|
|
741
|
+
offset_x = disk_radius + spacing // 2 if row % 2 == 1 else 0
|
|
742
|
+
center_x = col * (2 * disk_radius + spacing) + disk_radius + offset_x
|
|
743
|
+
_place_disk_in_grid(shape_array, center_y, center_x, disk_radius, value=1.0)
|
|
744
|
+
|
|
745
|
+
return Device(device_array=shape_array, **kwargs)
|
|
586
746
|
|
|
587
747
|
|
|
588
748
|
def disks_varying(
|
|
@@ -591,7 +751,7 @@ def disks_varying(
|
|
|
591
751
|
min_disk_radius: int = 10,
|
|
592
752
|
max_disk_radius: int = 30,
|
|
593
753
|
spacing: int = 30,
|
|
594
|
-
**kwargs,
|
|
754
|
+
**kwargs: Any,
|
|
595
755
|
) -> Device:
|
|
596
756
|
"""
|
|
597
757
|
Create a Device object with a grid of disks with varying radii.
|
|
@@ -615,29 +775,36 @@ def disks_varying(
|
|
|
615
775
|
-------
|
|
616
776
|
Device
|
|
617
777
|
A Device object containing a grid of disks with varying radii.
|
|
778
|
+
|
|
779
|
+
Notes
|
|
780
|
+
-----
|
|
781
|
+
Disk radii vary linearly from min_disk_radius to max_disk_radius across
|
|
782
|
+
the grid, progressing row by row, left to right.
|
|
618
783
|
"""
|
|
619
784
|
grid_height = rows * (2 * max_disk_radius + spacing) - spacing
|
|
620
785
|
grid_width = cols * (2 * max_disk_radius + spacing) - spacing
|
|
621
|
-
|
|
786
|
+
shape_array = np.zeros((grid_height, grid_width))
|
|
787
|
+
|
|
622
788
|
radius_range = np.linspace(min_disk_radius, max_disk_radius, rows * cols).reshape(
|
|
623
789
|
rows, cols
|
|
624
790
|
)
|
|
791
|
+
|
|
625
792
|
for row in range(rows):
|
|
626
793
|
for col in range(cols):
|
|
627
794
|
disk_radius = int(radius_range[row, col])
|
|
628
|
-
y, x = np.ogrid[-disk_radius:disk_radius, -disk_radius:disk_radius]
|
|
629
|
-
mask = x**2 + y**2 <= disk_radius**2
|
|
630
795
|
center_y = row * (2 * max_disk_radius + spacing) + max_disk_radius
|
|
631
796
|
center_x = col * (2 * max_disk_radius + spacing) + max_disk_radius
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
][mask] = 1
|
|
636
|
-
return Device(device_array=disks_varying, **kwargs)
|
|
797
|
+
_place_disk_in_grid(shape_array, center_y, center_x, disk_radius, value=1.0)
|
|
798
|
+
|
|
799
|
+
return Device(device_array=shape_array, **kwargs)
|
|
637
800
|
|
|
638
801
|
|
|
639
802
|
def holes(
|
|
640
|
-
rows: int = 5,
|
|
803
|
+
rows: int = 5,
|
|
804
|
+
cols: int = 5,
|
|
805
|
+
hole_radius: int = 30,
|
|
806
|
+
spacing: int = 30,
|
|
807
|
+
**kwargs: Any,
|
|
641
808
|
) -> Device:
|
|
642
809
|
"""
|
|
643
810
|
Create a Device object with a grid of uniform circular holes.
|
|
@@ -662,22 +829,23 @@ def holes(
|
|
|
662
829
|
"""
|
|
663
830
|
grid_height = rows * (2 * hole_radius + spacing) - spacing
|
|
664
831
|
grid_width = cols * (2 * hole_radius + spacing) - spacing
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
mask = x**2 + y**2 <= hole_radius**2
|
|
832
|
+
shape_array = np.ones((grid_height, grid_width))
|
|
833
|
+
|
|
668
834
|
for row in range(rows):
|
|
669
835
|
for col in range(cols):
|
|
670
836
|
center_y = row * (2 * hole_radius + spacing) + hole_radius
|
|
671
837
|
center_x = col * (2 * hole_radius + spacing) + hole_radius
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
][mask] = 0
|
|
676
|
-
return Device(device_array=holes, **kwargs)
|
|
838
|
+
_place_disk_in_grid(shape_array, center_y, center_x, hole_radius, value=0.0)
|
|
839
|
+
|
|
840
|
+
return Device(device_array=shape_array, **kwargs)
|
|
677
841
|
|
|
678
842
|
|
|
679
843
|
def holes_offset(
|
|
680
|
-
rows: int = 5,
|
|
844
|
+
rows: int = 5,
|
|
845
|
+
cols: int = 5,
|
|
846
|
+
hole_radius: int = 30,
|
|
847
|
+
spacing: int = 30,
|
|
848
|
+
**kwargs: Any,
|
|
681
849
|
) -> Device:
|
|
682
850
|
"""
|
|
683
851
|
Create a Device object with an offset grid of circular holes.
|
|
@@ -699,27 +867,26 @@ def holes_offset(
|
|
|
699
867
|
-------
|
|
700
868
|
Device
|
|
701
869
|
A Device object containing an offset grid of circular holes.
|
|
870
|
+
|
|
871
|
+
Notes
|
|
872
|
+
-----
|
|
873
|
+
Odd-numbered rows are shifted by (hole_radius + spacing // 2) to create
|
|
874
|
+
an offset hexagonal packing pattern.
|
|
702
875
|
"""
|
|
703
876
|
grid_height = rows * (2 * hole_radius + spacing) - spacing
|
|
704
877
|
grid_width = (
|
|
705
878
|
cols * (2 * hole_radius + spacing) - spacing + (hole_radius + spacing // 2)
|
|
706
879
|
)
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
mask = x**2 + y**2 <= hole_radius**2
|
|
880
|
+
shape_array = np.ones((grid_height, grid_width))
|
|
881
|
+
|
|
710
882
|
for row in range(rows):
|
|
711
883
|
for col in range(cols):
|
|
712
884
|
center_y = row * (2 * hole_radius + spacing) + hole_radius
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
holes_offset[
|
|
719
|
-
center_y - hole_radius : center_y + hole_radius,
|
|
720
|
-
center_x - hole_radius : center_x + hole_radius,
|
|
721
|
-
][mask] = 0
|
|
722
|
-
return Device(device_array=holes_offset, **kwargs)
|
|
885
|
+
offset_x = hole_radius + spacing // 2 if row % 2 == 1 else 0
|
|
886
|
+
center_x = col * (2 * hole_radius + spacing) + hole_radius + offset_x
|
|
887
|
+
_place_disk_in_grid(shape_array, center_y, center_x, hole_radius, value=0.0)
|
|
888
|
+
|
|
889
|
+
return Device(device_array=shape_array, **kwargs)
|
|
723
890
|
|
|
724
891
|
|
|
725
892
|
def holes_varying(
|
|
@@ -728,7 +895,7 @@ def holes_varying(
|
|
|
728
895
|
min_hole_radius: int = 10,
|
|
729
896
|
max_hole_radius: int = 30,
|
|
730
897
|
spacing: int = 30,
|
|
731
|
-
**kwargs,
|
|
898
|
+
**kwargs: Any,
|
|
732
899
|
) -> Device:
|
|
733
900
|
"""
|
|
734
901
|
Create a Device object with a grid of circular holes with varying radii.
|
|
@@ -752,22 +919,25 @@ def holes_varying(
|
|
|
752
919
|
-------
|
|
753
920
|
Device
|
|
754
921
|
A Device object containing a grid of circular holes with varying radii.
|
|
922
|
+
|
|
923
|
+
Notes
|
|
924
|
+
-----
|
|
925
|
+
Hole radii vary linearly from min_hole_radius to max_hole_radius across
|
|
926
|
+
the grid, progressing row by row, left to right.
|
|
755
927
|
"""
|
|
756
928
|
grid_height = rows * (2 * max_hole_radius + spacing) - spacing
|
|
757
929
|
grid_width = cols * (2 * max_hole_radius + spacing) - spacing
|
|
758
|
-
|
|
930
|
+
shape_array = np.ones((grid_height, grid_width))
|
|
931
|
+
|
|
759
932
|
radius_range = np.linspace(min_hole_radius, max_hole_radius, rows * cols).reshape(
|
|
760
933
|
rows, cols
|
|
761
934
|
)
|
|
935
|
+
|
|
762
936
|
for row in range(rows):
|
|
763
937
|
for col in range(cols):
|
|
764
938
|
hole_radius = int(radius_range[row, col])
|
|
765
|
-
y, x = np.ogrid[-hole_radius:hole_radius, -hole_radius:hole_radius]
|
|
766
|
-
mask = x**2 + y**2 <= hole_radius**2
|
|
767
939
|
center_y = row * (2 * max_hole_radius + spacing) + max_hole_radius
|
|
768
940
|
center_x = col * (2 * max_hole_radius + spacing) + max_hole_radius
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
][mask] = 0
|
|
773
|
-
return Device(device_array=holes_varying, **kwargs)
|
|
941
|
+
_place_disk_in_grid(shape_array, center_y, center_x, hole_radius, value=0.0)
|
|
942
|
+
|
|
943
|
+
return Device(device_array=shape_array, **kwargs)
|