-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgraphviz_helper.py
244 lines (213 loc) · 6.86 KB
/
graphviz_helper.py
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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
import os
import subprocess
import sys
import tempfile
from typing import List, Optional, Tuple, Union
import numpy as np
from onnx import ModelProto
def _find_in_PATH(prog: str) -> Optional[str]:
"""
Looks into every path mentioned in ``%PATH%`` a specific file,
it raises an exception if not found.
:param prog: program to look for
:return: path
"""
sep = ";" if sys.platform.startswith("win") else ":"
path = os.environ["PATH"]
for p in path.split(sep):
f = os.path.join(p, prog)
if os.path.exists(f):
return p
return None
def _find_graphviz_dot(exc: bool = True) -> str:
"""
Determines the path to graphviz (on Windows),
the function tests the existence of versions 34 to 45
assuming it was installed in a standard folder:
``C:\\Program Files\\MiKTeX 2.9\\miktex\\bin\\x64``.
:param exc: raise exception of be silent
:return: path to dot
:raises FileNotFoundError: if graphviz not found
"""
if sys.platform.startswith("win"):
version = list(range(34, 60))
version.extend([f"{v}.1" for v in version])
for v in version:
graphviz_dot = f"C:\\Program Files (x86)\\Graphviz2.{v}\\bin\\dot.exe"
if os.path.exists(graphviz_dot):
return graphviz_dot
extra = ["build/update_modules/Graphviz/bin"]
for ext in extra:
graphviz_dot = os.path.join(ext, "dot.exe")
if os.path.exists(graphviz_dot):
return graphviz_dot
p = _find_in_PATH("dot.exe")
if p is None:
if exc:
raise FileNotFoundError(
f"Unable to find graphviz, look into paths such as {graphviz_dot}."
)
return None
return os.path.join(p, "dot.exe")
# linux
return "dot"
def _run_subprocess(
args: List[str],
cwd: Optional[str] = None,
):
assert not isinstance(
args, str
), "args should be a sequence of strings, not a string."
p = subprocess.Popen(
args,
cwd=cwd,
shell=False,
env=os.environ,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
raise_exception = False
output = ""
while True:
output = p.stdout.readline().decode(errors="ignore")
if output == "" and p.poll() is not None:
break
if output:
if (
"fatal error" in output
or "CMake Error" in output
or "gmake: ***" in output
or "): error C" in output
or ": error: " in output
):
raise_exception = True
p.poll()
error = p.stderr.readline().decode(errors="ignore")
p.stdout.close()
if error and raise_exception:
raise RuntimeError(
f"An error was found in the output. The build is stopped."
f"\n{output}\n---\n{error}"
)
return output + "\n" + error
def _run_graphviz(filename: str, image: str, engine: str = "dot") -> str:
"""
Run :epkg:`Graphviz`.
:param filename: filename which contains the graph definition
:param image: output image
:param engine: *dot* or *neato*
:return: output of graphviz
"""
ext = os.path.splitext(image)[-1]
assert ext in {
".png",
".bmp",
".fig",
".gif",
".ico",
".jpg",
".jpeg",
".pdf",
".ps",
".svg",
".vrml",
".tif",
".tiff",
".wbmp",
}, f"Unexpected extension {ext!r} for {image!r}."
if sys.platform.startswith("win"):
bin_ = os.path.dirname(_find_graphviz_dot())
# if bin not in os.environ["PATH"]:
# os.environ["PATH"] = os.environ["PATH"] + ";" + bin
exe = os.path.join(bin_, engine)
else:
exe = engine
if os.path.exists(image):
os.remove(image)
cmd = [exe, f"-T{ext[1:]}", filename, "-o", image]
output = _run_subprocess(cmd)
assert os.path.exists(image), (
f"Unable to find {image!r}, command line is "
f"{' '.join(cmd)!r}, Graphviz failed due to\n{output}"
)
return output
def draw_graph_graphviz(
dot: Union[str, ModelProto],
image: str,
engine: str = "dot",
) -> str:
"""
Draws a graph using :epkg:`Graphviz`.
:param dot: dot graph or ModelProto
:param image: output image, None, just returns the output
:param engine: *dot* or *neato*
:return: :epkg:`Graphviz` output or
the dot text if *image* is None
The function creates a temporary file to store the dot file if *image* is not None.
"""
if isinstance(dot, ModelProto):
from .dot_plot import to_dot
sdot = to_dot(dot)
else:
sdot = dot
with tempfile.NamedTemporaryFile(delete=False) as fp:
fp.write(sdot.encode("utf-8"))
fp.close()
filename = fp.name
assert os.path.exists(
filename
), f"File {filename!r} cannot be created to store the graph."
out = _run_graphviz(filename, image, engine=engine)
assert os.path.exists(
image
), f"Graphviz failed with no reason, {image!r} not found, output is {out}."
os.remove(filename)
return out
def plot_dot(
dot: Union[str, ModelProto],
ax: Optional["matplotlib.axis.Axis"] = None, # noqa: F821
engine: str = "dot",
figsize: Optional[Tuple[int, int]] = None,
) -> "matplotlib.axis.Axis": # noqa: F821
"""
Draws a dot graph into a matplotlib graph.
:param dot: dot graph or ModelProto
:param image: output image, None, just returns the output
:param engine: *dot* or *neato*
:param figsize: figsize of ax is None
:return: :epkg:`Graphviz` output or, the dot text if *image* is None
.. plot::
import matplotlib.pyplot as plt
import onnx.parser
from onnx_array_api.plotting.graphviz_helper import plot_dot
model = onnx.parser.parse_model(
'''
<ir_version: 8, opset_import: [ "": 18]>
agraph (float[N] x) => (float[N] z) {
two = Constant <value_float=2.0> ()
four = Add(two, two)
z = Mul(four, four)
}
''')
ax = plot_dot(model)
ax.set_title("Dummy graph")
plt.show()
"""
if ax is None:
import matplotlib.pyplot as plt
_, ax = plt.subplots(1, 1, figsize=figsize)
clean = True
else:
clean = False
from PIL import Image
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as fp:
fp.close()
draw_graph_graphviz(dot, fp.name, engine=engine)
img = np.asarray(Image.open(fp.name))
os.remove(fp.name)
ax.imshow(img)
if clean:
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax.get_figure().tight_layout()
return ax