Skip to content

Commit f3bf314

Browse files
committed
add feature for embedded images
1 parent dccbcd4 commit f3bf314

4 files changed

Lines changed: 238 additions & 1 deletion

File tree

makestudy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ fi
2626

2727
if [ "$#" -eq 1 ]; then
2828
studydir="$(basename "${graphml%.*}")"
29+
studydir="${studydir%.graphml}"
2930
else
3031
studydir="$2"
3132
fi

makestudy.bat

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,23 @@ set "ext1=%~x1"
1212
set "file1=!file1:\=\\!"
1313
set "dir1=!dir1:\=\\!"
1414

15+
:: when the input file is 'something.graphml.jpg'
16+
set "name1=!name1:.graphml=!"
17+
1518
:: If the second argument (file2) is not provided
1619
if not defined file2 (
1720
if /I "%ext1%"==".graphml" (
1821
echo python mkconcore.py "!file1!" "!dir1!" "!name1!" windows
1922
python mkconcore.py "!file1!" "!dir1!" "!name1!" windows
23+
) else if /I "%ext1%"==".png" (
24+
echo python mkconcore.py "!file1!" "!dir1!" "!name1!" windows
25+
python mkconcore.py "!file1!" "!dir1!" "!name1!" windows
26+
) else if /I "%ext1%"==".jpg" (
27+
echo python mkconcore.py "!file1!" "!dir1!" "!name1!" windows
28+
python mkconcore.py "!file1!" "!dir1!" "!name1!" windows
29+
) else if /I "%ext1%"==".jpeg" (
30+
echo python mkconcore.py "!file1!" "!dir1!" "!name1!" windows
31+
python mkconcore.py "!file1!" "!dir1!" "!name1!" windows
2032
) else (
2133
echo python mkconcore.py "!dir1!!name1!.graphml" "!dir1!" "!name1!" windows
2234
python mkconcore.py "!dir1!!name1!.graphml" "!dir1!" "!name1!" windows
@@ -25,6 +37,15 @@ if not defined file2 (
2537
if /I "%ext1%"==".graphml" (
2638
echo python mkconcore.py "!file1!" "!dir1!" "!file2!" windows
2739
python mkconcore.py "!file1!" "!dir1!" "!file2!" windows
40+
) else if /I "%ext1%"==".png" (
41+
echo python mkconcore.py "!file1!" "!dir1!" "!file2!" windows
42+
python mkconcore.py "!file1!" "!dir1!" "!file2!" windows
43+
) else if /I "%ext1%"==".jpg" (
44+
echo python mkconcore.py "!file1!" "!dir1!" "!file2!" windows
45+
python mkconcore.py "!file1!" "!dir1!" "!file2!" windows
46+
) else if /I "%ext1%"==".jpeg" (
47+
echo python mkconcore.py "!file1!" "!dir1!" "!file2!" windows
48+
python mkconcore.py "!file1!" "!dir1!" "!file2!" windows
2849
) else (
2950
echo python mkconcore.py "!dir1!!name1!.graphml" "!dir1!" "!file2!" windows
3051
python mkconcore.py "!dir1!!name1!.graphml" "!dir1!" "!file2!" windows

mkconcore.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@
6969
import sys
7070
import os
7171
import shutil
72-
import stat
72+
import stat
73+
import tempfile
7374
import copy_with_port_portname
7475
import numpy as np
7576
import shlex # Added for POSIX shell escaping
@@ -144,6 +145,32 @@ def _resolve_concore_path():
144145

145146
ORIGINAL_CWD = os.getcwd()
146147
GRAPHML_FILE = os.path.abspath(sys.argv[1])
148+
149+
# --- Image input support: extract embedded GraphML from PNG/JPG ---
150+
_IMAGE_EXTS = {".png", ".jpg", ".jpeg"}
151+
_tmp_graphml_file = None # keep reference so the temp file isn't deleted early
152+
if os.path.splitext(GRAPHML_FILE)[1].lower() in _IMAGE_EXTS:
153+
try:
154+
_tool_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tools")
155+
sys.path.insert(0, _tool_dir)
156+
from extract_graphml import extract_graphml as _extract_graphml
157+
_graphml_str = _extract_graphml(GRAPHML_FILE)
158+
if _graphml_str is None:
159+
print(f"No embedded GraphML found in '{GRAPHML_FILE}'.")
160+
sys.exit(1)
161+
_tmp_graphml_file = tempfile.NamedTemporaryFile(
162+
mode="w", suffix=".graphml", delete=False, encoding="utf-8"
163+
)
164+
_tmp_graphml_file.write(_graphml_str)
165+
_tmp_graphml_file.close()
166+
GRAPHML_FILE = _tmp_graphml_file.name
167+
import atexit as _atexit
168+
_atexit.register(os.unlink, GRAPHML_FILE)
169+
except Exception as _e:
170+
print(f"Failed to extract GraphML from image: {_e}")
171+
sys.exit(1)
172+
# ------------------------------------------------------------------
173+
147174
TRIMMED_LOGS = True
148175
CONCOREPATH = _resolve_concore_path()
149176
CPPWIN = os.environ.get("CONCORE_CPPWIN", "g++") #Windows C++ 6/22/21

tools/extract_graphml.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import struct
2+
import zlib
3+
import sys
4+
import os
5+
6+
7+
# ---------------------------------------------------------------------------
8+
# PNG extraction
9+
# ---------------------------------------------------------------------------
10+
11+
_PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
12+
13+
14+
def _extract_from_png(data: bytes) -> str | None:
15+
#Return the GraphML string embedded in a PNG file, or None
16+
if data[:8] != _PNG_SIGNATURE:
17+
return None
18+
19+
pos = 8
20+
while pos + 12 <= len(data):
21+
length = struct.unpack(">I", data[pos : pos + 4])[0]
22+
chunk_type = data[pos + 4 : pos + 8]
23+
chunk_data = data[pos + 8 : pos + 8 + length]
24+
25+
if chunk_type == b"tEXt":
26+
try:
27+
null_pos = chunk_data.index(b"\x00")
28+
except ValueError:
29+
pos += 12 + length
30+
continue
31+
keyword = chunk_data[:null_pos].decode("latin-1")
32+
if keyword.lower() == "graphml":
33+
text = chunk_data[null_pos + 1 :].decode("latin-1")
34+
return text
35+
36+
elif chunk_type == b"zTXt":
37+
try:
38+
null_pos = chunk_data.index(b"\x00")
39+
except ValueError:
40+
pos += 12 + length
41+
continue
42+
keyword = chunk_data[:null_pos].decode("latin-1")
43+
if keyword.lower() == "graphml":
44+
# compression method byte follows null, then deflate data
45+
compressed = chunk_data[null_pos + 2 :]
46+
try:
47+
text = zlib.decompress(compressed).decode("utf-8")
48+
return text
49+
except Exception:
50+
pass
51+
52+
elif chunk_type == b"iTXt":
53+
# Keyword, null, compression flag, compression method, lang,
54+
# translated keyword, null, text (may be compressed)
55+
try:
56+
null_pos = chunk_data.index(b"\x00")
57+
keyword = chunk_data[:null_pos].decode("latin-1")
58+
if keyword.lower() == "graphml":
59+
rest = chunk_data[null_pos + 1 :]
60+
comp_flag = rest[0]
61+
comp_method = rest[1]
62+
rest = rest[2:]
63+
# skip language tag
64+
second_null = rest.index(b"\x00")
65+
rest = rest[second_null + 1 :]
66+
# skip translated keyword
67+
third_null = rest.index(b"\x00")
68+
text_bytes = rest[third_null + 1 :]
69+
if comp_flag == 1:
70+
text_bytes = zlib.decompress(text_bytes)
71+
return text_bytes.decode("utf-8")
72+
except Exception:
73+
pass
74+
75+
elif chunk_type == b"IEND":
76+
break
77+
78+
pos += 12 + length
79+
80+
return None
81+
82+
83+
# ---------------------------------------------------------------------------
84+
# JPEG extraction
85+
# ---------------------------------------------------------------------------
86+
87+
_JPEG_SOI = b"\xff\xd8"
88+
89+
90+
def _extract_from_jpeg(data: bytes) -> str | None:
91+
#Return the GraphML string embedded in a JPEG file, or None.
92+
if data[:2] != _JPEG_SOI:
93+
return None
94+
95+
# Strategy 1: look for raw <?xml ... </graphml> span anywhere in the file.
96+
xml_start = data.find(b"<?xml")
97+
if xml_start == -1:
98+
# Also try without XML declaration
99+
xml_start = data.find(b"<graphml")
100+
if xml_start == -1:
101+
return None
102+
103+
graphml_end = data.find(b"</graphml>", xml_start)
104+
if graphml_end == -1:
105+
return None
106+
107+
xml_bytes = data[xml_start : graphml_end + len(b"</graphml>")]
108+
try:
109+
return xml_bytes.decode("utf-8")
110+
except UnicodeDecodeError:
111+
try:
112+
return xml_bytes.decode("latin-1")
113+
except Exception:
114+
return None
115+
116+
117+
# ---------------------------------------------------------------------------
118+
# Public API
119+
# ---------------------------------------------------------------------------
120+
121+
122+
def extract_graphml(image_path: str) -> str | None:
123+
try:
124+
with open(image_path, "rb") as fh:
125+
data = fh.read()
126+
except OSError as exc:
127+
print(f"[extract_graphml] Cannot open '{image_path}': {exc}", file=sys.stderr)
128+
return None
129+
130+
# Detect by magic bytes
131+
if data[:8] == _PNG_SIGNATURE:
132+
return _extract_from_png(data)
133+
134+
if data[:2] == _JPEG_SOI:
135+
return _extract_from_jpeg(data)
136+
137+
print(
138+
f"[extract_graphml] '{image_path}' is neither PNG nor JPEG.",
139+
file=sys.stderr,
140+
)
141+
return None
142+
143+
144+
def extract_graphml_to_file(image_path: str, output_path: str | None = None) -> str | None:
145+
graphml = extract_graphml(image_path)
146+
if graphml is None:
147+
print(
148+
f"[extract_graphml] No embedded GraphML found in '{image_path}'.",
149+
file=sys.stderr,
150+
)
151+
return None
152+
153+
if output_path is None:
154+
base, _ = os.path.splitext(image_path)
155+
# Handle double-extension names like "foo.graphml.png" -> "foo.graphml"
156+
if base.endswith(".graphml"):
157+
output_path = base
158+
else:
159+
output_path = base + ".graphml"
160+
161+
try:
162+
with open(output_path, "w", encoding="utf-8") as fh:
163+
fh.write(graphml)
164+
print(f"[extract_graphml] Extracted GraphML written to '{output_path}'.")
165+
return output_path
166+
except OSError as exc:
167+
print(
168+
f"[extract_graphml] Cannot write to '{output_path}': {exc}",
169+
file=sys.stderr,
170+
)
171+
return None
172+
173+
174+
# ---------------------------------------------------------------------------
175+
# CLI entry point
176+
# ---------------------------------------------------------------------------
177+
178+
if __name__ == "__main__":
179+
if len(sys.argv) < 2:
180+
print("Usage: python extract_graphml.py <image.png|jpg> [output.graphml]")
181+
sys.exit(1)
182+
183+
in_path = sys.argv[1]
184+
out_path = sys.argv[2] if len(sys.argv) >= 3 else None
185+
186+
result = extract_graphml_to_file(in_path, out_path)
187+
if result is None:
188+
sys.exit(1)

0 commit comments

Comments
 (0)