Back to blog
Ali KamalyAli Kamaly
August 14, 2026
12 min read
Hardware Validation

How to Migrate from LabVIEW to Python in 2026 (Step-by-Step Guide)

A concrete, ordered migration plan from LabVIEW to Python, with the library equivalents for the VIs you actually use, real code, and the four traps that stall most migrations halfway.

How to Migrate from LabVIEW to Python in 2026 (Step-by-Step Guide)

Migrating from LabVIEW to Python is a sequencing problem, not a language problem. The libraries exist and are mature: pyVISA for SCPI instruments, nidaqmx for NI DAQ hardware, numpy and pandas for analysis. What stalls migrations is doing them in the wrong order and losing the ability to check the new results against the old.

This guide gives the ordered plan, the library equivalents for the VIs you actually use, real code, and the four traps that leave teams running both stacks for two years.

First, scope what should not move

Be precise about the boundary. Three things genuinely belong in LabVIEW:

  • LabVIEW FPGA. If you compile to an FPGA target, Python is not a replacement. Nothing on this page changes that.
  • NI Real-Time targets with deterministic loop requirements. See LabVIEW FPGA and real-time.
  • Certified legacy in regulated environments where the qualification cost of changing language exceeds the licence saving. This is a business decision, not a technical one.

Everything else, and for most teams that is 80 to 95 percent of the VIs, moves cleanly.

The library equivalents

LabVIEWPythonNotes
VISA Read/Write/OpenpyvisaSame underlying NI-VISA or Keysight IO layer
DAQmx VIsnidaqmxNI's own package, keeps your hardware
Waveform Chart / Graphmatplotlib, plotlyStatic and interactive respectively
Array and signal analysisnumpy, scipy.signalscipy is broader than LabVIEW's base analysis
Write to Measurement Filepandas.to_csv, nptdmsnpTDMS reads and writes TDMS
Report Generation Toolkitreportlab, WeasyPrint, python-docx
Front panelPyQt, Dash, StreamlitOr drop it, see below
State machine / queued messageplain classes, enum, asyncio
TCP/UDP VIssocket, requests
Serial (VISA)pyserial or pyvisa
Database Connectivity Toolkitsqlalchemy, psycopg
Call Library Function Nodectypes, cffiSame DLLs, called from Python

The ordered plan

Step 1: instrument the old system first

Before writing any Python, make the LabVIEW application log every measurement it takes to CSV with a timestamp, if it does not already. You need a reference dataset.

This is the single highest-value hour in the whole migration. Without it, "does the new code agree with the old code" is unanswerable and every subsequent step becomes a matter of opinion.

Step 2: port the instrument layer

This is mechanical. For each instrument, the LabVIEW VI is wrapping a SCPI string. Find the string, write the Python.

import pyvisa

class PowerSupply:
    def __init__(self, resource: str):
        self.rm = pyvisa.ResourceManager()
        self.inst = self.rm.open_resource(resource)
        self.inst.timeout = 5000
        idn = self.inst.query("*IDN?").strip()
        print(f"connected: {idn}")

    def set_voltage(self, volts: float, current_limit: float = 1.0):
        self.inst.write(f"VOLT {volts}")
        self.inst.write(f"CURR {current_limit}")

    def output(self, on: bool):
        self.inst.write(f"OUTP {'ON' if on else 'OFF'}")

    def measure_current(self) -> float:
        return float(self.inst.query("MEAS:CURR?"))

    def close(self):
        self.output(False)
        self.inst.close()

Write one class per instrument, matching the operations your tests actually use. Do not try to wrap the whole SCPI command set. You need maybe eight methods per instrument.

Checkpoint: run each class standalone against the real instrument and confirm it returns sane values before going further.

Step 3: port one complete test, then diff

Pick the simplest test in your suite. Port it end to end. Run it on the same DUT the LabVIEW version ran on, and diff the CSVs.

import pandas as pd

old = pd.read_csv("labview_run.csv")
new = pd.read_csv("python_run.csv")

delta = (new["VOUT"] - old["VOUT"]).abs()
print(f"max delta {delta.max():.6f} V, resolution 0.0001 V")
assert delta.max() < 0.0005, "results disagree beyond measurement resolution"

Agreement to the measurement's own resolution is the pass criterion. Not "looks about right".

Step 4: keep both stacks running, deliberately

During migration you will need to call LabVIEW from Python or the reverse. Both work.

LabVIEW VI from Python: build the VI into a DLL with the Application Builder, then ctypes.CDLL("mytest.dll").

Python from LabVIEW: the Python Node, available in LabVIEW 2018 and later, calls a Python function directly. This is the easier direction and lets you migrate test by test inside the existing application shell.

Use the Python Node approach if your application has a big front panel you are not ready to replace. You get to move the logic without touching the UI.

Step 5: decide honestly about the front panel

Most test applications do not need one. The front panel exists because LabVIEW makes it free, not because operators use it.

Ask what the panel is actually for:

  • Nobody watches it, it just runs. Delete it. A log file and a report replace it.
  • An operator starts runs and reads pass/fail. A small PyQt window or a web page. A day of work.
  • Engineers debug with live plots. Grafana or a Streamlit page, and better than the original.
  • It is a genuine instrument replica with 40 controls. Budget properly for PyQt, or keep this one application in LabVIEW.

Step 6: port the rest in batches, retire at renewal

Batch by instrument family, not by test order. All the DMM tests, then all the scope tests. The instrument layer is already done, so each batch goes faster than the last.

Keep one LabVIEW seat until a full quarter of production has run on Python. Then drop the licences at renewal. See LabVIEW pricing for what that saves.

The four traps

1. Rewriting instead of porting. The temptation is to fix the architecture while you migrate. Do not. Port the behaviour exactly, verify it matches, then refactor as a separate change with tests in place. Teams that combine the two cannot tell whether a discrepancy is a bug or an improvement.

2. Losing the reference. Repurposing the LabVIEW machine before the migration is complete. This is the one that turns a three-month migration into a year. The old system is your test oracle.

3. Timing loops written in Python. A while loop with time.sleep() is not how you acquire at 10 kHz. Use nidaqmx hardware timing, exactly as LabVIEW does under the hood.

task.timing.cfg_samp_clk_timing(
    rate=10000,
    sample_mode=nidaqmx.constants.AcquisitionType.CONTINUOUS,
    samps_per_chan=10000,
)

4. Underestimating the undocumented. There is always a magic delay, a retry, or a settling time that someone added in 2014 to fix an intermittent failure and never wrote down. You find these by diffing results, which is why step 1 matters.

What you gain beyond the licence saving

  • The tests run in CI. A pull request can run the test suite against simulated instruments. This is not possible in any practical way with LabVIEW.
  • Diffable code review. A VI diff is a screenshot. A Python diff is a diff.
  • Hiring. Every graduate knows Python.
  • The analysis and the test live in one language. No export to DIAdem as a separate stage.

Handling the VIs nobody understands

Every migration hits at least one VI that works, matters, and cannot be read. A practical procedure:

  1. 1

    Treat it as a black box and characterise it. Feed it a range of known inputs through a test harness and record the outputs. You now have a specification derived from behaviour rather than from reading the diagram.

  2. 2

    Reimplement against that specification, not against the block diagram.

  3. 3

    Compare across the full input range, including the edges and the invalid cases. Undocumented VIs frequently have special handling at boundaries, and that handling is often the reason they exist.

  4. 4

    If it cannot be characterised, build it into a DLL and call it from Python with ctypes for now. Do not block the migration on one VI.

Point 4 is the escape hatch that keeps migrations moving. A single incomprehensible VI should never hold up the other ninety.

A realistic phased timeline

For a single validation station with six instruments and around forty tests:

PhaseWorkElapsed
1Add CSV logging to the LabVIEW app, collect reference data1 day
2Write instrument classes, verify each against hardware3 to 5 days
3Port the simplest test, diff against reference1 day
4Port remaining tests in batches of ten2 to 3 weeks
5Replace or retire the front panel1 to 5 days
6Parallel running, both stacks, same DUTs2 to 4 weeks
7Cut over, keep LabVIEW seat until renewal

Six to ten weeks elapsed, of which perhaps three are hands-on. The parallel running phase is mostly waiting, and it is not optional.

Scale phase 4 by test count. Everything else stays roughly constant, which is why migrating a second station takes a fraction of the time of the first.

The project layout that keeps this maintainable

The failure mode after a successful migration is a directory of forty scripts that only their authors can run. The structure below is boring on purpose, and it is what separates a migration that lasts from one that gets rewritten again in three years.

validation/
  instruments/          # one module per instrument class, not per model
    dmm.py              #   open, configure, read, close
    psu.py
    scope.py
  measurements/         # the maths. Pure functions, no I/O, easy to test
    regulation.py
    efficiency.py
  sequences/            # one file per test, calls instruments + measurements
    line_regulation.py
    thermal_soak.py
  limits/
    pmic_rev_c.yaml     # limits as data, never hardcoded in the sequence
  reports/
    template.html
  tests/                # runs with no hardware present
    test_regulation.py

Three rules that carry most of the value:

  1. 1

    Limits live in data, not code. A new silicon revision is a new YAML file, not a code change and not a rebuild. This is the single biggest maintainability win over the typical LabVIEW front panel with numbers typed into constants.

  2. 2

    Measurement maths is pure functions. No instrument handles, no file writes. That makes it unit-testable with no hardware, which is the first time most validation maths has ever had a test.

  3. 3

    One module per instrument class, not per model. A DMM is a DMM. Keep the model-specific SCPI differences inside the module and the sequences stay readable when the bench changes.

Testing without the bench

The objection to code is "I cannot run it without the instruments". You can, and it is the capability LabVIEW never gave you.

# tests/test_regulation.py
from measurements.regulation import load_regulation_pct

def test_load_regulation_typical():
    assert load_regulation_pct(v_noload=3.310, v_fullload=3.288) == 0.665

def test_load_regulation_rejects_zero():
    import pytest
    with pytest.raises(ValueError):
        load_regulation_pct(v_noload=0.0, v_fullload=3.288)

For the instrument layer, a fake that returns canned responses lets the whole sequence run on a laptop:

class FakeDMM:
    def __init__(self, readings): self._r = iter(readings)
    def configure(self, *a, **k): pass
    def read(self): return next(self._r)

def test_sequence_flags_out_of_spec():
    dmm = FakeDMM([3.31, 3.29, 3.55])       # third point out of limits
    result = run_line_regulation(dmm, limits=(3.20, 3.40))
    assert result.verdict == "FAIL"
    assert result.first_failure_index == 2

This is what makes the migration pay off beyond the licence. A sequence that can be tested without hardware can be reviewed, changed confidently, and run in CI. See test sequencer comparison for how this fits alongside a formal sequencer, and automated test report generation for the output stage.

Common mistakes

  • Porting VI by VI. The VI boundaries reflect LabVIEW's dataflow model, not your test's logic. Port tests, not diagrams.
  • Hardcoding limits. They will change with every silicon revision. Put them in data on day one.
  • Rewriting validated measurement maths. Wrap it if it is in a DLL, port it with tests if it is not, but do not casually retype it.
  • Skipping the parallel run. Same DUT, both stacks, diff the CSVs. It is the only proof that survives an audit question.
  • Building a GUI first. Most migrated sequences run unattended and need a log file. Build the GUI last, if at all.
  • Using `time.sleep()` for instrument settling without checking. Poll the instrument's own status or use *OPC? where it is supported. Fixed sleeps are either too short and wrong or too long and slow.
  • One giant script per test station. See the layout above. The split into instruments, measurements, and sequences is what makes it maintainable.

Where TestFlow fits

Most LabVIEW migrations stall in the middle, with half the tests ported and both stacks running. Generating the mechanical half is how you get through the middle quickly.

  1. 1

    Connect your instruments. Pick the manufacturer and model, paste the VISA address (USB, LAN, GPIB, or serial), and the agent knows what is on your bench. No bench yet? Use a placeholder address, build the full automation, and swap in the real address when you are in the lab.

  2. 2

    Tell the agent what to test, in plain English. For example, "run a VI sweep from 1 to 10 V in 1 V steps at 0.5 A load current," or "suggest the tests for a power-management device."

  3. 3

    The agent builds the complete workflow in seconds. Instrument-aware automation appears on the canvas, with the generated scripts visible in a code panel you can inspect and edit.

  4. 4

    Run it in your lab. Click Run and the status panel streams results step by step, with measured values inline (VOUT = 3.301 V, asserted 3.2 to 3.4 V, PASS). One click exports a structured PDF report, or the raw results as CSV.

The TestFlow builder: a plain-English request on the left, the generated instrument workflow in the centre, and the live run with its streaming SCPI execution log on the right.
The TestFlow agent turning a plain-English request into a runnable workflow, then running it on the bench. Click to enlarge.
  • Vendor-neutral by design. One workflow drives Keysight, Tektronix, Rohde & Schwarz, NI, Rigol, Keithley, Anritsu, and more over standard VISA and SCPI.
  • Browser-based and shareable. Workflows live in your workspace, so a sequence built in one lab runs the same way in another.
  • Free version to start. Sign in at app.testflowinc.com and build your first workflow today; plans and quotes are on the pricing page.
Instrument vendors TestFlow drives over VISA and SCPI: Keysight, Tektronix, Rohde & Schwarz, NI, Keithley, Agilent, Anritsu, Siglent, Chroma, Fluke, Yokogawa, Kikusui, TDK-Lambda, ESPEC, Watlow, Pickering, Copper Mountain, inTEST, Thermonics, and Microchip
Works with the instruments already on your bench. Full list on the supported instruments page.

The step-by-step walkthrough, VISA address formats, and Test Planner prompts are all in the TestFlow product guide.

Frequently asked questions

Can Python fully replace LabVIEW?

For instrument control, data acquisition, sequencing, and reporting, yes. For hard real-time execution on an NI real-time target, and for FPGA work through LabVIEW FPGA, no. Scope your migration around that boundary.

Can Python control NI DAQ hardware without LabVIEW?

Yes. The nidaqmx package is NI's own Python API and talks to the NI-DAQmx driver directly. You keep your DAQ hardware and drop the LabVIEW licence.

How long does a LabVIEW to Python migration take?

For a single test station with a handful of instruments, days. For a production test system with hundreds of sequences and custom hardware, quarters. The variable is not code volume, it is how much undocumented behaviour is buried in the VIs.

What replaces LabVIEW's front panel in Python?

For operator UIs, PyQt or Dash. For engineering dashboards, Streamlit or Grafana. For most test applications the honest answer is that the front panel was not needed and a log plus a report replaces it.

Can I call LabVIEW VIs from Python during migration?

Yes. Build the VI into a DLL with the LabVIEW Application Builder and call it with ctypes, or use the LabVIEW Python Node in the other direction. This lets both stacks run during a phased migration.

Is Python fast enough to replace LabVIEW?

For SCPI instrument control the bottleneck is the instrument and the bus, not the language, so Python is equivalent. For tight acquisition loops use nidaqmx hardware timing rather than a Python loop, exactly as you would in LabVIEW.

Ready to automate your lab?

Connect your instruments, describe a test in plain English, and TestFlow builds and runs it in minutes.

Tags

labview to python migrationmigrate labview to pythonlabview pythonreplace labview with pythonpyvisanidaqmx
Share this article:
Ali Kamaly

Article by

Ali Kamaly

Ali Kamaly is the Co-Founder and CEO of TestFlow, an AI-native platform for electronics test automation. He writes about test automation, lab validation, and the infrastructure behind modern hardware engineering.

Put it on your bench this week

A new way for testing, from specs to automated sequences, capture clean data, and accelerate your validation cycle.