File size: 7,725 Bytes
1ea7ba6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env python
"""SpiceFusionNet architecture figure, TResNet-food reference style: 3D tensor slabs,
small pastel boxes, an MBConv-block call-out above the flow, a snaking backbone so the
three streams converge cleanly, operation circles, and class-circle outputs.
Saves outputs/spicefusionnet_arch.{png,pdf}.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch, FancyArrowPatch, Rectangle, Circle, Polygon
from matplotlib.colors import to_rgb

matplotlib.rcParams.update({"font.family": "DejaVu Sans", "font.size": 7})
ROOT = Path("/mnt/d/SpiceNet") if Path("/mnt/d/SpiceNet").exists() else Path("D:/SpiceNet")
BLOCK, PINK, YEL, GRN, PUR = "#c9dcf2", "#f2c9c9", "#f6e6b0", "#d3ecd0", "#dccdee"
SLAB, EDGE, ARR = "#e9eef5", "#555", "#555"


def _d(c, f): r, g, b = to_rgb(c); return (r * f, g * f, b * f)


fig, ax = plt.subplots(figsize=(13, 6.7))
ax.set_xlim(0, 13); ax.set_ylim(2.1, 8.9); ax.axis("off")


def box(x, y, w, h, label, fc=BLOCK, fs=6.6):
    ax.add_patch(FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.006,rounding_size=0.04",
                                fc=fc, ec=EDGE, lw=0.8, mutation_scale=1, zorder=4))
    ax.text(x + w / 2, y + h / 2, label, ha="center", va="center", fontsize=fs, zorder=5)


def slab(x, y, w, h, fc=SLAB, d=0.15, label="", n=1, fs=6.6):
    for i in range(n - 1, -1, -1):
        ox, oy = x + i * 0.1, y + i * 0.1
        ax.add_patch(Polygon([(ox, oy + h), (ox + w, oy + h), (ox + w + d, oy + h + d), (ox + d, oy + h + d)],
                             fc=_d(fc, .9), ec=EDGE, lw=0.7, zorder=3 + n - i))
        ax.add_patch(Polygon([(ox + w, oy), (ox + w + d, oy + d), (ox + w + d, oy + h + d), (ox + w, oy + h)],
                             fc=_d(fc, .78), ec=EDGE, lw=0.7, zorder=3 + n - i))
        ax.add_patch(Rectangle((ox, oy), w, h, fc=fc, ec=EDGE, lw=0.8, zorder=3 + n - i))
    if label:
        ax.text(x + w / 2, y - 0.12, label, ha="center", va="top", fontsize=fs)


def opc(x, y, s, r=0.13):
    ax.add_patch(Circle((x, y), r, fc="white", ec=EDGE, lw=0.9, zorder=6))
    ax.text(x, y, s, ha="center", va="center", fontsize=8, zorder=7)


def ar(x1, y1, x2, y2, lw=0.85, ls="-"):
    ax.add_patch(FancyArrowPatch((x1, y1), (x2, y2), arrowstyle="-|>", mutation_scale=7,
                                 lw=lw, ls=ls, color=ARR, shrinkA=1.5, shrinkB=1.5, zorder=2))


bw = 1.15
# ---------------- MBConv call-out (top) ----------------
cy = 8.35
mx = 3.6; prev = None
for lab, w, fc in [("$1{\\times}1$ exp", 0.9, BLOCK), ("DWConv $k$", 0.9, BLOCK),
                   ("SE", 0.55, YEL), ("$1{\\times}1$ proj", 0.9, BLOCK)]:
    box(mx, cy - 0.25, w, 0.5, lab, fc, 6.3)
    if prev:
        ar(prev, cy, mx, cy)
    prev = mx + w; mx += w + 0.28
opc(mx + 0.02, cy, "$+$")
ax.annotate("", xy=(mx + 0.02, cy + 0.12), xytext=(3.8, cy + 0.25),
            arrowprops=dict(arrowstyle="-|>", color=ARR, lw=0.8, connectionstyle="arc3,rad=-0.45"))
ax.text(3.5, cy, "MBConv", ha="right", va="center", fontsize=6.6, style="italic")

# ---------------- backbone, snaking two rows ----------------
y1, y2 = 7.15, 5.55
slab(0.25, y1 - 0.5, 0.5, 1.0, SLAB, label="input\n$224^2$", n=3, fs=6.3)
row1 = [("Stem", "$3{\\times}3$/2路48", PINK), ("S1", "MBC1 k3路24$\\times$2", BLOCK),
        ("S2", "MBC6 k3路32$\\times$4", BLOCK), ("S3", "MBC6 k5路56$\\times$4", BLOCK),
        ("S4", "MBC6 k3路112$\\times$6", BLOCK)]
row2 = [("S5", "MBC6 k5路160$\\times$6", BLOCK), ("S6", "MBC6 k5路272$\\times$8", BLOCK),
        ("S7", "MBC6 k3路448$\\times$2", BLOCK), ("Head", "$1{\\times}1$+GAP路1792", PINK)]
bx = 1.3; prev = 0.9; s2c = None
for name, cfg, fc in row1:
    box(bx, y1 - 0.38, bw, 0.76, f"$\\bf{{{name}}}$\n{cfg}", fc, 6.1)
    ar(prev, y1, bx, y1)
    if name == "S2":
        s2c = bx + bw / 2
    prev = bx + bw; bx += bw + 0.05
xr = prev            # right edge of row1
ax.annotate("", xy=(xr - bw / 2, y2 + 0.38), xytext=(xr - bw / 2, y1 - 0.38),
            arrowprops=dict(arrowstyle="-|>", color=ARR, lw=0.85, connectionstyle="arc3,rad=0.0"))  # turn down
ax.annotate("", xy=(s2c, cy - 0.25), xytext=(s2c, y1 + 0.38),
            arrowprops=dict(arrowstyle="<->", color=EDGE, lw=0.9))                                   # callout link
bx = xr - bw; prev = None
for name, cfg, fc in row2:                      # right to left
    box(bx - bw, y2 - 0.38, bw, 0.76, f"$\\bf{{{name}}}$\n{cfg}", fc, 6.1)
    if prev is not None:
        ar(prev, y2, bx, y2)
    prev = bx - bw; bx -= bw + 0.05
ar(prev, y2, prev - 0.28, y2)
slab(prev - 0.6, y2 - 0.5, 0.3, 1.0, "#f2c79a", label="$f_{\\mathrm{cnn}}$\n1792", fs=6.3)
fcnn_c = (prev - 0.45, y2)

# ---------------- descriptor branches (lower left) ----------------
yT, yK = 3.9, 2.85
box(1.3, yT - 0.26, 1.5, 0.52, "LBP+GLCM 58-d", YEL, 6.2)
box(2.95, yT - 0.26, 1.05, 0.52, "MLP$\\to$256", YEL, 6.2)
slab(4.2, yT - 0.4, 0.3, 0.8, "#f0d9a8", label="$f_{\\mathrm{tex}}$\n256", fs=6.2)
box(1.3, yK - 0.26, 1.5, 0.52, "HSV hist 100-d", GRN, 6.2)
box(2.95, yK - 0.26, 1.05, 0.52, "MLP$\\to$128", GRN, 6.2)
slab(4.2, yK - 0.4, 0.3, 0.8, "#c9e3c6", label="$f_{\\mathrm{col}}$\n128", fs=6.2)
ax.text(2.6, 4.55, "hand-built descriptors of the input image", fontsize=6.2, style="italic", ha="center")
ar(2.8, yT, 2.95, yT); ar(4.0, yT, 4.2, yT)
ar(2.8, yK, 2.95, yK); ar(4.0, yK, 4.2, yK)

# ---------------- attention fusion + classifier + output ----------------
yf = 3.35
fx0 = 5.2
ax.add_patch(FancyBboxPatch((fx0, yf - 1.05), 2.75, 2.1, boxstyle="round,pad=0.02",
                            fc="#f4f1f8", ec=EDGE, lw=0.85, ls="--", zorder=1))
ax.text(fx0 + 1.37, yf + 0.88, "attention fusion", ha="center", fontsize=6.6, style="italic")
ar(fcnn_c[0], y2 - 0.5, fx0, yf + 0.55)      # f_cnn -> fusion
ar(4.5, yT, fx0, yf + 0.1)                    # f_tex -> fusion
ar(4.5, yK, fx0, yf - 0.4)                    # f_col -> fusion
box(fx0 + 0.15, yf + 0.3, 1.2, 0.4, "concat 2176", "white", 6.2)
box(fx0 + 1.45, yf + 0.3, 1.15, 0.4, "gate softmax", "white", 6.2)
ar(fx0 + 1.35, yf + 0.5, fx0 + 1.45, yf + 0.5)
opc(fx0 + 2.0, yf - 0.4, "$\\times$")
ar(fx0 + 2.0, yf + 0.3, fx0 + 2.0, yf - 0.28)
box(fx0 + 0.15, yf - 0.6, 1.2, 0.4, "weighted sum", "white", 6.2)
ar(fx0 + 1.87, yf - 0.4, fx0 + 1.35, yf - 0.4)
slab(fx0 + 2.85, yf - 0.4, 0.26, 0.8, "#d8c9ea", label="fused\n2176", fs=6.2)
ar(fx0 + 2.62, yf, fx0 + 2.85, yf)

cx = 8.55
ax.text(cx + 1.0, yf + 0.75, "classifier", ha="center", fontsize=6.6, style="italic")
ar(fx0 + 3.15, yf, cx, yf)
cbx = cx; prev = None
for lab, c in [("FC$\\to$512", PUR), ("BN路ReLU", PUR), ("Drop", PUR), ("FC$\\to$22", PUR), ("soft\nmax", YEL)]:
    box(cbx, yf - 0.24, 0.82, 0.48, lab, c, 6.0)
    if prev is not None:
        ar(prev, yf, cbx, yf)
    prev = cbx + 0.82; cbx += 0.82 + 0.05

ox = prev + 0.55
ax.text(ox, yf + 0.95, "22 classes", ha="center", fontsize=6.6, style="italic")
for k, (name, yo) in enumerate([("coriander", yf + 0.62), ("cumin", yf + 0.2),
                                ("$\\vdots$", yf - 0.18), ("nutmeg", yf - 0.55)]):
    if name != "$\\vdots$":
        ax.add_patch(Circle((ox, yo), 0.12, fc="#e8b6b0" if k == 0 else "white", ec=EDGE, lw=0.8, zorder=5))
        ar(prev, yf, ox - 0.12, yo, lw=0.7)
        ax.text(ox + 0.2, yo, name, ha="left", va="center", fontsize=6.3,
                color="#c0504d" if k == 0 else "#333", fontweight="bold" if k == 0 else "normal")
    else:
        ax.text(ox, yo, name, ha="center", va="center", fontsize=8)

fig.tight_layout(pad=0.3)
out = ROOT / "outputs" / "spicefusionnet_arch"
fig.savefig(str(out) + ".png", dpi=300, bbox_inches="tight")
fig.savefig(str(out) + ".pdf", bbox_inches="tight")
print("saved", out)