File: plot_footprint_decompositions.py

package info (click to toggle)
skimage 0.26.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 32,720 kB
  • sloc: python: 61,600; cpp: 2,592; ansic: 1,591; xml: 1,342; javascript: 1,267; makefile: 135; sh: 16
file content (178 lines) | stat: -rw-r--r-- 7,658 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
"""
================================================
Decompose flat footprints (structuring elements)
================================================

Many footprints (structuring elements) can be decomposed into an equivalent
series of smaller structuring elements. The term "flat" refers to footprints
that only contain values of 0 or 1 (i.e., all methods in
``skimage.morphology.footprints``). Binary dilation operations have an
associative and distributive property that often allows decomposition into
an equivalent series of smaller footprints. Most often this is done to provide
a performance benefit.

As a concrete example, dilation with a square footprint of shape (15, 15) is
equivalent to dilation with a rectangle of shape (15, 1) followed by another
dilation with a rectangle of shape (1, 15). It is also equivalent to 7
consecutive dilations with a square footprint of shape (3, 3).

There are many possible decompositions and which one performs best may be
architecture-dependent.

scikit-image currently provides two forms of automated decomposition. For the
cases of ``square``, ``rectangle`` and ``cube`` footprints, there is an option
for a "separable" decomposition (size > 1 along only one axis at a time).

There is no separable decomposition into 1D operations for some other symmetric
convex shapes, e.g., ``diamond``, ``octahedron`` and ``octagon``. However, it
is possible to provide a "sequence" decomposition based on a series of small
footprints of shape ``(3,) * ndim``.

For simplicity of implementation, all decompositions shown below use only
odd-sized footprints with their origin located at the center of the footprint.
"""

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors
from mpl_toolkits.mplot3d import Axes3D

import skimage as ski


# Generate 2D and 3D structuring elements.
footprint_dict = {
    "square 11x11 (separable)": (
        ski.morphology.footprint_rectangle((11, 11), decomposition=None),
        ski.morphology.footprint_rectangle((11, 11), decomposition="separable"),
    ),
    "square 11x11 (sequence)": (
        ski.morphology.footprint_rectangle((11, 11), decomposition=None),
        ski.morphology.footprint_rectangle((11, 11), decomposition="sequence"),
    ),
    "rectangle 7x11 (separable)": (
        ski.morphology.footprint_rectangle((7, 11), decomposition=None),
        ski.morphology.footprint_rectangle((7, 11), decomposition="separable"),
    ),
    "rectangle 7x11 (sequence)": (
        ski.morphology.footprint_rectangle((7, 11), decomposition=None),
        ski.morphology.footprint_rectangle((7, 11), decomposition="sequence"),
    ),
    "diamond(5) (sequence)": (
        ski.morphology.diamond(5, decomposition=None),
        ski.morphology.diamond(5, decomposition="sequence"),
    ),
    "disk(7, strict_radius=False) (sequence)": (
        ski.morphology.disk(7, strict_radius=False, decomposition=None),
        ski.morphology.disk(7, strict_radius=False, decomposition="sequence"),
    ),
    "disk(7, strict_radius=True) (crosses)": (
        ski.morphology.disk(7, strict_radius=True, decomposition=None),
        ski.morphology.disk(7, strict_radius=True, decomposition="crosses"),
    ),
    "ellipse(4, 9) (crosses)": (
        ski.morphology.ellipse(4, 9, decomposition=None),
        ski.morphology.ellipse(4, 9, decomposition="crosses"),
    ),
    "disk(20) (sequence)": (
        ski.morphology.disk(20, strict_radius=False, decomposition=None),
        ski.morphology.disk(20, strict_radius=False, decomposition="sequence"),
    ),
    "octagon(7, 4) (sequence)": (
        ski.morphology.octagon(7, 4, decomposition=None),
        ski.morphology.octagon(7, 4, decomposition="sequence"),
    ),
    "cube 11x11x11 (separable)": (
        ski.morphology.footprint_rectangle((11, 11, 11), decomposition=None),
        ski.morphology.footprint_rectangle((11, 11, 11), decomposition="separable"),
    ),
    "cube 11x11x11 (sequence)": (
        ski.morphology.footprint_rectangle((11, 11, 11), decomposition=None),
        ski.morphology.footprint_rectangle((11, 11, 11), decomposition="sequence"),
    ),
    "octahedron(7) (sequence)": (
        ski.morphology.octahedron(7, decomposition=None),
        ski.morphology.octahedron(7, decomposition="sequence"),
    ),
    "ball(9) (sequence)": (
        ski.morphology.ball(9, strict_radius=False, decomposition=None),
        ski.morphology.ball(9, strict_radius=False, decomposition="sequence"),
    ),
}

# Visualize the elements

# binary white / blue colormap
cmap = colors.ListedColormap(['white', (0.1216, 0.4706, 0.70588)])

fontdict = dict(fontsize=16, fontweight='bold')
for title, (footprint, footprint_sequence) in footprint_dict.items():
    ndim = footprint.ndim
    num_seq = len(footprint_sequence)
    approximate_decomposition = 'ball' in title or 'disk' in title or 'ellipse' in title
    if approximate_decomposition:
        # Two extra plot in approximate cases to show both:
        # 1.) decomposition=None idea footprint
        # 2.) actual composite footprint corresponding to the sequence
        num_subplots = num_seq + 2
    else:
        # composite and decomposition=None are identical so only 1 extra plot
        num_subplots = num_seq + 1
    fig = plt.figure(figsize=(4 * num_subplots, 5))
    if ndim == 2:
        ax = fig.add_subplot(1, num_subplots, num_subplots)
        ax.imshow(footprint, cmap=cmap, vmin=0, vmax=1)
        if approximate_decomposition:
            ax2 = fig.add_subplot(1, num_subplots, num_subplots - 1)
            footprint_composite = ski.morphology.footprint_from_sequence(
                footprint_sequence
            )
            ax2.imshow(footprint_composite, cmap=cmap, vmin=0, vmax=1)

    else:
        ax = fig.add_subplot(1, num_subplots, num_subplots, projection=Axes3D.name)
        ax.voxels(footprint, cmap=cmap)
        if approximate_decomposition:
            ax2 = fig.add_subplot(
                1, num_subplots, num_subplots - 1, projection=Axes3D.name
            )
            footprint_composite = ski.morphology.footprint_from_sequence(
                footprint_sequence
            )
            ax2.voxels(footprint_composite, cmap=cmap)

    title1 = title.split(' (')[0]
    if approximate_decomposition:
        # plot decomposition=None on a separate axis from the composite
        title = title1 + '\n(decomposition=None)'
    else:
        # for exact cases composite and decomposition=None are identical
        title = title1 + '\n(composite)'
    ax.set_title(title, fontdict=fontdict)
    ax.set_axis_off()
    if approximate_decomposition:
        ax2.set_title(title1 + '\n(composite)', fontdict=fontdict)
        ax2.set_axis_off()

    for n, (fp, num_reps) in enumerate(footprint_sequence):
        npad = [((footprint.shape[d] - fp.shape[d]) // 2,) * 2 for d in range(ndim)]
        fp = np.pad(fp, npad, mode='constant')
        if ndim == 2:
            ax = fig.add_subplot(1, num_subplots, n + 1)
            ax.imshow(fp, cmap=cmap, vmin=0, vmax=1)
        else:
            ax = fig.add_subplot(1, num_subplots, n + 1, projection=Axes3D.name)
            ax.voxels(fp, cmap=cmap)
        title = f"element {n + 1} of {num_seq}\n({num_reps} iteration"
        title += "s)" if num_reps > 1 else ")"
        ax.set_title(title, fontdict=fontdict)
        ax.set_axis_off()
        ax.set_xlabel(f'num_reps = {num_reps}')
    fig.tight_layout()

    # draw a line separating the sequence elements from the composite
    line_pos = num_seq / num_subplots
    line = plt.Line2D([line_pos, line_pos], [0, 1], color="black")
    fig.add_artist(line)

plt.show()