Line Inspection System · AfifAI
Case study · Industrial computer vision · 2026

Cameras that catch line defects and stop the machine within seconds

An Israeli plastics manufacturer wanted its production line watched by cameras instead of only by eye. I led and built a visual inspection system that checks every camera once a second, raises a fault only after three bad frames in a row, sets the line PLC's fault signal, and shows operators what was found and where, in Hebrew or English.

.NET 8 / ASP.NET CoreSignalRPostgreSQLReact + TypeScriptPyTorch + scikit-learnSiemens S7 PLC
Client
Israeli industrial plastics manufacturer (confidential)
My role
Team lead & architect (hands-on)
Timeline
Jun – Oct 2026 · on-prem, one production line, PLC-wired
Status
In production
At a glance
≈1 sfrom the third bad frame to the operator screen, down from up to 30 s on the health heartbeat
93.9%held-out accuracy of the defect classifier (98.2% precision on “problem”)
120 to 2audit records for a one-hour database outage, after switching to transition-only logging
86,400 to 1database reads per day for the system mode, now held in memory after startup
≈600 to 0redundant prediction calls per open fault, by muting that one camera for its 10-minute alert window
3 in a rowbad frames needed before a fault is raised, so single-frame noise never stops the line
20 + 2REST endpoints and live SignalR streams, behind 2 roles
≈4 monthsfrom first commit to PLC integration, across 3 repositories
The problem

Defects were spotted by eye, if someone was looking

Plastic sheet leaves the machine through an exit window. Folds, side tears, off-center material or the end of a roll can appear at any moment, and until someone notices, the line keeps producing scrap. The client wanted cameras on the exit window, a model that tells a good frame from a bad one, a screen for operators and administrators, and a direct signal to the line's PLC. It all had to run on premises next to the machine. It also had to stay quiet when nothing was wrong: a system that raises false alarms gets switched off.

What I owned

From an image-classifier POC to a wired line

  • Architecture and technical lead for the whole system: backend, admin app, model service and PLC integration.
  • Hands-on author of most of the code: the .NET inspection pipeline, health and fault streams, audit trail, retention, and the React admin screens.
  • Turned a Python classifier POC (642 labelled images, 7 defect types) into an HTTP prediction service, and embedded its manual-inspection UI in the admin app.
  • Designed the fault rules (consecutive frames, per-camera alert window, acknowledge, Setup mode) to fit how the line is actually operated.
  • Set up the dev → QA → stage → prod branch flow, with 8 versioned admin releases.
Architecture

One on-prem service owns the loop, the rules and the signal

The diagram is wider than the screen: swipe sideways to see all of it →

Everything runs on one machine next to the line, so there is no cloud dependency on the detection path. The cameras and the backend are coupled by nothing but a folder: a camera writes images, and the pipeline reads the newest valid one. Swapping a camera vendor needs no code change.

Design decisions

Quiet when the line is fine, loud only once

Two streams, one meaning each

At first, one health status covered both “the server is broken” and “the line has a defect”. A red light could mean a dead database or a torn sheet. I split it into a service-health stream and a line-fault stream, each with its own clock. Trade-off: the client merges the two itself. It also shows a third state when the fault stream can't be trusted.

A run of frames, then a per-camera mute

A fault needs 3 problem frames in a row. A good frame resets the count, and an unreadable frame leaves it unchanged. After a fault, only that camera stops being sampled for 10 minutes, so other cameras can still report. Trade-off: the system can't see a defect clear early, so operators got an Acknowledge button.

Freeze restart-only settings at startup

Thresholds and the sampling interval are read once and cached for the life of the process. The mode is held in memory and written to the database first. A query on every 1 s tick would have been 86,400 reads a day. Trade-off: some edits need a restart, so the admin app shows a “pending restart” bar until one happens.

The PLC never blocks detection

The fault notifier only queues the new bit. A background loop opens a connection, writes once and closes, and retries every 5 s until the write lands. Edges can be hours apart, and idle S7 connections get dropped. Trade-off: after a PLC power cycle, the bit keeps its last value until the next fault edge. There is a manual override for testing the wiring.

A small model that trains in seconds

With 642 labelled images, a frozen MobileNetV3 backbone plus a random-forest head beat fine-tuning. It runs on a CPU at about 30 ms per image and retrains in under a second from cached features. Trade-off: borderline, unclassified images are the weak spot (58% recall on that subset).

How a fault behaves

Step through the fault rules

This is an illustrative walk-through of the rules the backend enforces for one camera, sampled once a second. It contains no client data.

Engineering log

What broke, and how it was fixed

Most of the hard parts were about time, noise and trust, not the model. A selection:

Jul 22

The model lived in a separate tool

The classifier POC had its own standalone web page, in English. Operators would have had to switch between two systems.

Translated it to Hebrew, adapted it to run inside an iframe, and embedded it as the Manual Inspection screen of the admin app, behind an HTTPS URL set by environment.

Aug 2

The dashboard polled for health

The status screen asked the server again and again, and could only be as fresh as its polling interval.

Moved health to a SignalR stream that pushes on connect, on a heartbeat and on every change. The polling path was kept as a fallback.

Aug 19

Pressing “stop” didn't stop at once

A prediction call can take up to 30 s. If Setup mode had waited for the cycle in flight, the system would have kept reading frames and filing evidence for half a minute after an operator stopped it.

Linked a per-session cancellation token into every cycle. Entering Setup cancels the HTTP call and the evidence write immediately, and a cancelled cycle is logged as information, not as a pipeline failure.

Aug 23

An audit trail that would have drowned itself

The health check runs every 30 s and the sampler every 1 s. Logging every result would have meant ≈2,900 and ≈86,400 identical lines a day.

Each looping component keeps its own “already reported” state and audits transitions only. A one-hour database outage is now 2 records instead of 120. Exception text is kept out of the trail, so connection strings can't leak into it.

Aug 24

Faults waited for the heartbeat

Camera faults reached clients through the health publisher, which runs on a fixed 30 s clock. A detected defect could sit unreported for half a minute.

Split faults into their own stream, pushed by the component that sees the third frame. The time from detection to screen dropped from up to 30 s to one sampling tick (≈1 s).

Aug 26

A setting could change mid-flight

Restart-required parameters were cached on first read. Until something first read them, a value edited in the UI could still take effect, so the behavior depended on timing.

Froze every restart-required parameter in one snapshot at startup. Moved the restart flag to the parameters where it was worth it, and added a “pending restart” bar in the admin app.

Sep 2

A blind camera looked healthy

If a camera's folder went empty or unreachable, the pipeline loop kept turning and stayed green, even though one line was no longer watched.

A camera that yields no frame moves to Error on the first tick and back on the first good one, using a compare-and-set update. Any camera in Error makes the image pipeline Unhealthy.

Oct 7

Some cameras write into dated subfolders

Some camera models write images into per-date subfolders instead of the folder configured for them, so the sampler found nothing.

Added a stream folder layout that finds frames in date subfolders, and extended snapshot retention so those subfolders are also swept.

Oct 8–14

Wiring the PLC safely

The line's PLC needed one fault bit. Engineers also needed a way to test the wiring without waiting for a real defect.

Built an edge-triggered, queued S7 writer with retries and audit records, plus an admin-only manual override. Releasing the override always writes the real state back, so the PLC is never left with a test value. Operators got an Acknowledge button to clear a fault they have fixed.

Outcome

Where it landed

WhatBeforeAfter
Watching the exit windowBy eye, between other tasksEvery camera checked once a second, in Monitor mode
Defect to operator screenUp to 30 s (health heartbeat)≈1 s (one sampling tick)
Defect to machineSomeone had to notice and reactPLC fault bit set on the same edge, retried until it lands
NoiseAny single bad frame could alert3 in a row required, 1 report per fault, per-camera 10-min window
Audit volume, 1-hour outage120 identical records2 records (failed, restored)
Config reads on the hot pathA query per tick (86,400 a day)1 read at startup, then memory
  • A live dashboard with a site and line header, camera counts, the current mode, an active-fault bar with an Acknowledge button, and a pending-restart bar.
  • Camera management (create, edit, delete, status) and 19 typed, validated system parameters, with Hebrew descriptions and units.
  • Evidence images filed by date and camera, with path-traversal protection, plus 24-hour retention sweeps for evidence, snapshots and logs.
  • Monitor and Setup modes that operators switch from the header. Setup freezes inspection immediately.
  • An audit trail with 28 event types covering sign-ins, configuration, cameras, the pipeline, retention and PLC writes.
  • Hebrew and English throughout (288 translation keys each), right-to-left layout, and light and dark themes.
Stack

Technology stack

Backend
.NET 8, ASP.NET Core, BackgroundService, HealthChecks, AutoMapper
Data
PostgreSQL, EF Core (database-first), 12 versioned SQL migrations
Real time
SignalR: health hub and camera-fault hub, auto-reconnect client
Front end
React 18, TypeScript, Vite, Redux Toolkit, Ant Design, i18next
Vision model
PyTorch MobileNetV3-Small features, scikit-learn random forest, Flask
Industrial
Siemens S7-1200 over the S7 protocol, one data-block bit
Security & ops
JWT with refresh, 2 roles, NLog JSON audit, on-prem folder publish

Free scoping call · 30 minutes · No commitment

Bring one process. Leave with a plan and a number to beat.

Write a few lines: the process or system, what hurts today (time, errors, cost or an AI pilot that stalled) and who uses it. After a short call you get a written plan and a fixed quote within ≈3 days.

Get a free scoping callafif.mansour@gmail.com

Opens a pre-filled email · reply within one business day

The model reads. The code counts.

All projects