Views
No views yet
[1, 1, H, W] with dynamic H and W (height/width)[1, C, H, W]
C = 1 (internal nodes only)model.config.json stored alongside model.onnx encodes key
preprocessing and decoding parameters (e.g. resize/max_side, padding multiple,
and peak-detection thresholds) so downstream applications can reproduce the
training-time inference behavior consistently.model.onnx – ONNX graph for the node detector.model.config.json – JSON configuration with fields such as:
backbone (e.g. "resnet34")in_channels (1 for grayscale)internal_only / no_root_prednormalize_inputmax_side, pad_multipledecode:
threshwindowper_channel_topkmax_peaksfallback_topkassign_root_leftmost_when_two_classesmodel.onnx for the network and
model.config.json for the preprocessing / peak-decoding logic.onnxruntime to obtain approximate internal node locations from a
grayscale crop of a tree image.1import json
2import numpy as np
3from PIL import Image
4import onnxruntime as ort
5
6# Load config
7with open("model.config.json", "r") as f:
8 cfg = json.load(f)
9
10max_side = cfg["max_side"]
11pad_multiple = cfg["pad_multiple"]
12
13# Load and preprocess image crop
14img = Image.open("tree_crop.png").convert("L")
15w, h = img.size
16
17# Resize with aspect ratio preserved
18scale = min(1.0, max_side / max(w, h))
19new_w = max(1, int(round(w * scale)))
20new_h = max(1, int(round(h * scale)))
21img_resized = img.resize((new_w, new_h), resample=Image.BILINEAR)
22
23# Pad so dimensions are multiples of pad_multiple
24pad_w = (pad_multiple - (new_w % pad_multiple)) % pad_multiple
25pad_h = (pad_multiple - (new_h % pad_multiple)) % pad_multiple
26canvas = Image.new("L", (new_w + pad_w, new_h + pad_h), 0)
27canvas.paste(img_resized, (0, 0))
28
29arr = np.array(canvas, dtype=np.float32) / 255.0
30input_tensor = arr[None, None, :, :] # [1,1,H,W]
31
32# Run ONNX inference (CPU)
33sess = ort.InferenceSession("model.onnx", providers=["CPUExecutionProvider"])
34logits, = sess.run(None, {"input": input_tensor})
35
36# logits: [1, C, H, W] where C=1 for internal heatmap
37logits = logits[0, 0]
38prob = 1.0 / (1.0 + np.exp(-logits))
39
40# Simple thresholding (Treemble uses more advanced NMS/peak limiting)
41thresh = cfg["decode"]["thresh"]
42ys, xs = np.where(prob > thresh)
43
44# Map back to original crop coordinates
45sx = new_w / w
46sy = new_h / h
47coords = [((x / sx), (y / sy)) for x, y in zip(xs, ys)]
48
49print("Detected internal-node candidates (sample):", coords[:20])decode section of the config.Treemble: A Graphical Tool to Generate Newick Strings from Phylogenetic Tree Images.
John B. Allard and Sudhir Kumar (2025).
arXiv: 2508.07081. DOI: 10.48550/arXiv.2508.07081