System designTroubleshooting

Coordinate system update differs in a DLL or EXE vs SubVI

Why a coordinate system update differs in a DLL or built EXE, how to prove which calibration file opened, and the path rules that fix the shift.

LumaSpec Vision12 min read Share on LinkedIn

On this page 9 sections
  1. 01Why does the coordinate system update land in a different place in the EXE than in the development environment?
  2. 02Where does a relative calibration path resolve in a built EXE or DLL?
  3. 03Why does the DLL call behave differently from the SubVI call in the same run?
  4. 04How can you prove which calibration file the update step actually opened?
  5. 05How do you make the calibration path survive the build?
  6. 06Why did a small test application not reproduce the shift?
  7. 07Which numbers tell you it is a calibration fallback and not a matching fault?
  8. 08What should the deployed vision runtime look like?
  9. 09Questions

The inspection ran all week in the development environment: the coordinate system update settled on the fiducial within a couple of tenths of a millimetre, and every measurement downstream agreed. Compile the same function into a DLL, or build the application into an EXE, and the same image puts the coordinate system somewhere else entirely. Nothing in the algorithm changed, so the fault is almost never in the algorithm. It sits in what the update step reads from disk when it starts, and where the runtime process looks for it. The sections below follow the order you meet the fault: the fallback mechanism, the working-directory rule behind it, why a DLL call differs from a SubVI, the two-log test that proves the cause, the path rules that survive a build, and the numbers that separate a calibration fallback from a matching fault.

Why does the coordinate system update land in a different place in the EXE than in the development environment?

The update step is almost never wrong in the EXE: it opens its calibration image or descriptor file through a relative path, the EXE resolves that path against its own folder instead of your project folder, and the step silently falls back to a pixel-unit default calibration.

The update step does two jobs, and the first one depends on a file. It reads a learned calibration — pixel scale, lens distortion, reference origin — then moves that reference onto the fiducial the matcher finds in the current image. In development, the learn step found its calibration image or grid descriptor at commissioning time, and the environment's working directory has pointed at that folder ever since.

At runtime the path string is identical; the folder it names is not. The learn step errors or starts from a blank, pixel-unit calibration, the update still runs, the origin still moves, and the numbers downstream come back in pixels where you expected millimetres. An origin that lands tens of pixels from nominal — instead of fractions of a millimetre — is that fallback showing itself. When the origin moves even in development, the matcher is at fault instead, and fixing a pattern-matching reference coordinate system is the article for that case.

Where does a relative calibration path resolve in a built EXE or DLL?

Relative paths resolve against the working directory of the process: in development that directory is stable and points into your project area, an EXE launched by double-click gets its own folder as the working directory, and a DLL inherits whatever directory its host process was started in.

Three launch paths, three answers. Double-click the EXE and the working directory is the folder holding the EXE. Start it from a console and the directory follows the console, even when you type the full path to the EXE from somewhere else. Let another program or a service start it and the directory is inherited, which is how relative paths end up naming system folders nobody chose. Inside the development environment the directory has been stable for the life of the project, which is exactly why the fault only appears once you leave it.

The same rule catches primitives that report the calling VI's own location: compiled into a DLL there is no VI file left to report, and built into an EXE the reported location sits inside the build rather than your project. Any path derived from those primitives moves with the compiled code, so derive it from a location you control instead — the fix comes under the build rules below.

Why does the DLL call behave differently from the SubVI call in the same run?

The SubVI runs inside the development environment, with the environment's working directory and whatever calibration state the VI still holds in uninitialized shift registers or globals; the DLL runs in the host process with a different working directory and a fresh instance, so none of that carried state exists.

State is the second half of the fault. A functional global or an uninitialized shift register in the SubVI carries the learned calibration from the last run of the session, and a development session stays alive for days, so the state is always there. A DLL instance can be loaded and unloaded by its host between steps, and a fresh load starts with default state — if the coordinate update relied on calibration learned in a previous call, the new instance has never heard of it.

The same applies to images and sessions: a reference the caller passes across the boundary points at real memory, while a reference the DLL creates for itself lives and dies with the instance. Pass the calibration in, pass the image in, and return the updated coordinate system out; nothing important should ride on instance memory. A .NET dispatcher that returns a DLL name changes none of this: the DLL lands in the same process and inherits the same working directory. The mechanics of crossing that boundary with camera SDK calls are covered in calling a camera SDK DLL from LabVIEW, and the instance rules are why reusing image buffers instead of per-frame allocation matters once code is compiled.

How can you prove which calibration file the update step actually opened?

Log the exact path the step opens, its existence check and the error cluster to a text file beside the EXE, once in development and once in the EXE, and diff the two logs — the runtime line names a folder that does not exist, and that is the fault.

Run the comparison once and the cause is settled:

  1. At the calibration-learning step, wire the error cluster through to a popup and a log line. If it was unwired, wire it now — the silent fallback lives exactly there.
  2. Before the step runs, build the full path string it will open and write that string, plus a file-exists result, to a log file beside the EXE.
  3. Run once in the development environment and once as the built EXE, same image, same part, and keep both logs.
  4. Compare the two path lines. A different directory, a missing folder or a false existence check at runtime is the cause, proven in one line.
  5. Log the calibration units the step reports as well. Pixel units after a supposedly successful learn confirm the fallback even when a file did open.

When the runtime log also shows missing libraries or support files, the deployment is incomplete rather than calibration-only: the article on diagnosing missing DLLs behind a vision application startup error covers that wider case, and the build mechanics behind it are the same ones that produce SubVI-not-found errors — the walkthrough for resolving SubVI-not-found errors in built applications applies the same checks.

How do you make the calibration path survive the build?

Resolve the file from a location you control at runtime and ship the file there: copy the calibration file beside the EXE in the build output, then open it with a path built from the executable's own directory, never from the development project tree.

The rules, in order of preference:

  • Copy the calibration file into the build output beside the EXE and assemble its path from the executable directory at runtime. The file then ships with the application and cannot drift out of the project tree.
  • Keep one configured absolute path — in an INI file or a start-up setting written at commissioning — and read it at start-up. This survives builds and lets the cell move its data location without a rebuild.
  • For the DLL, pass the resolved path in as an input from the caller. The caller knows where it was started from; the DLL cannot know.
  • Fail loudly. If the file is missing or the units report as pixels after the learn, stop the inspection and raise the error; a run on a fallback calibration is worse than a stopped line.

The same four rules cover every support file a vision application opens: model files, overlays, lookup tables, camera parameter sets. Machine vision model file paths and the deployment errors they cause works through the matching fault for trained models, and the fix is identical here.

Stamp the calibration file with the part, field of view and date it was learned, and compare the stamp at start-up: a calibration learned at a different working distance is a quieter second source of large shifts.

Why did a small test application not reproduce the shift?

The small test matched the directory layout, not the code: its calibration file sat in the same folder as the test EXE, so a double-click set the working directory there and the relative path resolved, while your real application keeps the file in a project subfolder the build never copies.

To reproduce the fault on purpose, build the small application into an empty folder with no calibration file anywhere near it and run it — the shift appears. Copy the calibration file into that folder beside the EXE and run it again — the shift disappears. The same experiment explains a puzzling variant: launch the supposedly fixed EXE from a console sitting in a different directory and the shift returns, because the working directory follows the console rather than the EXE. That gives you a repro on demand, with no debugger and no rebuild.

Which numbers tell you it is a calibration fallback and not a matching fault?

Convert the shift to millimetres with the pixel scale: at a 100 mm field of view across a 2448-pixel sensor, the scale is 0.041 mm per pixel, so a 73-pixel origin shift is 3.0 mm — six times a 0.5 mm tolerance — while the calibrated run holds under 0.1 mm.

pixel scale (mm/px) = field of view (mm) / sensor width (px) origin error (mm) = origin shift (px) × pixel scale (mm/px)

Worked example, on a 5 MP area scan camera reading a 100.0 mm field of view across 2448 pixels:

  • Pixel scale: 100.0 mm / 2448 px = 0.0409 mm/px, call it 0.041.
  • Fallback run: the origin sits 73 pixels from nominal. 73 px × 0.0409 mm/px = 2.99 mm, about 3.0 mm — against a ±0.5 mm tolerance, the part fails by six times the limit.
  • Calibrated run: the same image, origin settled onto the fiducial to within 2 pixels. 2 px × 0.0409 mm/px = 0.082 mm, about 0.08 mm — roughly a sixth of the tolerance.
  • Field of view scales the damage: at 50.0 mm the same 73-pixel shift is 1.5 mm. The millimetres move with the optics; the signature does not.

The signature is the giveaway. A fallback origin lands on a whole number of pixels, because it is a raw pixel count with no calibration behind it. A healthy coordinate system reports positions off the pixel grid, in learned units, repeatable to a fraction of a pixel. Once everything reads in pixels, coordinate conventions start to leak into the numbers too — the half-pixel offset in blob centroids is the classic case of a whole extra pixel appearing from convention alone.

What should the deployed vision runtime look like?

Run the built EXE on fixed hardware that mirrors the development station: the same vision library and camera driver versions, the calibration files shipped beside the executable and checksummed at start-up, and a fanless vision controller reserved for vision instead of a shared office PC that updates underneath you.

Prevention list, per release:

  • Ship the calibration file beside the EXE and checksum it at start-up.
  • Resolve every support file from the executable directory or the configured data path, never from a project-relative path.
  • Wire and log the error cluster at every calibration step, and stop the inspection when the learn fails or the units report as pixels.
  • Log resolved paths and calibration units at every start-up, appending between runs.
  • Match vision library and camera driver versions between the development station and the runtime machine.
  • Release-test the built EXE twice: double-clicked from a clean folder, and started from a console in a different directory.

A purpose-built vision controller takes the shared-machine variables off the table: fixed start-up, camera and light wiring already on the box, and the vision software licensed for deployed applications. Small cell, one or two cameras:

Vision controllersHikrobot MV-VC3301H-128G66 I3-8100, 8GB+128G SSD, No PoE, PCIe expansion supported, Incl. VM6200Pro licenseMV-VC3301H-128G66
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
$2,419.00View part
Vision controllersHikrobot MV-VC2140-128G40-1T VC2100 Vision Controller, N97, 8GB+128G SSD+ 1THDDMV-VC2140-128G40-1T
LAN ports
3-4 LAN ports
USB 3 ports
2 USB 3 ports
$1,679.00View part

The MV-VC3301H-128G66 pairs an Intel Core i3 processor with 8 GB of memory, 5+ LAN ports, 4 built-in light channels, PCIe expansion and a VM6200Pro license included. The MV-VC2140-128G40-1T runs an Intel N-series processor with 8 GB of memory, a 128 GB SSD plus a 1 TB HDD, 3-4 LAN ports and 2 USB 3 ports. Cells carrying several cameras over PoE want more headroom:

Vision controllersHikrobot MV-VC5525-256G70-NN VC5000 Vision Controller, 12th-I5, 32GB+256G SSD, Supported PCIEMV-VC5525-256G70-NN
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
PoE
PoE camera ports
Light outputs
8 built-in channels
$4,479.00 · quotedView and quoteBuild a system with it

The MV-VC5525-256G70-NN pairs a 12th-gen Intel Core i5 with 32 GB of memory, a 256 GB SSD, PoE camera ports, 8 built-in light channels and 5+ LAN ports.

Before ordering, run the clean-folder test on the bench machine with the release build, then match controller to camera count and light channels on the vision controllers category page or start at the system configurator. More faults in this family — start-up errors, path bugs, buffer discipline — sit in the systems topic, and the contact page reaches the applications team when a deployment keeps fighting the path rules.

Questions

Can you embed the calibration in the EXE instead of shipping a file?

You can, and it removes the path fault entirely: serialise the learned calibration to a data file you include in the build and read it with a path resolved from the executable directory, or store it in a string constant. The practical compromise is a small calibration folder beside the EXE, written once at commissioning and read-only afterwards, so an operator can relearn after moving the camera without a rebuild.

How often does calibration need relearning compared with the coordinate system update?

Two different rates: the calibration learn runs once at commissioning and again only after you move or replace the camera, change the lens, f-number or working distance, or alter the fixture. The coordinate system update runs every part, because it only moves the learned reference onto the located fiducial. If the update step needs relearning to hold tolerance, the calibration is wrong, not the part.

What does a silent calibration fallback cost on the line?

It hides until a part fails downstream: the update step still returns coordinates, but in pixels against a default reference, so a 3 mm placement error can pass as a 73-unit shift nobody checks. The fail-safe costs almost nothing by comparison — one error wire and a units check at startup stop the run before scrap, and the path log names the missing file in the first line.

Should you keep the coordinate update as a SubVI or call it as a DLL?

Keep the form your dispatcher needs: the SubVI-versus-DLL choice does not change the accuracy of a correct installation, because the shift came from the missing calibration file, not the call mechanism. If the .NET dispatcher returns a DLL name, call the DLL and pass the calibration path in as an input; if you can call the SubVI directly, do that and delete one deployment variable.