Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions makestudy
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ fi

if [ "$#" -eq 1 ]; then
studydir="$(basename "${graphml%.*}")"
studydir="${studydir%.graphml}"
else
studydir="$2"
fi
Expand Down
21 changes: 21 additions & 0 deletions makestudy.bat
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,23 @@ set "ext1=%~x1"
set "file1=!file1:\=\\!"
set "dir1=!dir1:\=\\!"

:: when the input file is 'something.graphml.jpg'
if /I "!name1:~-8!"==".graphml" set "name1=!name1:~0,-8!"

:: If the second argument (file2) is not provided
if not defined file2 (
if /I "%ext1%"==".graphml" (
echo python mkconcore.py "!file1!" "!dir1!" "!name1!" windows
python mkconcore.py "!file1!" "!dir1!" "!name1!" windows
) else if /I "%ext1%"==".png" (
echo python mkconcore.py "!file1!" "!dir1!" "!name1!" windows
python mkconcore.py "!file1!" "!dir1!" "!name1!" windows
) else if /I "%ext1%"==".jpg" (
echo python mkconcore.py "!file1!" "!dir1!" "!name1!" windows
python mkconcore.py "!file1!" "!dir1!" "!name1!" windows
) else if /I "%ext1%"==".jpeg" (
echo python mkconcore.py "!file1!" "!dir1!" "!name1!" windows
python mkconcore.py "!file1!" "!dir1!" "!name1!" windows
) else (
echo python mkconcore.py "!dir1!!name1!.graphml" "!dir1!" "!name1!" windows
python mkconcore.py "!dir1!!name1!.graphml" "!dir1!" "!name1!" windows
Expand All @@ -25,6 +37,15 @@ if not defined file2 (
if /I "%ext1%"==".graphml" (
echo python mkconcore.py "!file1!" "!dir1!" "!file2!" windows
python mkconcore.py "!file1!" "!dir1!" "!file2!" windows
) else if /I "%ext1%"==".png" (
echo python mkconcore.py "!file1!" "!dir1!" "!file2!" windows
python mkconcore.py "!file1!" "!dir1!" "!file2!" windows
) else if /I "%ext1%"==".jpg" (
echo python mkconcore.py "!file1!" "!dir1!" "!file2!" windows
python mkconcore.py "!file1!" "!dir1!" "!file2!" windows
) else if /I "%ext1%"==".jpeg" (
echo python mkconcore.py "!file1!" "!dir1!" "!file2!" windows
python mkconcore.py "!file1!" "!dir1!" "!file2!" windows
) else (
echo python mkconcore.py "!dir1!!name1!.graphml" "!dir1!" "!file2!" windows
python mkconcore.py "!dir1!!name1!.graphml" "!dir1!" "!file2!" windows
Expand Down
32 changes: 31 additions & 1 deletion mkconcore.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@
import sys
import os
import shutil
import stat
import stat
import tempfile
import copy_with_port_portname
import numpy as np
import shlex # Added for POSIX shell escaping
Expand Down Expand Up @@ -144,6 +145,35 @@ def _resolve_concore_path():

ORIGINAL_CWD = os.getcwd()
GRAPHML_FILE = os.path.abspath(sys.argv[1])

# --- Image input support: extract embedded GraphML from PNG/JPG ---
_IMAGE_EXTS = {".png", ".jpg", ".jpeg"}
_tmp_graphml_file = None # keep reference so the temp file isn't deleted early
if os.path.splitext(GRAPHML_FILE)[1].lower() in _IMAGE_EXTS:
Comment on lines +149 to +152
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that image inputs are supported, the script’s usage: py mkconcore.py file.graphml ... message (earlier in the file) is misleading. Consider updating the usage/help text to mention .png/.jpg/.jpeg so users don’t assume only .graphml works.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will document this properly later in guide.md.

try:
import importlib.util
_tool_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tools", "extract_graphml.py")
_spec = importlib.util.spec_from_file_location("extract_graphml", _tool_path)
_extract_graphml_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_extract_graphml_module)
_extract_graphml = _extract_graphml_module.extract_graphml
_graphml_str = _extract_graphml(GRAPHML_FILE)
if _graphml_str is None:
print(f"No embedded GraphML found in '{GRAPHML_FILE}'.")
sys.exit(1)
_tmp_graphml_file = tempfile.NamedTemporaryFile(
mode="w", suffix=".graphml", delete=False, encoding="utf-8"
)
_tmp_graphml_file.write(_graphml_str)
_tmp_graphml_file.close()
GRAPHML_FILE = _tmp_graphml_file.name
import atexit as _atexit
_atexit.register(os.unlink, GRAPHML_FILE)
Comment on lines +164 to +171
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The temp file cleanup uses atexit.register(os.unlink, GRAPHML_FILE). If the file is already removed (or never created due to a partial failure after creation), os.unlink will raise at process exit and can produce noisy shutdown output. Consider registering a small cleanup wrapper that ignores FileNotFoundError.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This issue exists before this PR also for normal graphml, I can raise a separate PR for all together, not necessary to fix right now

except Exception as _e:
print(f"Failed to extract GraphML from image: {_e}")
sys.exit(1)
# ------------------------------------------------------------------

TRIMMED_LOGS = True
CONCOREPATH = _resolve_concore_path()
CPPWIN = os.environ.get("CONCORE_CPPWIN", "g++") #Windows C++ 6/22/21
Expand Down
189 changes: 189 additions & 0 deletions tools/extract_graphml.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
from typing import Optional
import struct
import zlib
import sys
import os


# ---------------------------------------------------------------------------
# PNG extraction
# ---------------------------------------------------------------------------

_PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"


def _extract_from_png(data: bytes) -> Optional[str]:
#Return the GraphML string embedded in a PNG file, or None
if data[:8] != _PNG_SIGNATURE:
return None

pos = 8
while pos + 12 <= len(data):
length = struct.unpack(">I", data[pos : pos + 4])[0]
chunk_type = data[pos + 4 : pos + 8]
chunk_data = data[pos + 8 : pos + 8 + length]

if chunk_type == b"tEXt":
try:
null_pos = chunk_data.index(b"\x00")
except ValueError:
pos += 12 + length
continue
keyword = chunk_data[:null_pos].decode("latin-1")
if keyword.lower() == "graphml":
text = chunk_data[null_pos + 1 :].decode("latin-1")
return text

elif chunk_type == b"zTXt":
try:
null_pos = chunk_data.index(b"\x00")
except ValueError:
pos += 12 + length
continue
keyword = chunk_data[:null_pos].decode("latin-1")
if keyword.lower() == "graphml":
# compression method byte follows null, then deflate data
compressed = chunk_data[null_pos + 2 :]
try:
text = zlib.decompress(compressed).decode("utf-8")
return text
except Exception:
pass

elif chunk_type == b"iTXt":
# Keyword, null, compression flag, compression method, lang,
# translated keyword, null, text (may be compressed)
try:
null_pos = chunk_data.index(b"\x00")
keyword = chunk_data[:null_pos].decode("latin-1")
if keyword.lower() == "graphml":
rest = chunk_data[null_pos + 1 :]
comp_flag = rest[0]
comp_method = rest[1]
rest = rest[2:]
# skip language tag
second_null = rest.index(b"\x00")
rest = rest[second_null + 1 :]
# skip translated keyword
third_null = rest.index(b"\x00")
text_bytes = rest[third_null + 1 :]
if comp_flag == 1:
text_bytes = zlib.decompress(text_bytes)
return text_bytes.decode("utf-8")
except Exception:
pass

elif chunk_type == b"IEND":
break

pos += 12 + length

return None


# ---------------------------------------------------------------------------
# JPEG extraction
# ---------------------------------------------------------------------------

_JPEG_SOI = b"\xff\xd8"


def _extract_from_jpeg(data: bytes) -> Optional[str]:
#Return the GraphML string embedded in a JPEG file, or None.
if data[:2] != _JPEG_SOI:
return None

# Strategy 1: look for raw <?xml ... </graphml> span anywhere in the file.
xml_start = data.find(b"<?xml")
if xml_start == -1:
# Also try without XML declaration
xml_start = data.find(b"<graphml")
if xml_start == -1:
return None

graphml_end = data.find(b"</graphml>", xml_start)
if graphml_end == -1:
return None

xml_bytes = data[xml_start : graphml_end + len(b"</graphml>")]
try:
return xml_bytes.decode("utf-8")
except UnicodeDecodeError:
try:
return xml_bytes.decode("latin-1")
except Exception:
return None


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------


def extract_graphml(image_path: str) -> Optional[str]:
try:
with open(image_path, "rb") as fh:
data = fh.read()
except OSError as exc:
print(f"[extract_graphml] Cannot open '{image_path}': {exc}", file=sys.stderr)
return None

# Detect by magic bytes
if data[:8] == _PNG_SIGNATURE:
return _extract_from_png(data)

if data[:2] == _JPEG_SOI:
return _extract_from_jpeg(data)

print(
f"[extract_graphml] '{image_path}' is neither PNG nor JPEG.",
file=sys.stderr,
)
return None


def extract_graphml_to_file(image_path: str, output_path: Optional[str] = None) -> Optional[str]:
graphml = extract_graphml(image_path)
if graphml is None:
print(
f"[extract_graphml] No embedded GraphML found in '{image_path}'.",
file=sys.stderr,
)
return None

if output_path is None:
base, _ = os.path.splitext(image_path)
# Handle double-extension names like "foo.graphml.png" -> "foo.graphml"
if base.endswith(".graphml"):
output_path = base
else:
output_path = base + ".graphml"

try:
with open(output_path, "w", encoding="utf-8") as fh:
fh.write(graphml)
print(f"[extract_graphml] Extracted GraphML written to '{output_path}'.")
return output_path
except OSError as exc:
print(
f"[extract_graphml] Cannot write to '{output_path}': {exc}",
file=sys.stderr,
)
return None


# ---------------------------------------------------------------------------
# CLI entry point
# ---------------------------------------------------------------------------

if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python extract_graphml.py <image.png|jpg|jpeg> [output.graphml]")
sys.exit(1)

in_path = sys.argv[1]
out_path = sys.argv[2] if len(sys.argv) >= 3 else None

result = extract_graphml_to_file(in_path, out_path)
if result is None:
sys.exit(1)
Loading