Script Examples
Copy these scripts into a .oneai.js file in your project folder and adapt the constants at the top.
See Scripting Overview for how to create and run a script.
Capture training images
Shows a live camera preview and saves a fixed number of images into the dataset. The dashboard closes after the last image when the script was started by the AI agent.
const TARGET_COUNT = 20;
const SPLIT = 'Train';
const PREFIX = 'part';
ui.title('Capture training images');
ui.columns(2);
const batch = time.timestamp().replace(/[^0-9]/g, '').slice(0, 14);
let captured = 0;
let requested = false;
ui.label('instructions', 'Vary the position, rotation and lighting of the part between captures.', { columnSpan: 2 });
ui.label('progress', `0 / ${TARGET_COUNT}`, { columnSpan: 2 });
await camera.preview('live', undefined, { title: 'Live camera', fps: 30, rowSpan: 2 });
ui.image('last', null, { title: 'Last capture', rowSpan: 2 });
ui.button('capture', 'Capture', () => { requested = true; }, { columnSpan: 2 });
while (app.isRunning && captured < TARGET_COUNT) {
if (!requested) {
await time.sleep(50);
continue;
}
requested = false;
ui.button('capture', 'Capturing...', () => {}, { enabled: false });
const frame = await camera.capture();
try {
const number = String(captured + 1).padStart(3, '0');
image.save(frame, `Dataset/${SPLIT}/${PREFIX}_${batch}_${number}.png`);
ui.image('last', frame, { title: `Last capture (${number})` });
} finally {
image.release(frame);
}
captured++;
ui.label('progress', `${captured} / ${TARGET_COUNT}`);
ui.button('capture', 'Capture', () => { requested = true; });
}
await camera.stopPreview('live');
log.info(`Captured ${captured} images into Dataset/${SPLIT}`);
The new images are unlabeled. Annotate them in ONE AI before you train.
Count objects in a segmentation dataset
Counts the annotated objects per class in all samples of a split. Shows the first sample with its regions, so you can check the counting rule visually.
const SPLIT = 'Train';
const MIN_AREA = 20;
ui.title('Objects per class');
const ds = dataset.open('.');
const names = {};
for (const label of dataset.labels(ds)) names[label.id] = label.name;
const totals = {};
let samples = 0;
for (const sample of dataset.samples(ds, SPLIT)) {
if (!app.isRunning) break;
if (!sample.hasMask) continue;
const raw = dataset.loadMask(sample);
const clean = mask.open(raw, 1);
const regions = shape.components(clean, { labelNames: names, minArea: MIN_AREA });
for (const r of regions) totals[r.label] = (totals[r.label] ?? 0) + 1;
samples++;
ui.append('history', `${sample.name}: ${regions.length} objects`, { title: 'Samples' });
if (samples === 1) {
const img = dataset.loadImage(sample);
ui.image('preview', img, {
title: `First sample: ${sample.name}`,
overlays: shape.toOverlays(regions, { color: 'lime' })
});
image.release(img);
}
mask.release(clean);
mask.release(raw);
}
ui.table('perClass',
Object.entries(totals).map(([label, count]) => ({ label, count })),
{ title: `Objects in ${samples} samples`, columnSpan: 2 });
Evaluate a model on a folder of images
Runs the newest exported model on every image in a folder and lists the results. This works for classification, object detection and segmentation models.
const FOLDER = 'Dataset/Test'; // any folder, absolute paths are allowed
ui.title('Model evaluation');
const model = onnx.latest();
if (!model) throw new Error('No ONNX model found in ./Models');
log.info(`Using ${model.name} (${model.info.modelType})`);
const rows = [];
for (const file of fs.list(FOLDER, { pattern: '*.png', filesOnly: true })) {
if (!app.isRunning) break;
if (file.name.endsWith('_seg.png')) continue; // skip segmentation masks
const img = image.load(file.path);
const p = onnx.predict(model.path, img);
let result = '';
if (p.type === 'classes') result = `${p.top.label} (${(p.top.score * 100).toFixed(1)} %)`;
else if (p.type === 'objects') result = `${p.boxes.length} objects`;
else if (p.type === 'segmentation') {
const cleaned = mask.open(p.mask, 1);
result = `${shape.count(cleaned, { minArea: 20 })} regions`;
mask.release(cleaned);
}
rows.push({ image: file.name, result });
ui.table('results', rows, { title: `${rows.length} images`, columnSpan: 2 });
image.release(img);
await time.sleep(1); // let the dashboard update
}
The example skips *_seg.png files because these are segmentation annotations, not images.
Live inspection station
A complete inspection station with a segmentation model:
- live camera preview and a Capture & inspect button
- PASS/FAIL verdict with the defects drawn on the frame
- inspection counter that persists between runs
- CSV log and optional PDF report per inspection
- optional result upload to an MES over HTTP
- separate history screen
const MODEL = 'Models/model.onnx'; // exported ONE AI segmentation model
const MIN_DEFECT_AREA = 30; // smallest defect, in model output pixels
const MES_URL = ''; // e.g. 'http://mes.local/api/inspections', empty to disable
const info = onnx.info(MODEL);
if (info.modelType !== 'segmentation') throw new Error(`${MODEL} is not a segmentation model`);
// Class names from the project, every class is treated as a defect.
const names = {};
for (const label of dataset.labels(dataset.open('.'))) names[label.id] = label.name;
let requested = false;
let saveReport = false;
const VERDICT_STYLE = { width: 'half', fontSize: 32 };
function showActions(enabled) {
ui.actions('actions', [
{ text: 'Capture & inspect', onPress: () => { requested = true; }, enabled },
{ text: 'History', onPress: () => ui.showScreen('history') }
], { width: 'full' });
}
ui.title('Defect inspection');
ui.screen('inspect', { title: 'Inspection', columns: 2 });
ui.image('live', null, { title: 'Live camera', rowSpan: 2 });
ui.image('result', null, { title: 'Last result', rowSpan: 2 });
ui.label('verdict', 'Not inspected', VERDICT_STYLE);
ui.label('total', `Inspected: ${storage.get('total', 0)}`, { width: 'quarter' });
ui.checkbox('saveReport', saveReport, value => { saveReport = value; }, { title: 'Save PDF report', width: 'quarter' });
showActions(false);
ui.screen('history', { title: 'History', columns: 1 });
ui.append('history', 'Inspections of this run:');
ui.button('back', 'Back to inspection', () => ui.showScreen('inspect'));
await camera.preview('live', undefined, { fps: 30, title: 'Live camera' });
showActions(true);
while (app.isRunning) {
if (!requested) {
await time.sleep(30);
continue;
}
requested = false;
showActions(false);
// Take a fresh frame. The preview stays frozen during the inspection.
await camera.stopPreview('live');
const frame = await camera.capture();
ui.busy('result', 'Inspecting...');
await time.sleep(1);
const prediction = onnx.predict(MODEL, frame);
const cleaned = mask.open(prediction.mask, 1);
const defects = shape.components(cleaned, { labelNames: names, minArea: MIN_DEFECT_AREA });
const pass = defects.length === 0;
const verdict = pass ? 'PASS' : 'FAIL';
const overlay = mask.overlay(frame, cleaned, { opacity: 0.45 });
ui.busy('result', false);
ui.image('result', overlay, { title: `${defects.length} defects` });
ui.label('verdict', verdict, { ...VERDICT_STYLE, color: pass ? 'lime' : 'red' });
const total = storage.increment('total', 1);
ui.label('total', `Inspected: ${total}`);
const stamp = time.timestamp();
ui.append('history', `${stamp} ${verdict} ${defects.length} defects`);
fs.appendText('Results/inspections.csv', `${stamp};${verdict};${defects.length}\n`);
if (saveReport) {
const path = report.savePdf(`Results/report_${total}.pdf`, {
title: 'Defect inspection',
summary: [
{ label: 'Verdict', value: verdict },
{ label: 'Defects', value: defects.length },
{ label: 'Time', value: stamp }
],
overviewImages: [{ image: overlay, caption: 'Segmentation result' }],
findings: defects.map(d => ({
title: d.label,
verdict: 'Defect',
area: d.area,
geometry: { x: d.x, y: d.y, width: d.width, height: d.height }
}))
});
log.info(`Report saved to ${path}`);
}
if (MES_URL) {
const response = await http.post(MES_URL, { timestamp: stamp, pass, defects: defects.length });
if (!response.ok) log.warn(`MES returned HTTP ${response.status}`);
}
image.release(overlay);
image.release(frame);
mask.release(cleaned);
// Resume the live preview for the next part.
await camera.preview('live', undefined, { fps: 30, title: 'Live camera' });
showActions(true);
}
Things to adapt:
- Defect rule: this example fails a part as soon as any region is found. Filter
defectsby class, area or shape to match your quality criteria. See Image Analysis & Inference. - Operating point: to use a confidence threshold other than 0.5, replace
prediction.maskwithmask.fromConfidence(prediction, undefined, { min: 0.8 }). This requires an export with confidence output. - MES: add the host to Settings → ONE AI → Scripting → Allowed HTTP Hosts to restrict scripts to it.
- Permanent operation: keep Run Timeout at
0and raise or disable the Memory Limit for stations that run all day. - Testing without hardware: create a camera from a recorded video with
const input = camera.video('recordings/parts.mp4')and passinput.idtocamera.previewandcamera.capture.
To reset the inspection counter, call storage.clear() or delete the .oneai-storage.json file next to the script.