fdtdx 0.1.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.
- fdtdx/__init__.py +37 -0
- fdtdx/constraints/__init__.py +14 -0
- fdtdx/constraints/binary_transform.py +407 -0
- fdtdx/constraints/discrete.py +407 -0
- fdtdx/constraints/mapping.py +102 -0
- fdtdx/constraints/module.py +268 -0
- fdtdx/constraints/pillars.py +140 -0
- fdtdx/constraints/utils.py +186 -0
- fdtdx/core/__init__.py +15 -0
- fdtdx/core/config.py +153 -0
- fdtdx/core/conversion/__init__.py +14 -0
- fdtdx/core/conversion/export.py +169 -0
- fdtdx/core/conversion/import_utils.py +57 -0
- fdtdx/core/conversion/load_stl.py +55 -0
- fdtdx/core/gaussian_blur.py +148 -0
- fdtdx/core/jax/__init__.py +14 -0
- fdtdx/core/jax/pytrees.py +307 -0
- fdtdx/core/jax/sharding.py +134 -0
- fdtdx/core/jax/ste.py +33 -0
- fdtdx/core/jax/typing.py +58 -0
- fdtdx/core/jax/utils.py +36 -0
- fdtdx/core/misc.py +753 -0
- fdtdx/core/modes.py +135 -0
- fdtdx/core/physics/__init__.py +14 -0
- fdtdx/core/physics/constants.py +119 -0
- fdtdx/core/physics/losses.py +45 -0
- fdtdx/core/physics/metrics.py +86 -0
- fdtdx/core/physics/modes.py +119 -0
- fdtdx/core/plotting/__init__.py +14 -0
- fdtdx/core/plotting/colors.py +40 -0
- fdtdx/core/plotting/debug.py +98 -0
- fdtdx/core/plotting/device_permittivity_index_utils.py +148 -0
- fdtdx/core/plotting/utils.py +89 -0
- fdtdx/fdtd/__init__.py +17 -0
- fdtdx/fdtd/backward.py +139 -0
- fdtdx/fdtd/curl.py +120 -0
- fdtdx/fdtd/fdtd.py +420 -0
- fdtdx/fdtd/forward.py +196 -0
- fdtdx/fdtd/update.py +453 -0
- fdtdx/interfaces/__init__.py +0 -0
- fdtdx/interfaces/modules.py +227 -0
- fdtdx/interfaces/recorder.py +321 -0
- fdtdx/interfaces/state.py +82 -0
- fdtdx/interfaces/time_filter.py +576 -0
- fdtdx/objects/__init__.py +14 -0
- fdtdx/objects/boundaries/__init__.py +14 -0
- fdtdx/objects/boundaries/boundary_utils.py +306 -0
- fdtdx/objects/boundaries/initialization.py +219 -0
- fdtdx/objects/boundaries/perfectly_matched_layer.py +415 -0
- fdtdx/objects/container.py +175 -0
- fdtdx/objects/detectors/__init__.py +14 -0
- fdtdx/objects/detectors/detector.py +402 -0
- fdtdx/objects/detectors/energy.py +106 -0
- fdtdx/objects/detectors/phasor.py +142 -0
- fdtdx/objects/detectors/plotting/__init__.py +14 -0
- fdtdx/objects/detectors/plotting/line_plot.py +28 -0
- fdtdx/objects/detectors/plotting/plot2d.py +148 -0
- fdtdx/objects/detectors/plotting/video.py +166 -0
- fdtdx/objects/detectors/poynting_flux.py +103 -0
- fdtdx/objects/initialization.py +630 -0
- fdtdx/objects/material.py +130 -0
- fdtdx/objects/multi_material/__init__.py +14 -0
- fdtdx/objects/multi_material/device.py +174 -0
- fdtdx/objects/multi_material/fiber.py +164 -0
- fdtdx/objects/multi_material/multi_material.py +169 -0
- fdtdx/objects/multi_material/random_scatterer.py +202 -0
- fdtdx/objects/object.py +811 -0
- fdtdx/objects/sources/__init__.py +14 -0
- fdtdx/objects/sources/plane_source.py +926 -0
- fdtdx/objects/sources/source.py +258 -0
- fdtdx/objects/wavelength.py +112 -0
- fdtdx/py.typed +0 -0
- fdtdx/shared/__init__.py +0 -0
- fdtdx/shared/logger.py +328 -0
- fdtdx/shared/misc.py +70 -0
- fdtdx/shared/plot_setup.py +166 -0
- fdtdx-0.1.0.dist-info/METADATA +86 -0
- fdtdx-0.1.0.dist-info/RECORD +80 -0
- fdtdx-0.1.0.dist-info/WHEEL +4 -0
- fdtdx-0.1.0.dist-info/licenses/LICENSE +21 -0
fdtdx/__init__.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""FDTDX: A JAX-based Finite-Difference Time-Domain (FDTD) Electromagnetic Simulation Package.
|
|
2
|
+
|
|
3
|
+
A high-performance framework for electromagnetic simulations and inverse design optimization
|
|
4
|
+
using the FDTD method, implemented in JAX for GPU acceleration and automatic differentiation.
|
|
5
|
+
|
|
6
|
+
Key Features:
|
|
7
|
+
- Memory-efficient automatic differentiation using time-reversibility
|
|
8
|
+
- Multi-GPU support for large-scale simulations
|
|
9
|
+
- Intuitive object positioning with relative coordinates
|
|
10
|
+
- Built-in optimization capabilities for inverse design
|
|
11
|
+
- Comprehensive boundary condition implementations (e.g., PML)
|
|
12
|
+
- Field detectors and visualization tools
|
|
13
|
+
- JIT-compiled operations for maximum performance
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
Basic simulation setup:
|
|
17
|
+
```python
|
|
18
|
+
import fdtdx
|
|
19
|
+
from fdtdx.objects import Source, Detector
|
|
20
|
+
|
|
21
|
+
# Create simulation components
|
|
22
|
+
source = Source(...)
|
|
23
|
+
detector = Detector(...)
|
|
24
|
+
|
|
25
|
+
# Run simulation
|
|
26
|
+
fields = fdtdx.simulate(source, detector)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Notes:
|
|
30
|
+
The package is designed for both research and industrial applications in nanophotonics,
|
|
31
|
+
metamaterials, and photonic integrated circuits. It supports both 2D and 3D simulations
|
|
32
|
+
with automatic gradient computation for optimization tasks.
|
|
33
|
+
|
|
34
|
+
References:
|
|
35
|
+
- Paper: https://github.com/ymahlau/fdtdx
|
|
36
|
+
- Documentation: https://github.com/ymahlau/fdtdx/docs
|
|
37
|
+
"""
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Constraint modules for enforcing physical and fabrication requirements.
|
|
2
|
+
|
|
3
|
+
This package provides constraint modules that can be applied during optimization
|
|
4
|
+
to ensure designs meet physical realizability and fabrication requirements.
|
|
5
|
+
Key constraints include:
|
|
6
|
+
|
|
7
|
+
- Minimum feature size constraints
|
|
8
|
+
- Binary material constraints (air/dielectric only)
|
|
9
|
+
- Connectivity constraints (no floating material)
|
|
10
|
+
- Fabrication constraints (no trapped air, etc.)
|
|
11
|
+
|
|
12
|
+
The constraints are implemented as modules that can be chained together
|
|
13
|
+
and applied during the optimization process.
|
|
14
|
+
"""
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import jax
|
|
2
|
+
import jax.numpy as jnp
|
|
3
|
+
|
|
4
|
+
from fdtdx.core.misc import PaddingConfig, advanced_padding
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def remove_floating_polymer(
|
|
8
|
+
matrix: jax.Array, # 1 is polymer, zero is air, shape (x, y, z)
|
|
9
|
+
) -> jax.Array:
|
|
10
|
+
"""Removes polymer regions that are not connected to the substrate.
|
|
11
|
+
|
|
12
|
+
Uses flood-fill algorithm to identify polymer regions connected to the bottom layer
|
|
13
|
+
and removes any floating polymer regions that are not connected.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
matrix: Binary array where 1 represents polymer and 0 represents air.
|
|
17
|
+
Shape is (x, y, z) representing the 3D grid.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Modified binary array with floating polymer regions removed.
|
|
21
|
+
"""
|
|
22
|
+
connected = compute_polymer_connection(matrix)
|
|
23
|
+
non_connected_polymer = jnp.invert(connected) & matrix
|
|
24
|
+
matrix = matrix & jnp.invert(non_connected_polymer)
|
|
25
|
+
return matrix
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def remove_polymer_non_connected_to_x_max_middle(
|
|
29
|
+
matrix: jax.Array, # 1 is polymer, zero is air, shape (x, y, z)
|
|
30
|
+
) -> jax.Array:
|
|
31
|
+
"""Removes polymer regions not connected to the middle of the x-max boundary.
|
|
32
|
+
|
|
33
|
+
Uses flood-fill algorithm starting from the middle point of the x-max boundary
|
|
34
|
+
to identify connected polymer regions and removes unconnected regions.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
matrix: Binary array where 1 represents polymer and 0 represents air.
|
|
38
|
+
Shape is (x, y, z) representing the 3D grid.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Modified binary array with unconnected polymer regions removed.
|
|
42
|
+
"""
|
|
43
|
+
y_middle = round(matrix.shape[1] / 2)
|
|
44
|
+
x_middle = round(matrix.shape[0] / 2)
|
|
45
|
+
connected = compute_polymer_connection(
|
|
46
|
+
matrix,
|
|
47
|
+
connected_slice=(x_middle, y_middle, None),
|
|
48
|
+
)
|
|
49
|
+
non_connected_polymer = jnp.invert(connected) & matrix
|
|
50
|
+
matrix = matrix & jnp.invert(non_connected_polymer)
|
|
51
|
+
return matrix
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def connect_holes_and_structures(
|
|
55
|
+
matrix: jax.Array, # 1 is polymer, zero is air, shape (x, y, z)
|
|
56
|
+
) -> jax.Array:
|
|
57
|
+
"""Connects disconnected polymer structures and air holes in the matrix.
|
|
58
|
+
|
|
59
|
+
Performs a two-pass algorithm:
|
|
60
|
+
1. Connects disconnected polymer structures by adding polymer material
|
|
61
|
+
2. Connects disconnected air regions by removing polymer material
|
|
62
|
+
|
|
63
|
+
This ensures both structural integrity and proper air ventilation.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
matrix: Binary array where 1 represents polymer and 0 represents air.
|
|
67
|
+
Shape is (x, y, z) representing the 3D grid.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Modified binary array with connected structures and holes.
|
|
71
|
+
"""
|
|
72
|
+
matrix = matrix.astype(bool)
|
|
73
|
+
# first connect all polymer structures # TODO: convert to fori-loops
|
|
74
|
+
for i in range(matrix.shape[-1] - 1):
|
|
75
|
+
connected = compute_polymer_connection(matrix)
|
|
76
|
+
if i == 0:
|
|
77
|
+
lower_slice = jnp.ones_like(matrix[..., 0])
|
|
78
|
+
else:
|
|
79
|
+
lower_slice = matrix[..., i - 1]
|
|
80
|
+
new_middle, new_upper = connect_slice(
|
|
81
|
+
lower_slice=lower_slice,
|
|
82
|
+
middle_slice=matrix[..., i],
|
|
83
|
+
upper_slice=matrix[..., i + 1],
|
|
84
|
+
upper_save_points=connected[..., i + 1],
|
|
85
|
+
)
|
|
86
|
+
matrix = matrix.at[..., i].set(new_middle)
|
|
87
|
+
matrix = matrix.at[..., i + 1].set(new_upper)
|
|
88
|
+
# then connect all air
|
|
89
|
+
for i in range(matrix.shape[-1], 0, -1):
|
|
90
|
+
air_connected = compute_air_connection(matrix)
|
|
91
|
+
if i == matrix.shape[-1]:
|
|
92
|
+
lower_slice = jnp.ones_like(matrix[..., 0])
|
|
93
|
+
else:
|
|
94
|
+
lower_slice = jnp.invert(matrix[..., i + 1])
|
|
95
|
+
new_middle, new_upper = connect_slice(
|
|
96
|
+
lower_slice=lower_slice,
|
|
97
|
+
middle_slice=jnp.invert(matrix[..., i]),
|
|
98
|
+
upper_slice=jnp.invert(matrix[..., i - 1]),
|
|
99
|
+
upper_save_points=air_connected[..., i - 1],
|
|
100
|
+
)
|
|
101
|
+
matrix = matrix.at[..., i].set(jnp.invert(new_middle))
|
|
102
|
+
matrix = matrix.at[..., i - 1].set(jnp.invert(new_upper))
|
|
103
|
+
# throw away all polymer that got free floating due to air connection in last step
|
|
104
|
+
connected = compute_polymer_connection(matrix)
|
|
105
|
+
non_connected_polymer = jnp.invert(connected) & matrix
|
|
106
|
+
matrix = matrix & jnp.invert(non_connected_polymer)
|
|
107
|
+
return matrix
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def compute_air_connection(matrix: jax.Array) -> jax.Array:
|
|
111
|
+
"""Computes a mask of air regions connected to the boundaries.
|
|
112
|
+
|
|
113
|
+
Uses iterative dilation to identify air regions (zeros in the matrix)
|
|
114
|
+
that are connected to the simulation boundaries. This is used to ensure
|
|
115
|
+
proper ventilation in the structure.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
matrix: Binary array where 1 represents polymer and 0 represents air.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Boolean array marking air regions connected to boundaries.
|
|
122
|
+
"""
|
|
123
|
+
inv_matrix = jnp.invert(matrix)
|
|
124
|
+
n = max([matrix.shape[0], matrix.shape[1], matrix.shape[2]])
|
|
125
|
+
n4_kernel = jnp.asarray(
|
|
126
|
+
[
|
|
127
|
+
[0, 1, 0],
|
|
128
|
+
[1, 1, 1],
|
|
129
|
+
[0, 1, 0],
|
|
130
|
+
],
|
|
131
|
+
dtype=bool,
|
|
132
|
+
)
|
|
133
|
+
connected = jnp.zeros_like(inv_matrix, dtype=bool)
|
|
134
|
+
connected = connected.at[:, :, -1].set(True)
|
|
135
|
+
connected = connected.at[0, :, :].set(True)
|
|
136
|
+
connected = connected.at[-1, :, :].set(True)
|
|
137
|
+
connected = connected.at[:, 0, :].set(True)
|
|
138
|
+
connected = connected.at[:, -1, :].set(True)
|
|
139
|
+
connected = connected & inv_matrix
|
|
140
|
+
|
|
141
|
+
def _body_fn(_, arr):
|
|
142
|
+
arr = seperated_3d_dilation(
|
|
143
|
+
arr_3d=arr,
|
|
144
|
+
kernel_xy=n4_kernel,
|
|
145
|
+
kernel_xz=n4_kernel,
|
|
146
|
+
kernel_yz=n4_kernel,
|
|
147
|
+
reduction_arr=inv_matrix,
|
|
148
|
+
)
|
|
149
|
+
return arr
|
|
150
|
+
|
|
151
|
+
connected = jax.lax.fori_loop(0, n, _body_fn, connected)
|
|
152
|
+
|
|
153
|
+
return connected
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def compute_polymer_connection(
|
|
157
|
+
matrix: jax.Array,
|
|
158
|
+
connected_slice: tuple[int | None, int | None, int | None] | None = None,
|
|
159
|
+
) -> jax.Array:
|
|
160
|
+
"""Computes a mask of polymer regions connected to specified points.
|
|
161
|
+
|
|
162
|
+
Uses iterative dilation to identify polymer regions (ones in the matrix)
|
|
163
|
+
that are connected either to the bottom layer or to specified points
|
|
164
|
+
in connected_slice.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
matrix: Binary array where 1 represents polymer and 0 represents air.
|
|
168
|
+
connected_slice: Optional tuple of indices specifying starting points
|
|
169
|
+
for the connection computation. If None, uses bottom layer.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Boolean array marking connected polymer regions.
|
|
173
|
+
"""
|
|
174
|
+
n = max([matrix.shape[0], matrix.shape[1], matrix.shape[2]])
|
|
175
|
+
padded = False
|
|
176
|
+
if matrix.shape[2] == 1:
|
|
177
|
+
padded = True
|
|
178
|
+
matrix = jnp.pad(matrix, pad_width=((0, 0), (0, 0), (1, 1)))
|
|
179
|
+
n4_kernel = jnp.asarray(
|
|
180
|
+
[
|
|
181
|
+
[0, 1, 0],
|
|
182
|
+
[1, 1, 1],
|
|
183
|
+
[0, 1, 0],
|
|
184
|
+
],
|
|
185
|
+
dtype=bool,
|
|
186
|
+
)
|
|
187
|
+
connected = jnp.zeros_like(matrix, dtype=bool)
|
|
188
|
+
if connected_slice is None:
|
|
189
|
+
connected = connected.at[..., 0].set(True)
|
|
190
|
+
else:
|
|
191
|
+
connected = connected.at[connected_slice].set(True)
|
|
192
|
+
|
|
193
|
+
def _body_fn(_, arr):
|
|
194
|
+
arr = seperated_3d_dilation(
|
|
195
|
+
arr_3d=arr,
|
|
196
|
+
kernel_xy=n4_kernel,
|
|
197
|
+
kernel_xz=n4_kernel,
|
|
198
|
+
kernel_yz=n4_kernel,
|
|
199
|
+
reduction_arr=matrix,
|
|
200
|
+
)
|
|
201
|
+
return arr
|
|
202
|
+
|
|
203
|
+
connected = jax.lax.fori_loop(0, n, _body_fn, connected)
|
|
204
|
+
|
|
205
|
+
if padded:
|
|
206
|
+
connected = connected[..., 1:2]
|
|
207
|
+
return connected
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def connect_slice(
|
|
211
|
+
lower_slice: jax.Array,
|
|
212
|
+
middle_slice: jax.Array, # this layer is optimized
|
|
213
|
+
upper_slice: jax.Array, # this layer is optimized,
|
|
214
|
+
upper_save_points: jax.Array, # points which are surely connected
|
|
215
|
+
) -> tuple[jax.Array, jax.Array]:
|
|
216
|
+
"""Connects polymer regions between adjacent slices.
|
|
217
|
+
|
|
218
|
+
Attempts to connect disconnected polymer regions between three adjacent
|
|
219
|
+
z-slices by modifying the middle and upper slices while preserving
|
|
220
|
+
specified connection points.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
lower_slice: Binary array representing the lower z-slice.
|
|
224
|
+
middle_slice: Binary array representing the middle z-slice to be optimized.
|
|
225
|
+
upper_slice: Binary array representing the upper z-slice to be optimized.
|
|
226
|
+
upper_save_points: Boolean mask of points in upper slice that must remain connected.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Tuple of (modified_middle_slice, modified_upper_slice) with connected regions.
|
|
230
|
+
"""
|
|
231
|
+
n = max(lower_slice.shape[0], lower_slice.shape[1])
|
|
232
|
+
|
|
233
|
+
# define kernels
|
|
234
|
+
n4_kernel = jnp.asarray(
|
|
235
|
+
[
|
|
236
|
+
[0, 1, 0],
|
|
237
|
+
[1, 1, 1],
|
|
238
|
+
[0, 1, 0],
|
|
239
|
+
],
|
|
240
|
+
dtype=bool,
|
|
241
|
+
)
|
|
242
|
+
n8_kernel = jnp.asarray(
|
|
243
|
+
[
|
|
244
|
+
[1, 1, 1],
|
|
245
|
+
[1, 1, 1],
|
|
246
|
+
[1, 1, 1],
|
|
247
|
+
],
|
|
248
|
+
dtype=bool,
|
|
249
|
+
)
|
|
250
|
+
direction_kernels = jnp.asarray(
|
|
251
|
+
[
|
|
252
|
+
[[0, 1, 0], [0, 0, 0], [0, 0, 0]],
|
|
253
|
+
[[0, 0, 0], [0, 0, 1], [0, 0, 0]],
|
|
254
|
+
[[0, 0, 0], [0, 0, 0], [0, 1, 0]],
|
|
255
|
+
[[0, 0, 0], [1, 0, 0], [0, 0, 0]],
|
|
256
|
+
],
|
|
257
|
+
dtype=bool,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# first determine all well-supported points
|
|
261
|
+
connected_points = upper_slice & middle_slice
|
|
262
|
+
connected_points = connected_points | upper_save_points
|
|
263
|
+
|
|
264
|
+
for _ in range(n):
|
|
265
|
+
connected_points = dilate_jax(connected_points, n4_kernel)
|
|
266
|
+
connected_points = connected_points & upper_slice
|
|
267
|
+
upper_air = jnp.invert(upper_slice)
|
|
268
|
+
non_connected_points = jnp.invert(upper_air | connected_points)
|
|
269
|
+
|
|
270
|
+
# first try to connect by setting additional polymers in lower array
|
|
271
|
+
connectable_region_lower = dilate_jax(middle_slice, n4_kernel)
|
|
272
|
+
connectable_region_lower = connectable_region_lower | lower_slice # connectable either vertical or horiz.
|
|
273
|
+
connectable_by_lower = non_connected_points & connectable_region_lower
|
|
274
|
+
# update matrix, non-connected and connected points
|
|
275
|
+
middle_slice = middle_slice | connectable_by_lower
|
|
276
|
+
connected_points = connected_points | connectable_by_lower
|
|
277
|
+
for _ in range(n):
|
|
278
|
+
connected_points = dilate_jax(connected_points, n4_kernel)
|
|
279
|
+
connected_points = connected_points & upper_slice
|
|
280
|
+
non_connected_points = jnp.invert(upper_air | connected_points)
|
|
281
|
+
|
|
282
|
+
# then try to connect by adding polymer in upper array
|
|
283
|
+
connectable_region_upper = dilate_jax(connected_points, n8_kernel)
|
|
284
|
+
connectable_by_upper = non_connected_points & connectable_region_upper
|
|
285
|
+
connection_points = jax.vmap(dilate_jax, in_axes=(None, 0))(connectable_by_upper, direction_kernels)
|
|
286
|
+
valid_connection_points = connectable_region_upper & connection_points
|
|
287
|
+
valid_connection_points = jnp.sum(valid_connection_points, axis=0).astype(bool)
|
|
288
|
+
# update matrix, non-connected and connected points
|
|
289
|
+
upper_slice = upper_slice | valid_connection_points
|
|
290
|
+
for _ in range(n):
|
|
291
|
+
connected_points = dilate_jax(connected_points, n4_kernel)
|
|
292
|
+
connected_points = connected_points & upper_slice
|
|
293
|
+
non_connected_points = jnp.invert(upper_air | connected_points)
|
|
294
|
+
|
|
295
|
+
# delete all non-connected
|
|
296
|
+
upper_slice = upper_slice & jnp.invert(non_connected_points)
|
|
297
|
+
|
|
298
|
+
return middle_slice, upper_slice
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def dilate_jax(image: jax.Array, kernel: jax.Array) -> jax.Array:
|
|
302
|
+
"""Performs morphological dilation on a binary image using JAX.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
image: Binary input array to be dilated.
|
|
306
|
+
kernel: Binary kernel defining the dilation shape.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
Dilated binary array.
|
|
310
|
+
"""
|
|
311
|
+
conv = jax.scipy.signal.convolve2d(image, kernel, mode="same", boundary="fill")
|
|
312
|
+
binary_arr = jnp.asarray(conv, dtype=bool)
|
|
313
|
+
return binary_arr
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def erode_jax(image: jax.Array, kernel: jax.Array) -> jax.Array:
|
|
317
|
+
"""Performs morphological erosion on a binary image using JAX.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
image: Binary input array to be eroded.
|
|
321
|
+
kernel: Binary kernel defining the erosion shape.
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
Eroded binary array.
|
|
325
|
+
"""
|
|
326
|
+
conv = jax.scipy.signal.convolve2d(~image, kernel, mode="same", boundary="fill")
|
|
327
|
+
binary_arr = jnp.asarray(conv, dtype=bool)
|
|
328
|
+
return ~binary_arr
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def seperated_3d_dilation(
|
|
332
|
+
arr_3d: jax.Array,
|
|
333
|
+
kernel_xy: jax.Array,
|
|
334
|
+
kernel_yz: jax.Array,
|
|
335
|
+
kernel_xz: jax.Array,
|
|
336
|
+
reduction_arr: jax.Array,
|
|
337
|
+
) -> jax.Array:
|
|
338
|
+
"""Performs separated 3D dilation along each axis plane.
|
|
339
|
+
|
|
340
|
+
Applies 2D dilation kernels separately in the XY, YZ, and XZ planes
|
|
341
|
+
to approximate 3D dilation while being more computationally efficient.
|
|
342
|
+
The result is masked by the reduction array after each operation.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
arr_3d: 3D binary array to be dilated.
|
|
346
|
+
kernel_xy: 2D kernel for XY plane dilation.
|
|
347
|
+
kernel_yz: 2D kernel for YZ plane dilation.
|
|
348
|
+
kernel_xz: 2D kernel for XZ plane dilation.
|
|
349
|
+
reduction_arr: Binary mask to constrain dilation results.
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
Dilated 3D binary array.
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
def convolve_partial(image: jax.Array, kernel: jax.Array):
|
|
356
|
+
return jax.scipy.signal.convolve2d(image, kernel, mode="same", boundary="fill")
|
|
357
|
+
|
|
358
|
+
arr_3d = jax.vmap(convolve_partial, in_axes=(2, None), out_axes=(2))(arr_3d, kernel_xy)
|
|
359
|
+
arr_3d = jnp.asarray(arr_3d, dtype=bool)
|
|
360
|
+
arr_3d = arr_3d & reduction_arr
|
|
361
|
+
|
|
362
|
+
arr_3d = jax.vmap(convolve_partial, in_axes=(1, None), out_axes=(1))(arr_3d, kernel_xz)
|
|
363
|
+
arr_3d = jnp.asarray(arr_3d, dtype=bool)
|
|
364
|
+
arr_3d = arr_3d & reduction_arr
|
|
365
|
+
|
|
366
|
+
arr_3d = jax.vmap(convolve_partial, in_axes=(0, None), out_axes=(0))(arr_3d, kernel_yz)
|
|
367
|
+
arr_3d = jnp.asarray(arr_3d, dtype=bool)
|
|
368
|
+
arr_3d = arr_3d & reduction_arr
|
|
369
|
+
|
|
370
|
+
return arr_3d
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def binary_median_filter(
|
|
374
|
+
arr_3d: jax.Array,
|
|
375
|
+
kernel_sizes: tuple[int, int, int],
|
|
376
|
+
padding_cfg: PaddingConfig,
|
|
377
|
+
) -> jax.Array:
|
|
378
|
+
"""Applies a binary median filter to a 3D array.
|
|
379
|
+
|
|
380
|
+
Implements a median filter for binary data by using convolution
|
|
381
|
+
and thresholding. The filter is applied separately along each axis
|
|
382
|
+
using the specified kernel sizes.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
arr_3d: 3D binary input array.
|
|
386
|
+
kernel_sizes: Tuple of (kx, ky, kz) specifying filter size in each dimension.
|
|
387
|
+
padding_cfg: Configuration for padding the input array.
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
Filtered binary array.
|
|
391
|
+
"""
|
|
392
|
+
# padding
|
|
393
|
+
padded_arr, orig_slice = advanced_padding(arr_3d, padding_cfg)
|
|
394
|
+
padded_arr = padded_arr.astype(jnp.float32)
|
|
395
|
+
# create filter kernel and convolve (avg filter)
|
|
396
|
+
for axis, k_size in enumerate(kernel_sizes):
|
|
397
|
+
shape_list = [1, 1, 1]
|
|
398
|
+
shape_list[axis] = k_size
|
|
399
|
+
shape_tpl = tuple(shape_list)
|
|
400
|
+
kernel = jnp.ones(shape=shape_tpl, dtype=jnp.float32)
|
|
401
|
+
padded_arr = jax.scipy.signal.convolve(padded_arr, kernel, mode="same", method="direct")
|
|
402
|
+
# discretize again to get median
|
|
403
|
+
kernel_sum = jnp.prod(jnp.asarray(kernel_sizes))
|
|
404
|
+
result = padded_arr / kernel_sum
|
|
405
|
+
result = result[orig_slice]
|
|
406
|
+
result = jnp.round(result).astype(arr_3d.dtype)
|
|
407
|
+
return result
|