You built a custom operator interface for your inspection software, and it works: buttons, results, pass/fail flags. Then you add the one display an operator actually watches — a histogram — save the file, point the inspection-interface configuration at it, and the loader throws an exception: a custom interface cannot have dependencies such as sub-functions or DLL calls. Delete the histogram and the interface loads again.
This guide keeps the plot. You will compute the histogram inside the interface with your own counting loop, draw it with plain bars, and prove the counts with two checks that take seconds. It covers packed and padded buffers, 8-, 10- and 12-bit sources, a worked example on a 12 MP frame, and the cases where the panel should not compute anything at all.
Why the loader refuses your histogram
The interface you save does not run as a normal program. The inspection environment copies it into a managed container and calls into it, and to keep that reliable across machines and software versions it accepts only self-contained code: no sub-functions held in other files, no DLL calls, no shared control type definitions. A histogram block pulled from a chart library is all three at once — a library call, a foreign control type and usually a helper sub-function — so the loader rejects the whole interface at configuration time, before a single pixel is drawn.
That is why deleting the histogram fixes the load, and why no option on your PC changes it: the check is structural, not a setting. It is also the useful kind of failure, firing at your desk during configuration instead of on the line at shift start.
What the rule forbids is calling out. What it allows is everything you can write inside the interface itself: loops, arrays, integer arithmetic and drawing primitives. A histogram is a count of pixel values, and counting is arithmetic. If you are still choosing the framework for the panel, selecting an HMI framework for a custom operator interface settles that decision before any of this matters.
The rule that keeps the plot
An 8-bit mono image has exactly 256 possible values, so the whole histogram is a 256-element integer array: one pass over the image buffer, one increment per pixel, then 256 rectangles scaled against the tallest bin. The persistent state is 256 × 4 = 1,024 bytes, allocated once and reused every frame — the same argument as reuse image buffers instead of per-frame allocation, applied to a kilobyte array instead of a megabyte frame.
What you need before you start
Three things, and only the first is technical:
- The current image buffer, as 8-bit mono or convertible to it. Four numbers come with it: width and height in pixels, stride in bytes (rows are often padded past the width), and the source bit depth.
- A draw event on the interface — the paint or refresh tick where controls render. That is where the bars get drawn.
- A host with room for both jobs, since the inspection and the panel usually share one machine.
A fanless vision controller is the usual host — camera ports on one side, your panel on the display, light channels in the same box. The MV-VC3301H-128G66 is that class of machine, listed with an Intel Core i3 processor and 8 GB of memory.
- LAN ports
- 5+ LAN ports
- USB 3 ports
- 3-4 USB 3 ports
- Light outputs
- 4 built-in channels
- Memory
- 8 GB
The full range sits in vision controllers when the panel grows into more cameras and lights. If the panel is also what launches inspections, start an inspection automatically from a custom program covers that half of the design.
Build the histogram into the interface
Every name in the code below belongs to this article; nothing is a documented call from any toolkit. Port the loop to whatever language your interface uses.
-
Get the frame as 8-bit. If the camera streams 10- or 12-bit data, take the display copy or shift values down as in the table below.
-
Declare the counters once, outside the draw path, and clear them with your own loop — a runtime clear call is exactly the kind of outward reference a strict loader counts.
/* Own example names; types and initial values shown. */
static uint32_t hist[256]; /* one counter per grey level */
static uint32_t peak = 1; /* tallest bin this frame, minimum 1 */
int32_t w = 2448; /* example input: width in pixels */
int32_t h = 2048; /* example input: height in pixels */
int32_t stride = 2448; /* example input: row pitch in bytes */
const uint8_t *img = 0; /* example input: current frame */
for (int32_t b = 0; b < 256; b++) hist[b] = 0;
- Count with one pass over the rows. The stride-indexed form is safe for packed and padded buffers alike:
for (int32_t y = 0; y < h; y++) {
const uint8_t *row = img + (int64_t)y * stride;
for (int32_t x = 0; x < w; x++) {
hist[row[x]]++;
}
}
-
Prove the pass with the sum check. Add all 256 counters; the total must equal width × height. On the example frame, 2448 × 2048 = 5,013,504. A short sum means the loop read the wrong stride or the wrong bit depth — the two mistakes this check exists to catch.
-
Find the peak. One comparison per bin, and start the peak at 1, never 0, or a black frame divides by zero during scaling.
peak = 1;
for (int32_t b = 0; b < 256; b++)
if (hist[b] > peak) peak = hist[b];
- Scale and draw each bar. Height in pixels is count × plot height / peak, computed with a 64-bit product; the worked example below shows the 32-bit multiply breaking at ordinary frame sizes.
/* Inside the draw event, for each bin b: */
int32_t bar_px = (int32_t)(((int64_t)hist[b] * plot_h) / peak);
if (hist[b] > 0 && bar_px == 0) bar_px = 1; /* keep faint bins visible */
-
Draw 256 rectangles. A plot 512 px wide gives 2 px per bin, which stays readable, and any framework that can fill a rectangle can draw it.
-
Update at display rate, not per acquisition. Hook the pass to the panel tick; the grab loop belongs to the camera, and live edge detection from a video stream: wiring the grab loop shows the same discipline for processing code.
Worked example: a 12 MP mono frame at 30 fps
Inputs, each with its source. The frame source is the LS-CMR-0166, listed as a 12 MP USB3 area scan camera with 4000 × 3000 resolution at 30 fps. The display copy is 8-bit mono, which the panel requests from the inspection software, and the plot is 480 px tall — a panel-design choice. The rules in words: bins are two raised to the bit depth, counter state is bins times four bytes, and the scaling product must stay below the 32-bit limit.
Arithmetic, written out:
- Pixels per frame: 4000 × 3000 = 12,000,000 px. Packed at 8 bits, the buffer is 12,000,000 B, about 11.4 MiB.
- Histogram state: 256 bins × 4 B = 1,024 B, allocated once.
- Worst-case bin: a uniform frame stacks all 12,000,000 counts into one bin, against a 32-bit ceiling of 4,294,967,295 — headroom of about 358 times, so 32-bit counters hold.
- Scaling product: 12,000,000 × 480 = 5,760,000,000, past the 32-bit ceiling. The counters fit and the product does not, so the multiply runs in 64 bits.
- Time budget: 30 fps gives 1 / 30 s = 33.3 ms per frame. One pass costs 12,000,000 increments, 256 clears and 256 bar draws.
Decision, with its thresholds: keep a single 1,024-byte array for the life of the session; run the scaling product in 64 bits whenever pixels × plot height passes 4,294,967,295; and hold the whole update under 33.3 ms — if the measured pass plus drawing exceeds that, compute every second frame or on a fixed region of interest rather than dropping the plot.
Any area scan camera that streams mono frames suits the method; the camera finder filters by resolution and interface.
- Sensor format
- 1/1.7"
- Interface
- USB3
- Resolution
- 12 MP
- Shutter
- Rolling
Same loop, different source: bit depth and color
| Source | Levels | Bins kept | Counter array | Bin index |
|---|---|---|---|---|
| 8-bit mono | 256 | 256 | 1,024 B | value |
| 10-bit | 1,024 | 256 | 1,024 B | value >> 2 |
| 12-bit | 4,096 | 256 | 1,024 B | value >> 4 |
| 12-bit, full detail | 4,096 | 4,096 | 16,384 B | value |
Shifting right by 4 folds sixteen 12-bit levels into each displayed bin: right for an operator watching exposure drift, wrong for a measurement that must separate close grey levels. Full detail costs 16,384 B of counters and produces a plot no person can read — compute it in the inspection, draw it folded on the panel.
Color gives you three arrays — red, green and blue — at 3,072 B total, or one luma array with index = (r + g + b) / 3. Raw Bayer frames yield a mosaic histogram: honest for exposure checks, misleading for grey-level tolerances, so label the plot accordingly. When the counts drive a threshold instead of a person, automatic threshold segmentation in Lab color with Otsu's method is the next read. For what the shape tells you beyond exposure, machine vision edge inspection: backlight and histogram methods covers reading edges and levels off the plot.
Checks before you release the interface
- Synthetic ramp test. Fill 25,600 bytes with the values 0 to 255 repeated 100 times; every bin must read exactly 100 and the sum must read 25,600.
- Real-frame identity. The sum of the bins equals width × height, run on ten real frames including one dark frame.
- Peak guard. A black frame must draw a flat line, not crash on a division.
- Uniform-frame scaling. Cap the lens, feed one level everywhere, and confirm the bars render with the 64-bit product.
- Load test. Run the pass at production frame rate for ten minutes; if acquisition stutters while the panel draws, camera acquisition stalls under load: isolate recovery faults is the diagnostic path.
When the panel should not compute it
- The inspection tool already publishes statistics. Many packages compute the histogram in the inspection step and expose the result to the panel. Read those numbers and draw bars from them — display work only, zero image passes, and no dependency problem, because the panel only consumes what the tool hands it.
- The counts drive a measurement. Threshold selection belongs in the inspection, where numbers are logged and reproducible; the panel copy is for humans. Keep one source of truth.
- The camera does the inspection. Vision sensors and smart cameras run the inspection on the device and push results over the network; a PC panel recomputing statistics from compressed stream copies is doing it the hard way. AI vs rule-based smart camera inspection: how to choose covers that split. An x86-platform model such as the LS-SMC-0017, listed at 8.8 MP, can host self-contained add-on code under the same no-dependency rule.
- Interface
- USB 2.0
- Resolution
- 6-10 MP
- Sensor format
- 1"
- Shutter
- Global
Pick the host first — a vision controller for a PC panel, smart cameras when the device runs the inspection — then wire the loop into the draw event and run the checks above. If the panel plan needs a second opinion, tell us about the line and we will size the parts with you.
Questions
Why does the loader reject the histogram but accept the rest of the interface?
The loader copies your interface into a managed container and can only take code with no external references. A histogram block from a UI library is a sub-function plus a control type definition, sometimes a DLL call, so it is refused on sight. Arithmetic you write yourself, the counting loop, has no reference outside the file, so it passes. The rejection is structural, not a settings problem; no configuration flag relaxes it.
Do I need 256 bins, or does the bit depth change that?
For 8-bit mono, 256 bins covers every level exactly. A 10-bit source has 1,024 levels and a 12-bit source 4,096, so either keep full detail with a 1,024- or 4,096-element array (4,096 or 16,384 bytes as 32-bit counters) or fold the low bits away by shifting each value right 2 or 4 bits and stay at 256 bins. For operator displays 256 bins is the right choice; keep the full array only for measurements.
Can I use the histogram control from my UI toolkit if I only feed it my own counts?
No. The refusal comes from the block being present in the saved interface, whether or not it executes. A control from the toolkit library is exactly the outward reference the loader scans for. Draw the bars yourself: 256 filled rectangles scaled against the peak bin is a complete histogram plot, needs no control, and renders in any framework that can fill a rectangle.
How do I test the loop before connecting a camera?
Fill a synthetic buffer with a known pattern, for example pixel values 0 to 255 repeated 100 times across 25,600 bytes, run the loop, and check that every bin holds exactly 100 and that the bin sum equals 25,600. Then feed one real frame and check the sum equals width times height. Both checks take seconds and catch stride errors, wrong bit-depth assumptions and off-by-one loops before they reach production.
Where should the histogram run when the panel PC also runs the inspection?
On the interface update tick, not inside the acquisition callback. The counting pass is one trip through the frame buffer, so it belongs with drawing, triggered at display rate. If the panel and the inspection share one CPU, measure the pass with a timer and, when it competes with the grab, compute every second frame or on a fixed region of interest. An operator watching exposure drift does not need 60 updates a second.


