packages/yoloe/src/decode.nu — turn YOLOE's output0 into boxes.
output0 is [1, 4+nc+nm, na] (na = 8400 anchors over 3 scales): the first 4 channels are the box (cx,cy,w,h, already decoded to pixels in the letterboxed frame), the next nc are per-class scores (already sigmoid), the rest are segmentation mask coefficients (ignored here). For each anchor we take the best class above a threshold, then non-max suppress.
: Detection { i cls f score f cx f cy f w f h i ai }A detection in letterboxed-pixel centre/size coordinates. ai is the anchor index it came from — needed to read the 32 mask coefficients (output0 channels 4+nc..4+nc+nm at that anchor) for segmentation.
@ yolo_decode GpuHost out__h i na i nc f thresh → ( Vec Detection )Decode the best detections above thresh. nc = number of classes, na = anchors, no = total channels (4 + nc + masks). An output too small for that many (a class list longer than the model's) decodes to none.
@ yolo_nms ( Vec Detection ) dets f iou_thresh → ( Vec Detection )Greedy non-max suppression (per class), keeping highest-score boxes.
packages/yoloe/src/mask.nu — instance-segmentation mask decode + overlay.
YOLOE-seg has two outputs: output0 [1, 4+nc+nm, na] (boxes, class scores, and nm=32 mask coefficients per anchor) and output1 [1, nm, MH, MW] — the mask "prototypes" (MH=MW=160, a quarter of the 640 input). The mask for a detection is a linear combination of the prototypes weighted by that anchor's 32 coefficients, then thresholded:
logit[y,x] = Σ_m coeff[m] · proto[m,y,x] (160×160) mask = sigmoid(logit) > 0.5 ⟺ logit > 0
cropped to the box and upsampled to image resolution (bilinear) — exactly ops.process_mask(..., upsample=True) in the reference. We precompute the 160×160 logit map per detection, then bilinear-sample it as we paint the box region of the original image, blending a translucent colour where the mask is set. This matches onnxruntime's masks (the proto tensor itself is bit-checked against onnxruntime in tests/seg_fwd.nu).
@ mask_nm → iMask prototype geometry: 32 coefficients, 160×160 prototypes at stride 4.
@ mask_dim → i@ mask_stride → i@ mask_coeffs GpuHost out i na i nc i ai → ( Vec f )Read a detection's nm mask coefficients from output0 (channels 4+nc .. 4+nc+nm at anchor ai). Each is the f32 value, exactly.
@ mask_logits GpuHost proto ( Vec f ) coeff i MH i MW → ( Vec f )Build the 160×160 mask logit map for one detection: logit[y,x] = Σ_m coeff[m]·proto[m,y,x]. proto is laid out [nm, MH, MW] row-major. Each pixel sums its terms in m order, as one loop per pixel did, but a prototype plane at a time: the inner loop runs over contiguous windows and vectorises. Each logit is rounded to f32, as the map was always stored. A proto smaller than that is no mask: every logit 0.
@ mask_sample ( Slice f ) L i MH i MW f fy f fx → fBilinear sample of an MH×MW grid at fractional (fy,fx); out-of-range coordinates clamp to the edge (align_corners=False convention).
@ mask_blend Image im i x i y i r i gg i bb i alpha → vBlend colour (r,g,b) into a pixel at alpha/256 (alpha 0..256).
@ mask_overlay Image im ( Vec f ) L__v Letterbox lb i S i x0 i y0 i ow i oh i r i gg i bb i alpha → iOverlay one detection's mask onto the original image. The box is given in ORIGINAL-image pixels (x0,y0,ow,oh); for each pixel we map back through the letterbox to the 160×160 mask grid and threshold the bilinearly-sampled logit at 0 (sigmoid > 0.5). S is the model input side (640).
packages/yoloe/src/display.nu — show an Image live in the terminal.
Renders a packed-RGB Image as 24-bit-colour half-blocks: each character cell is the upper-half-block glyph ▀ whose FOREGROUND colour is one image pixel and BACKGROUND colour the pixel just below it — so one cell carries two vertical pixels, giving a square-ish picture in any truecolor terminal (gnome-terminal, kitty, xterm, … and over SSH). No X11, no SDL, no library.
The frame is scaled to fit the terminal (stdlib's term_width / term_height) preserving aspect, built into one String, and printed after homing the cursor so a continuous capture loop overwrites in place like a video.
@ term_enter → vClear the screen and hide the cursor (call once before a live loop).
@ term_leave → vShow the cursor again (call when the live loop ends).
@ img_show Image im → vRender im to the terminal as truecolor half-blocks, scaled to fit.
packages/yoloe/src/image.nu — YOLO-facing adapter over packages/image.
The codecs and raster ops live in the image package (PNG / baseline + progressive JPEG / PPM decode+encode, rectangle drawing — see deps/image); this file keeps only what is YOLO-specific: letterbox preprocessing (aspect-preserving nearest resize + grey-114 pad, matching ultralytics), packing to the [0,1]-normalised NCHW float tensor the model consumes, edge-clamped reads for the mask overlay, and load/save-by-extension. The Image type is the image package's (width/height/channels/data).
: Letterbox { Image img f scale i padx i pady }Letterbox result: the square image plus the transform back to original pixels (orig = (lb - pad) / scale).
@ img_w Image im → i@ img_h Image im → i@ img_get Image im i x i y i ch → iEdge-clamped read (the mask overlay and letterbox sample with clamping).
@ img_set Image im i x i y i ch i val → vSaturating store (image_set masks to the low byte; overlay math must clamp).
@ img_blank i w i h i fill → ImageAn RGB image filled with a constant grey level.
@ img_load s path → ?ImageLoad any supported format (PNG / baseline+progressive JPEG / PPM) as 3-channel RGB. On None, ( image_error ) says why.
@ img_save s path Image im → bSave by extension: .png / .jpg / .jpeg (quality 90) / anything else = PPM.
@ letterbox Image im i S → Letterbox── letterbox to S×S (aspect-preserving, grey 114 pad) ────────────
@ img_to_nchw_norm Image im → GpuHostPack to NCHW float tensor normalised to [0,1]: a host buffer of 3HW f32, the input rt_run_host takes.
@ img_draw_rect Image im i x0 i y0 i x1 i y1 i r i gg i bb → vDraw a thickness-3 rectangle outline in the given colour.
packages/yoloe/src/prompt.nu — RUNTIME-promptable open-vocabulary detection.
yoloe-prompt <model.onnx> <tpe.f32> <classes.txt> <img> [out]
Unlike src/main.nu (whose vocabulary is baked into the model at export time), this takes the prompt text embeddings as a runtime input: the model has two inputs, the image and tpe [1,K,512] (raw MobileCLIP text features for the K prompt words). Swap the tpe file + classes file to detect a different set of objects with the same model — no re-export.
Produce the model + tpe with tools/export_promptable.py and tools/gen_tpe.py.
& c @ nurl_peek_f32 *u base i idx → f@ p s m → v@ pf f x → v@ pn i n → v@ load_f32 s path → ( Vec u )A raw little-endian f32 file as its bytes (empty when unreadable).
@ read_names s path → ( Vec String )@ name_at ( Vec String ) names i i → s@ shape4 i a i b i c i d → ( Vec i )@ shape3 i a i b i c → ( Vec i )@ main → ipackages/yoloe/src/v4l2.nu — webcam capture in pure NURL via Video4Linux2.
Opens /dev/videoN, negotiates a YUYV capture format, sets up memory-mapped streaming buffers, and hands back decoded RGB frames — no ffmpeg, no OpenCV, just open/ioctl/mmap on the kernel's V4L2 ABI through the FFI. The ioctl request codes and struct field offsets below are the stable x86_64 <linux/videodev2.h> layout (verified against the system headers):
v4l2_format 208 B type@0 pix.width@8 height@12 pixelformat@16 field@20 v4l2_requestbuffers 20 B count@0 type@4 memory@8 v4l2_buffer 88 B index@0 type@4 bytesused@8 memory@60 m.offset@64 length@72
Frames arrive as YUYV (4:2:2, 2 bytes/pixel) and are converted to packed RGB with the BT.601 integer transform. One Camera owns the fd plus the mmap'd ring of capture buffers: it is a handle, every copy is the same stream, and its last owner stops it, unmaps the ring and closes the fd (cam_close does that now — an optional early release).
& c @ ioctl i32 fd i req ... → i32ioctl as C declares it — int ioctl(int, unsigned long, ...). stdlib/std/term.nu declares the same symbol, and one linker symbol has one ABI: stating it with i where C says int is a different function, which the compiler now rejects rather than miscompiling.
& c @ nurl_peek_i32 *u base i idx → i32& c @ nurl_poke_i32 *u base i idx i32 val → v: Camera { i fd i w i h i nbuf ( Vec i ) bufptr ( Vec i ) buflen i ok }A streaming webcam: fd, frame geometry, and the mmap'd buffer ring. One holder owns it; its drop stops the stream, unmaps the ring and closes the fd.
% Drop Camera { @ drop Camera c → v { ( __cam_release c ) } }@ cam_ok Camera h → b@ cam_w Camera h → i@ cam_h Camera h → i@ cam_close sink Camera c → vRelease the camera now (optional — its drop does it at the end of its holder's scope anyway).
packages/yoloe/src/bpe.nu — the CLIP byte-pair-encoding tokenizer in pure NURL. This is the word → token-id half of the MobileCLIP text path; the other half (token-ids → 512-d features) is the text-encoder ONNX graph already run by the onnx runtime (see tests/text_fwd.nu).
Faithful port of open_clip's SimpleTokenizer (the tokenizer MobileCLIP's ClipTokenizer wraps as get_tokenizer("ViT-B-16")):
bpe_simple_vocab_16e6.txt, lines 1..48894),
Word splitting reproduces CLIP's regex 'r"'s|'t|'re|'ve|'m|'ll|'d|[\p{L}]+|[\p{N}]|[^\s\p{L}\p{N}]+" for the common case (ASCII / Latin-1 letters, digits, punctuation, English contractions). A letter is a-z / Latin-1 letters / any codepoint
= U+0100; a digit is 0-9. Exotic symbol-vs-letter classification of rare
BMP punctuation may differ from full Unicode \p{L}/\p{N}, which does not arise for object-detection class names.
: i BPE_INF 1000000000: Tokenizer: Tokenizer {
( Vec String ) byte_enc // byte value 0..255 → its symbol string
( HashMap s i ) enc // token string → id
( HashMap s i ) ranks // "a b" merge line → rank
( Vec String ) arena // owns enc/ranks key strings
i sot
i eot
}
A loaded tokenizer. The hash maps key on s (raw char*) pointers, so the backing strings must outlive the maps: arena owns every key string that is not already owned by byte_enc.
@ _ig ( Vec i ) v i k → i── small Vec<i> helpers ─────────────────────────────────────────
@ tokenizer_none → TokenizerThe tokenizer that encodes nothing (sot=-1): a placeholder for a struct field that holds one only when free-text prompting is on.
@ tokenizer_load s merges_path → Tokenizer── load ───────────────────────────────────────────────────────── Build the tokenizer from the merges asset. Returns a Tokenizer whose maps are empty (sot=-1) on read failure.
@ tokenizer_load_builtin → TokenizerBuild the tokenizer from the merge table compiled into the binary (src/clip_merges_data.nu, generated by tools/embed_merges.py). An installed tool has no package tree on disk, so this is the default; tokenizer_load stays for custom vocabularies via --merges.
@ bpe_encode Tokenizer tk s text → ( Vec i )── encode / tokenize ──────────────────────────────────────────── text → list of token ids (no sot/eot, no padding).
@ bpe_tokenize Tokenizer tk s text i ctx → ( Vec i )text → padded context: [sot] + ids + [eot], zero-padded/truncated to ctx. Returns a Vec<i> of length ctx.
packages/yoloe/src/main.nu — the yoloe command: promptable open-vocabulary object detection AND instance segmentation, pure NURL on the GPU, with a live webcam mode that draws straight to the terminal. Single binary, three sub-commands, parsed by the cli package (see yoloe --help):
yoloe detect --model M --classes C --image IMG [--out OUT] yoloe seg --model M --classes C --image IMG [--out OUT] yoloe cam --model M --classes C [--device DEV] [--frames N] [--out DIR] [--no-show] [--boxes]
cam shows the segmented feed live in the terminal (truecolor half-blocks); with no --frames it runs until Ctrl-C, and --out also saves the frames.
@ p s m → v@ pf f x → v@ pn i n → v@ read_names s path → ( Vec String )Read the prompt vocabulary from a text file (one class per line); the line count is the class count nc. Must match the names the model was exported with (tools/export.py) — swap both to detect a different vocabulary.
@ name_at ( Vec String ) names i i → s@ shape4 i a i b i c i d → ( Vec i )@ pal_r i k → i@ pal_g i k → i@ pal_b i k → i@ frame_path s dir i n → Stringframe path: <dir>/frameNNNNN.ppm
@ process_frame Image im OGraph g Engine e ( Vec String ) names i nc b want_boxes b want_masks b verbose → iRun the network on one image, drawing boxes (and, when want_masks, the per-object segmentation mask) onto im. When verbose, prints one line per detection. Returns the detection count.
@ load_model s path → ?OGraph@ run_still b want_boxes b want_masks String mp String np String ip String op i gpu → i── still-image modes (detect / seg) ──────────────────────────────
@ run_cam String mp String np String dev i nframes String od i mode b want_boxes b want_masks i gpu → i── webcam mode (cam): live terminal display and/or save frames ───
@ main → iGENERATED by tools/embed_merges.py from assets/clip_merges.txt — do not edit. The CLIP BPE merge table (48893 lines) in ~32 KB line-aligned chunks, embedded so installed binaries need no asset file on disk (see tokenizer_load_builtin in bpe.nu). Chunked because released nurlc (≤ v0.11.1) stack-overflows on string literals past ~48 KB.
@ clip_merges_chunks → i@ clip_merges_chunk i k → spackages/yoloe/src/window.nu — a real GUI window for the live preview, in pure NURL via Xlib (libX11) bound directly — no SDL, no GTK, no toolkit.
Opens an X11 window the size of the camera frame and blits each segmented RGB frame at full resolution with XPutImage (so unlike the terminal view there's no down-scaling). Polls for a keypress / window-close so the loop can exit cleanly. The toolchain auto-links -lX11 only when @XOpenDisplay appears (build.sh writes the runtime.X11 sentinel when libX11 is found), so a headless build is unaffected.
& X11 @ XOpenDisplay *u name → *u& X11 @ XDefaultScreen *u dpy → i& X11 @ XRootWindow *u dpy i screen → i& X11 @ XDefaultVisual *u dpy i screen → *u& X11 @ XDefaultDepth *u dpy i screen → i& X11 @ XCreateSimpleWindow *u dpy i parent i x i y i w i h i bw i border i bg → i& X11 @ XStoreName *u dpy i win s name → i& X11 @ XSelectInput *u dpy i win i mask → i& X11 @ XMapWindow *u dpy i win → i& X11 @ XCreateGC *u dpy i drawable i valuemask *u values → *u& X11 @ XCreateImage *u dpy *u visual i depth i format i offset *u data i width i height i pad i bpl → *u& X11 @ XPutImage *u dpy i d *u gc *u image i sx i sy i dx i dy i w i h → i& X11 @ XFlush *u dpy → i& X11 @ XPending *u dpy → i& X11 @ XNextEvent *u dpy *u event → i& X11 @ XInternAtom *u dpy s name i only → i& X11 @ XSetWMProtocols *u dpy i win *u protocols i count → i& X11 @ XCloseDisplay *u dpy → i& X11 @ XDestroyImage *u image → i& X11 @ XFreeGC *u dpy *u gc → i& c @ nurl_poke_i32 *u base i idx i32 val → v& c @ nurl_peek_i32 *u base i idx → i32: XWin { i dpy i win i gc i img ( Vec u ) data i w i h i ok }Pointers (Display, GC, XImage) carried as i64; w/h the frame size. The pixel buffer the XImage points at is the window's own Vec. A window, owned by one holder; its drop closes the display connection.
% Drop XWin { @ drop XWin x → v { ( __xwin_release . x dpy . x gc . x img ) } }@ xwin_none i w i h → XWinThe window that never opened (ok=0): w×h is still the frame size.
@ xwin_ok XWin h → b@ xwin_close sink XWin x → vClose the window now (optional — its drop does it anyway).