tensor·factory

Developer reference

Python API

The public surface of the core library and its siblings. The core (tensor_factory) is dependency-free; inference, generation, and training are lazy-imported behind extras so importing a module never pulls a heavy dependency you didn't ask for.

tensor_factory (core)

Top-level exports — the dependency-free primitives:

from tensor_factory import BBox, encode_uint8, decode_uint8, max_error_px

BBox

The canonical bounding box: axis-aligned, normalized xyxy in [0, 1], top-left origin. Immutable (slotted, no __setattr__), hashable, and resolution-independent — one annotation flows unchanged from 480 px training to whatever the camera feeds at inference.

BBox(x1, y1, x2, y2) # validates range + order; raises on degenerate

Construct directly only with values you know are valid; otherwise use the constructors that repair input:

ConstructorFrom
BBox.clamped(x1, y1, x2, y2)normalized coords — clamps to [0,1], fixes ordering
BBox.from_cxcywh(cx, cy, w, h)normalized center + size
BBox.from_pixels(x1, y1, x2, y2, *, width, height)absolute pixels
MemberReturns
.x1 .y1 .x2 .y2the four normalized coords (float)
.width .height .areaderived extents
.center(cx, cy)
.to_cxcywh()(cx, cy, w, h)
.to_pixels(*, width, height)absolute integer (x1, y1, x2, y2)
.iou(other)intersection-over-union in [0, 1]
from tensor_factory import BBox

b = BBox(0.1, 0.1, 0.9, 0.9)
b.to_pixels(width=480, height=480)   # (48, 48, 432, 432)
b.iou(BBox(0.0, 0.0, 0.5, 0.5))      # overlap ratio

codec — the 8-bit contract

A detection is four uint8 values. These convert a BBox to and from that form.

encode_uint8(box: BBox) -> tuple[int, int, int, int]
decode_uint8(values: Sequence[int]) -> BBox # repairs order, clamps
max_error_px(image_size: int) -> float # worst-case round-trip error
from tensor_factory import encode_uint8, decode_uint8, max_error_px

encode_uint8(BBox(0.1, 0.1, 0.9, 0.9))  # (26, 26, 230, 230)
max_error_px(480)                        # ~0.94 px (under the 3px budget)

Why four bytes is enough lives in Internals › the contract.

formats — annotation conversion

tensor_factory.formats converts the canonical BBox to/from COCO, YOLO, and Pascal VOC. Every exporter has a matching importer, so a box survives a round-trip (COCO/YOLO are lossless float; VOC rounds to integer pixels).

ToFromForm
to_coco_bbox(box, *, width, height)from_coco_bbox(bbox, *, width, height)COCO [x, y, w, h] abs px
to_yolo(box)from_yolo(cx, cy, w, h)YOLO normalized (cx, cy, w, h)
to_voc_box(box, *, width, height)from_voc_box(voc, *, width, height)VOC xmin/ymin/xmax/ymax int px

inference — Detector

CPU inference via onnxruntime, behind the infer extra (lazy-imported, so the pure-Python core stays dependency-free). The fixed contract: input image is (1, 3, S, S) float32 in [0, 1] (CHW, RGB); output box is (1, 4) normalized xyxy; a presence-head model adds a presence output (one objectness logit).

Detector(model_path, *, input_size=480, providers=None)
Method / attrReturns
.detect_box(image)a BBox
.detect_uint8(image)the four-uint8 tuple
.detect_presence(image)P(target present) — sigmoid(presence_logit); needs a presence head, else raises
.preprocess(image)the (1,3,S,S) float32 input array
.has_presenceTrue if the model emits a presence output
.input_name .output_names .sessionthe underlying ORT session details
benchmark(detector, image, *, n=100, warmup=5) -> float # mean fps
from PIL import Image
from tensor_factory.inference import Detector

det = Detector("model.onnx", input_size=480)
box = det.detect_box(Image.open("frame.png"))
det.detect_uint8(Image.open("frame.png"))   # (54, 85, 201, 201)

review — the validation gate

tensor_factory.review is the single source of truth for which labels may train. Each COCO annotation carries review (a triage decision) and source (provenance). Only approved is trainable; a missing review key normalizes to pending — untrusted by default.

StatesSources
PENDING · APPROVED · REJECTEDGROUNDINGDINO · HUMAN · SYNTHETIC_GT
FunctionReturns
normalize(review)a known state (unknown/None → PENDING)
is_trainable(review)True iff approved
review_summary(coco)image + annotation counts per state, plus trainable

See Internals › the review gate for the rationale.

tensor_factory_mcp.core — serving logic

Framework-free detection logic the MCP tools and the HTTP server both wrap, so all surfaces return byte-identical JSON. Detectors are cached by (model, input_size).

FunctionReturns
detect(image_path, model_path=None, input_size=480)the detection dict (norm/pixel/uint8 box, image size, model; present/score for a presence head)
detect_bytes(data, model_path=None, input_size=480)same, from raw image bytes (HTTP body)
model_info(model_path=None, input_size=480)resolved model, input size, input name, providers
benchmark(model_path=None, input_size=480, n=100)fps on a synthetic frame
resolve_model(model_path)absolute model path (falls back to the bundled default)

The HTTP server (tensor_factory_mcp.http_server) exposes make_server(host, port, …) and serve(…) for embedding the endpoint in your own process.

tensor_factory_synth — generation + labeling

Top-level exports (generation needs the gemini extra; the labeler needs gpu):

SymbolWhat
Generator (Protocol)generate(prompt, seed, *, size=480) -> GeneratedSample
MockGeneratordeterministic, torch-free; exact ground-truth boxes
NanoBananaGeneratorGemini gemini-2.5-flash-image; optional reference image
AutoLabeler (Protocol)label(image, features) -> list[Detection]
GroundingDinoAutoLabeleropen-vocabulary labeler; resolves device cuda → mps → cpu
GeneratedSample, Detectiondataclasses carrying provenance + review state
synth_dataset(generator, prompt, features, *, n, out_dir, …)end-to-end: generate → label → write COCO
resolve_device(prefer=None), enable_mps_fallback()device helpers

tensor_factory_train — model + training

Needs the train extra (torch).

TinyDetector

TinyDetector(width=16, presence=False, learn_gain=True)

Four stride-2 conv blocks → a per-edge heatmap → marginal soft-argmax → normalized xyxy. With presence=True a single global-pooled objectness logit head rides alongside, so forward returns (box, presence); otherwise just the box. learn_gain exposes the learnable soft-argmax inverse-temperature. See Internals › the model.

soft_argmax_xyxy(heat, gain=1.0) # (B,4,H,W) -> (B,4) — unit-testable in isolation

fit

fit(data_dir, out_path, *, epochs=10, batch=16, lr=1e-3, size=480, width=16, device=None, presence=False, val_frac=0.0, seed=0, box_weight=1.0, presence_weight=1.0, augment=False, weight_decay=0.0, require_review=True, negatives=None, learn_gain=True) -> Path

Trains on <data_dir>/annotations.coco.json + images and exports an int8 ONNX. require_review (default) trains only approved annotations and refuses an all-pending dataset; negatives turns the objectness head on and trains it toward absent (no class); with val_frac the best-scoring checkpoint is exported.

export_onnx

export_onnx(model, out_path, *, size, quantize=True) -> Path

Exports on CPU (opset 17), then onnxruntime dynamic-quantizes weights to QUInt8. Output names are fixed — image in, box (and presence for a presence-head model) out — so the runtime reads each by name regardless of graph order.

data loaders

FunctionReturns
load_coco_boxes(coco, root, *, require_review=True)list[(path, BBox)]
load_coco_labeled(coco, root, *, require_review=True)(list[(path, BBox, label)], names)
load_negatives(images_dir)list[Path] (no review gate — a known absence)
resolve_device(prefer=None)training device (cuda → mps → cpu)

tensor_factory_label — Label Studio

SymbolWhat
LabelStudioClient(url, token, *, timeout=60.0, transport=None)REST client: create_project, import_tasks, export_json
bbox_config(labels=("helicoil",))RectangleLabels config XML
coco_to_tasks(coco, image_url)COCO → Label Studio tasks with predictions
ls_export_to_coco(export, *, image_field="image")LS export → COCO (stamps approved/human)
http_image_url(base_url), local_storage_url(prefix=…)URL factories for the round-trip
Box coordinate note

COCO boxes are normalized [0, 1]; Label Studio boxes are percentages 0–100. The converters handle the scaling, and image file names are preserved through the round-trip via the URL factories.

Prev← CLI reference NextInternals →