Skip to content

Commit

Permalink
Add new LARGE_FEATURE_EDGES_STRICT ignore scheme and make it the de…
Browse files Browse the repository at this point in the history
…fault. (#35)

* Add corner ignore scheme

* rephrase

* Fix type annotation

* satisfy mypy

* large feature edges strict

* fix whitespace

* consider corners as edges

* Remove unneeded files

* Update readme and bump version (#21)

* Update readme

* update readme

* update readme

* Version updated from v0.2.0 to v0.3.0
  • Loading branch information
mfschubert authored Jul 12, 2024
1 parent 7cd8e1c commit 935d8a2
Show file tree
Hide file tree
Showing 10 changed files with 323 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tool.bumpversion]
current_version = "v0.2.0"
current_version = "v0.3.0"
commit = true
commit_args = "--no-verify"
tag = true
Expand Down
23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
[![Build Status](https://github.com/NanoComp/imageruler/workflows/CI/badge.svg)](https://github.com/NanoComp/imageruler/actions)
`v0.2.0`
`v0.3.0`

Imageruler is a free Python program to compute the minimum length scale of binary images which are typically designs produced by topology optimization. The algorithm is described in Section 2 of [J. Optical Society of America B, Vol. 42, pp. A161-A176 (2024)](https://opg.optica.org/josab/abstract.cfm?uri=josab-41-2-A161) and is based on morphological transformations implemented using [OpenCV](https://github.com/opencv/opencv). Imageruler also supports 1d binary images.

## Algorithm for Determining Minimum Length Scale

The procedure used by Imageruler for determining the minimum length scale of the solid regions in a binary image involves four steps:

1. Binarize the 2d array $\rho$ representing the image such that each of its elements is a Boolean value for solid (true) and void (false).
2. For a circular-ball [kernel](https://en.wikipedia.org/wiki/Kernel_(image_processing)) with diameter $d$, compute the morphological opening $\mathcal{O}_d(\rho)$ and obtain its difference with the original array via $\mathcal{O}_d(\rho) \oplus \rho$, where $\oplus$ denotes the exclusive-or operator.
3. Check whether $\mathcal{O}_d(\rho) \oplus \rho$ contains a solid pixel within the interior solid regions of $\rho$. If no, $d$ is less than the minimum length scale of solid regions. If yes, $d$ is equal or greater than the minimum length scale of the solid regions. The interior of the solid regions of $\rho$ is obtained by morphological erosion using a "cross" kernel of size $3\times3$ pixels.
4. Use a [binary search](https://en.wikipedia.org/wiki/Binary_search_algorithm) and repeat Steps 2 and 3 to find the smallest $d$ for which the check in Step 3 evaluates to true. This value of $d$ is considered to be the minimum length scale of the solid regions.
1. Binarize the 1d or 2d array $\rho$ representing the image such that each of its elements is a Boolean value for solid (true) and void (false).
2. For a circular-ball [kernel](https://en.wikipedia.org/wiki/Kernel_(image_processing)) with diameter $d$, compute the morphological opening $\mathcal{O}_d(\rho)$ and obtain its difference with the original array via $\mathcal{O}_d(\rho) \oplus \rho$, where $\oplus$ denotes the exclusive-or operator. In the strictest sense, solid pixels in $\mathcal{O}_d(\rho) \oplus \rho$ are violations of the length scale $d$.
3. Identify pixels where violations are to be ignored. The scheme used to identify ignored violating pixels is specified by the `IgnoreScheme`; by default, these include pixels at the edges of large features. Remove any violations to be ignored from consideration.
4. Check whether there are any remaining violations in $\mathcal{O}_d(\rho) \oplus \rho$. If there are no violations pixels, $d$ is less than or equal to the minimum length scale of solid regions.
5. Use a [binary search](https://en.wikipedia.org/wiki/Binary_search_algorithm) and repeat Steps 2 and 3 to find the largest $d$ for which no violating pixels exist. The search has some allowance for non-monotonicity, i.e. situations where there are violations for $d$ but not $d + 1$. This largest value of $d$ for which there are no violations is considered to be the minimum length scale of the solid regions.

To estimate the minimum length scale of the void regions, the binary image is inverted after the binarization of Step 1: $\rho \rightarrow \neg \rho$ such that the solid and void regions are interchanged. The remaining Steps 2-4 are unchanged. This approach is equivalent to computing $\mathcal{C}_d(\rho) \oplus \rho$ in Step 2 and then checking its overlap with the interior pixels of the void regions of $\rho$ in Step 3. $\mathcal{C}_d(\rho)$ denotes morphological closing.
To estimate the minimum length scale of the void regions, the binary image is inverted after the binarization of Step 1: $\rho \rightarrow \neg \rho$ such that the solid and void regions are interchanged. The remaining Steps 2-5 are unchanged. This approach is equivalent to computing $\mathcal{C}_d(\rho) \oplus \rho$ in Step 2 and then checking its overlap with the interior pixels of the void regions of $\rho$ in Step 3. $\mathcal{C}_d(\rho)$ denotes morphological closing.

The minimum length scale of $\rho$ is the smaller of the minimum length scales of the solid and void regions. Rather than determining these separately, it is possible to compute their minimum simultaneously using $\mathcal{O}_d(\rho) \oplus \mathcal{C}_d(\rho)$ in Step 2 and then to check its overlap with the union of the interior pixels of the solid and void regions of $\rho$ in Step 3. This approach involves a single binary search rather than two.
The minimum length scale of $\rho$ is the smaller of the minimum length scales of the solid and void regions. Rather than determining these separately, it is possible in principle to compute their minimum simultaneously using $\mathcal{O}_d(\rho) \oplus \mathcal{C}_d(\rho)$ and then to check its overlap with the union of the interior pixels of the solid and void regions of $\rho$. This approach involves a single binary search rather than two.

For a 1d binary image, the algorithm simply finds the minimum length among all solid or void segments.
## Schemes for Identifying Ignored Violations
The `ignore_scheme` is an optional argument to top-level functions such as `imageruler.minimum_length_scale`. The choice may affect the length scale value reported for a given design; the possible values are as follows:

- `IgnoreScheme.NONE`: a strict scheme in which no violations are ignored.
- `IgnoreScheme.EDGES`: ignores violations for any solid pixel removed by erosion.
- `IgnoreScheme.LARGE_FEATURE_EDGES`: ignores violations at the edges of large features only. A pixel is on the edge of a large feature if it removed by erosion and adjacent to an interior pixel. Interior pixels are those which are solid and surrounded on all sides (in an 8-connected sense) by solid pixels.
- `IgnoreScheme.LARGE_FEATURE_EDGES_STRICT`: the default choice. Similar to `LARGE_FEATURE_EDGES`, but uses a more strict algorithm to detect edges and does not ignore checkerboard patterns.

## Note on Accuracy

Expand Down
85 changes: 84 additions & 1 deletion docs/notebooks/advanced.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Here, we see that the width is now correctly reported, but we are also over-estimating the spacing. Fortunately, there is a third scheme for ignoring iolations, `IgnoreScheme.LARGE_FEATURE_EDGES`, which only ignores violations on the edges of _large_ features. This is actually the default choice, so if we simply call `imageruler.minimum_length_scale` without specifying an ignore scheme, this is what we will get."
"Here, we see that the width is now correctly reported, but we are also over-estimating the spacing. Fortunately, there is a third scheme for ignoring iolations, `IgnoreScheme.LARGE_FEATURE_EDGES_STRICT`, which only ignores violations on the _edges of large features_. This is actually the default choice, so if we simply call `imageruler.minimum_length_scale` without specifying an ignore scheme, this is what we will get."
]
},
{
Expand All @@ -139,6 +139,89 @@
"source": [
"These are the values we hoped for."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Challenging test cases\n",
"\n",
"The various ignore schemes appear quite similar for most designs. However, some designs are problematic, such as checkerboard patterns. Here we show a checkerboard and other test designs, and the measurements reported with different ignore schemes."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"x = onp.asarray([[0, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 0]])\n",
"x = onp.kron(x, onp.ones((7, 9))).astype(bool)\n",
"\n",
"title_str = \"\"\n",
"for scheme in imageruler.IgnoreScheme:\n",
" min_width, min_spacing = imageruler.minimum_length_scale(x, ignore_scheme=scheme)\n",
" title_str += f\"{scheme.name}: {min_width=}, {min_spacing=}\\n\"\n",
"\n",
"plt.imshow(x)\n",
"_ = plt.title(title_str)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"x = onp.asarray([[0, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 0]])\n",
"x = onp.kron(x, onp.ones((7, 9))).astype(bool)\n",
"x[13, :] = False\n",
"\n",
"title_str = \"\"\n",
"for scheme in imageruler.IgnoreScheme:\n",
" min_width, min_spacing = imageruler.minimum_length_scale(x, ignore_scheme=scheme)\n",
" title_str += f\"{scheme.name}: {min_width=}, {min_spacing=}\\n\"\n",
"\n",
"plt.imshow(x)\n",
"_ = plt.title(title_str)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"x = onp.asarray([[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]])\n",
"x = onp.kron(x, onp.ones((10, 10))).astype(bool)\n",
"\n",
"title_str = \"\"\n",
"for scheme in imageruler.IgnoreScheme:\n",
" min_width, min_spacing = imageruler.minimum_length_scale(x, ignore_scheme=scheme)\n",
" title_str += f\"{scheme.name}: {min_width=}, {min_spacing=}\\n\"\n",
"\n",
"plt.imshow(x)\n",
"_ = plt.title(title_str)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"x = onp.asarray([[0, 1, 1, 1, 0], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [0, 1, 1, 1, 0]])\n",
"x = onp.pad(x, ((3, 3), (3, 3))).astype(bool)\n",
"x[2, 5] = True\n",
"\n",
"title_str = \"\"\n",
"for scheme in imageruler.IgnoreScheme:\n",
" min_width, min_spacing = imageruler.minimum_length_scale(x, ignore_scheme=scheme)\n",
" title_str += f\"{scheme.name}: {min_width=}, {min_spacing=}\\n\"\n",
"\n",
"plt.imshow(x)\n",
"_ = plt.title(title_str)"
]
}
],
"metadata": {
Expand Down
2 changes: 1 addition & 1 deletion docs/notebooks/to_designs.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"import matplotlib.pyplot as plt\n",
"from skimage import measure\n",
"\n",
"design = onp.genfromtxt(\"../../reference_designs/Rasmus70nm.csv\", delimiter=\",\")\n",
"design = onp.genfromtxt(\"../../reference_designs/rgb_metalens/ex/Rasmus70nm.csv\", delimiter=\",\")\n",
"design = design > 0.5 # Binarize\n",
"design = onp.rot90(design)\n",
"\n",
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]

name = "imageruler"
version = "v0.2.0"
version = "v0.3.0"
description = "Measure minimum sollid/void lengthscales in binary images."
keywords = ["topology", "optimization", "length scale"]
readme = "README.md"
Expand Down
3 changes: 1 addition & 2 deletions reference_designs/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
# Reference designs
This directory contains several designs which can be used to demonstrate the calculation of length scales.
- Rasmus70nm.csv: from the [RGB metalens](https://github.com/NanoComp/photonics-opt-testbed/tree/main/RGB_metalens) problem of the photonics-opt-testbed repo.
This directory contains several designs which can be used to demonstrate the calculation of length scales. They are taken from the [photonics-opt-testbed](https://github.com/NanoComp/photonics-opt-testbed/) repo.
File renamed without changes.
2 changes: 1 addition & 1 deletion src/imageruler/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Imageruler for measuring minimum lengthscales in binary images."""

__version__ = "v0.2.0"
__version__ = "v0.3.0"
__all__ = [
"IgnoreScheme",
"kernel_for_length_scale",
Expand Down
Loading

0 comments on commit 935d8a2

Please sign in to comment.