---
title: "LabVIEW to Python: A Phased Migration Plan"
description: "A LabVIEW to Python migration plan in phases: inventory your VIs, move instrument I/O to PyVISA, then sequences, data and reports, with LabVIEW still running."
url: https://galoislabs.ai/blog/labview-to-python-migration
author: Alex Hernandez
author_url: https://galoislabs.ai/blog/authors/alex-hernandez
published: "2026-08-01"
topic: Comparisons
publisher: Galois Labs
---

# LabVIEW to Python: a phased migration plan that keeps the lab running

![A PXI-style modular chassis drawn as a dithered plate, one module pulled halfway out of its slot.](https://galoislabs.ai/blog/figures/ni-1.light.webp)

*FIG. 1 — MODULAR CHASSIS, ONE SLOT OPEN*

Migrate from LabVIEW to Python in phases, not in one rewrite. Inventory your VIs, move instrument I/O to PyVISA first, then sequences, then data and reports, and retire LabVIEW only where it stops earning its keep. LabVIEW and Python run side by side the whole time, so the lab keeps testing while the code moves.

This plan is for test engineers and lab managers who own a working LabVIEW bench and cannot take it offline for a quarter. Each phase ends with an exit criterion, and the checklist at the end puts all of them in one table.

## Why test teams move from LabVIEW to Python

The case rests on three things: hiring, code review, and cost.

**Hiring.** In our July 2026 study of 1,021 hardware test job postings from 70 companies, Python appeared in 54.8% of postings and LabVIEW in 7.1%. Of the 72 postings that named LabVIEW, 68 also named Python ([method and limits](https://galoislabs.ai/blog/hardware-test-hiring-study)). Job ads count the skills employers ask for, not what their benches run. For anyone staffing a bench, though, the signal is plain: the engineers you can hire already write Python.

**Review.** A VI is a binary file. Git can store it, but it cannot show a line diff, a pull request cannot display what changed, and git cannot merge two edits to the same VI. NI does ship graphical tools for this: the [Compare VIs](https://www.ni.com/docs/en-US/bundle/labview/page/comparing-vis.html) dialog, plus LVCompare.exe and LVMerge.exe, which a source-control client can call. They work, but only for someone with LabVIEW installed, and outside the review flow the rest of the engineering organization uses. A Python module or a YAML sequence gets reviewed the same way as firmware.

**Cost.** NI's US list price for LabVIEW Professional is $2,805 a year on subscription. The LabVIEW+ Suite, which adds TestStand, is $4,239 a year per seat. PyVISA is MIT-licensed and costs nothing.

(Source: [NI, LabVIEW editions and pricing, US list, read 2026-10-05](https://www.ni.com/en/shop/labview/select-edition.html))

License cost alone rarely justifies a migration; engineering hours do. None of this makes LabVIEW bad at what it does. It makes LabVIEW code expensive to staff and hard to review, which is why the plan moves the code that changes most often first. For a feature-by-feature view, see [LabVIEW compared with Galois](https://galoislabs.ai/compare/labview).

## Phase 0: inventory your LabVIEW VIs

Before moving anything, sort the VI hierarchy by what each VI does. Five buckets cover most test code:

| Bucket         | What it looks like in LabVIEW                                       | Where it goes                                |
| -------------- | ------------------------------------------------------------------- | -------------------------------------------- |
| Instrument I/O | VISA open, write and read; instrument driver VIs; IVI calls         | Python with PyVISA (Phase 1)                 |
| Sequencing     | State machines, test step VIs, limit checks, TestStand code modules | Text sequences in git (Phase 2)              |
| Logging        | TDMS, CSV or database writers                                       | Python data layer (Phase 3)                  |
| Reporting      | VIs that fill Word, Excel or HTML templates                         | Reports generated from run records (Phase 3) |
| Operator UI    | Front panels that operators use on the floor                        | Case by case (see below)                     |

Then sort the hardware the VIs touch into three classes:

- **SCPI instruments** over GPIB, USB, LAN or serial: supplies, DMMs, scopes, SMUs, loads. This is the easy part, because the same VISA resource strings work from Python.
- **NI DAQ and modular instruments** that use NI-DAQmx or NI's modular drivers (PXI DMMs, SMUs, digitizers, switches). These move too, through NI's own Python packages rather than VISA.
- **FPGA and Real-Time targets** such as CompactRIO and PXI real-time controllers. Mark them now; they usually stay in LabVIEW.

While you are in the code, flag VIs that depend on VISA events, service requests or GPIB serial polls. PyVISA can do all of these, but each one needs deliberate porting: plan it on purpose rather than translating it line by line, and check it against the operations your target VISA backend lists (the `pyvisa-galois` backend [publishes its list](https://docs.galoislabs.ai/guides/pyvisa/)).

**Exit criterion:** every VI in the production path has a bucket, an owner and a hardware class.

## Phase 1: move instrument I/O to PyVISA

Start where the payoff is highest and the risk is lowest. PyVISA can wrap the same NI-VISA library LabVIEW uses. With no argument, `pyvisa.ResourceManager()` picks an installed IVI VISA implementation such as NI-VISA, and falls back to the pure-Python PyVISA-py backend when none is installed ([PyVISA backend docs](https://pyvisa.readthedocs.io/en/latest/introduction/configuring.html)). Resource names follow the VISA specification, so the strings in your VIs and in NI MAX work unchanged ([resource name syntax](https://pyvisa.readthedocs.io/en/latest/introduction/names.html)). On a Linux bench PC without a vendor VISA, [PyVISA on Linux](https://galoislabs.ai/blog/pyvisa-linux-instrument-control) covers the PyVISA-py setup for each bus.

Port one instrument-I/O VI at a time, and keep its inputs and outputs to plain numbers and strings:

```python title="rail_check.py"
import pyvisa

# The same VISA resource strings the VIs use today.
PSU = "GPIB0::5::INSTR"
DMM = "TCPIP0::192.168.1.50::inst0::INSTR"


def measure_rail(setpoint_v: float) -> float:
    """Set the supply, read the rail. A float in and a float out,
    so a LabVIEW Python Node can call this function unchanged."""
    rm = pyvisa.ResourceManager()  # NI-VISA if installed, else pyvisa-py
    with rm.open_resource(PSU) as psu, rm.open_resource(DMM) as dmm:
        psu.timeout = 5000  # ms
        dmm.timeout = 5000
        psu.write(f"VOLT {setpoint_v:.3f}")
        psu.write("OUTP ON")
        try:
            psu.query("*OPC?")
            return float(dmm.query("MEAS:VOLT:DC?"))
        finally:
            psu.write("OUTP OFF")
```

The SCPI strings are typical for a bench supply and a DMM; copy the exact ones from the VI you are replacing.

### Run LabVIEW and Python side by side

NI ships two bridges that let both languages share the bench during the move. LabVIEW 2018 and later can call a Python function from a block diagram through the [Python Node](https://www.ni.com/en/support/documentation/supplemental/18/installing-python-for-calling-python-code.html), as long as the Python install has the same bitness as LabVIEW. TestStand's [Python Adapter](https://www.ni.com/docs/en-US/bundle/teststand/page/python-adapter.html) calls module functions and class methods directly from steps. Replace the inside of one VI or one step with a Python call, run the station as before, compare results, and move to the next one. The calling code does not change.

One rule while both run: only one program owns an instrument at a time. If a LabVIEW loop and a Python script can both reach the same supply, open it with an exclusive VISA lock (`access_mode=pyvisa.constants.AccessModes.exclusive_lock`) or route all traffic through a single owner.

### Where Galois fits in Phase 1

Galois is agent-driven test engineering for hardware teams: agents generate tests and instrument drivers, run them on real benches through the open-source galois-edge daemon, and turn the results into reports and a shared engineering record.

For Phase 1, the relevant piece is [`pyvisa-galois`](https://docs.galoislabs.ai/guides/pyvisa/), a PyVISA backend installed alongside `pyvisa` from the Galois package index ([install steps](https://docs.galoislabs.ai/guides/pyvisa/#installation)). The ported script changes by one line:

```python title="rail_check.py"
rm = pyvisa.ResourceManager("@galois")  # was: pyvisa.ResourceManager()
```

Calls then travel through the Galois backend (set by `GALOIS_BACKEND_URL` and `GALOIS_AUTH_TOKEN`) to the galois-edge daemon on the bench PC that owns the instrument. The machine running the script needs neither NI-VISA nor GPIB drivers, and the backend handles instrument locking and timeouts. `query`, `write`, `read` and timeouts behave as PyVISA scripts expect. Galois ships 573 instrument profiles across 135 manufacturers in its [instrument library](https://galoislabs.ai/instruments). When an instrument has a matching profile, the resource also gets typed methods such as `measure_current()` next to raw SCPI, so you stop writing a wrapper class per model. The [quickstart](https://docs.galoislabs.ai/getting-started/quickstart/) covers installing the daemon, and [SCPI instrument automation with Python](https://galoislabs.ai/blog/scpi-automation-python) goes deeper on the scripting patterns. The [PyVISA comparison](https://galoislabs.ai/compare/pyvisa) lays out what the backend adds and what it leaves alone. To have the platform draft, run and report the same rail check without porting the function, see [the agent path below](https://galoislabs.ai/blog/labview-to-python-migration#how-to-run-the-rail-check-in-galois-with-évariste).

**Exit criterion:** every instrument in the production path is reachable from Python with its existing resource string, and Python and LabVIEW readings agree on a known DUT within the instrument's accuracy.

## Phase 2: move test sequences into version-controlled text

With instrument calls in Python, move the logic that decides what to measure, in what order and against which limits. In LabVIEW that is usually a state machine or a TestStand sequence. The target is the same whichever tool you pick: a text file in git, with limits written into the file and a record of every step on every run.

Plain pytest can do it. Google's [OpenHTF](https://github.com/google/openhtf) (Apache-2.0) structures a test as phases with declared measurements and validators. In Galois, a sequence is a YAML file stored in git:

```yaml title="rail-check.sequence.yaml"
name: "rail-check"
description: "Power the DUT from 12 V and check the 5 V rail."
steps:
  - name: "Set input to 12 V"
    type: action
    config:
      instrument_id: "psu"
      command_name: "source_voltage"
      parameters: { value: "12.0" }

  - name: "Enable output"
    type: action
    config:
      instrument_id: "psu"
      command_name: "output_on"

  - name: "Settle"
    type: wait
    config: { duration_ms: 500 }

  - name: "Measure 5 V rail"
    type: numeric_limit
    config:
      instrument_id: "dmm"
      command_name: "measure_voltage_dc"
      low_limit: 4.9
      high_limit: 5.1
      unit: "V"
      comparison: "GELE"

  - name: "Disable output"
    type: action
    config:
      instrument_id: "psu"
      command_name: "output_off"
```

![Galois sequence builder showing a version 2, production-locked test sequence in its YAML view, with History and New Version controls. Demo data.](https://galoislabs.ai/blog/labview-to-python-migration/sequence-yaml-light.webp)

*FIG. 2 — Sequence as versioned YAML (demo data)*

Steps come from a small set of types: Action, Measure, Wait, Numeric Limit, String Value, Pass/Fail, Loop, Condition and Sequence Call. A limit names its comparison explicitly (`GELE` means low ≤ value ≤ high), so nobody has to guess whether a boundary passes. Every run stores, per step, the SCPI command sent, the raw response, the measured value, the limits and the instrument, with the operator and DUT serial on the run. A sequence an agent writes starts as a draft and cannot run until an engineer approves it. Edit an approved sequence and it needs approval again, because the approval is tied to the git commit.

If TestStand is your sequencer today, you have a choice: keep it and let the Python Adapter call the migrated code modules, or move the sequences too. [TestStand alternatives](https://galoislabs.ai/blog/teststand-alternatives) compares the options.

**Exit criterion:** each migrated sequence gives the same verdicts as the LabVIEW version on the same DUTs, and every limit in the file traces to a specification.

## Phase 3: move data, reports, and test evidence

**Data.** Many LabVIEW benches write TDMS files. Do not convert the archive on day one. The open-source [npTDMS](https://github.com/adamreeve/npTDMS) package reads TDMS files into NumPy arrays, so historical data stays usable from Python while new runs write to whatever store your team queries.

**Reports.** Report VIs that fill Word or Excel templates tie the report format to code, and the report can drift from the data it claims to summarize. Generate reports from the stored run record instead. Galois renders each run's report, as HTML or PDF, from the sequence version, the run and every step result.

**Evidence.** Because the record keeps the command sent and the raw response for each step, a reviewer can trace any number in a report back to the instrument exchange that produced it. That record, kept for every run, replaces the folder of exported spreadsheets.

**Exit criterion:** no new run depends on LabVIEW for logging or reporting, and historical data opens from Python.

## Phase 4: retire LabVIEW where it no longer earns its keep

Retire LabVIEW one station at a time, not one company at a time. A station is ready when no VI sits in its production path, its sequences and reports come from text, and the side-by-side runs from Phases 1 and 2 agree.

Time retirements to renewals. NI notes that [subscriptions can be renewed](https://www.ni.com/en-us/shop/product/labview.html) at the end of their terms at then-current prices, so each renewal date is a natural checkpoint for the seat count. Keep LabVIEW seats for FPGA and Real-Time code, for stations you chose not to migrate, and for anyone who still needs to open old VIs.

**Exit criterion:** every remaining LabVIEW seat maps to code that still needs it.

## How to run the rail check in Galois with Évariste

Phases 1 and 2 port the rail check by hand: a PyVISA function, then a YAML sequence. Évariste, the agent in the Galois platform, does the same work from a plain-English objective written from the test specification, not from the block diagram. Open it from the app sidebar (Ctrl+Shift+E) beside a project and state the task with its limits:

> Create a rail-check sequence. Power the DUT from 12 V with the supply on GPIB0::5::INSTR, wait 500 ms, measure the 5 V rail with the DMM at TCPIP0::192.168.1.50::inst0::INSTR, pass between 4.9 V and 5.1 V inclusive, then turn the supply output off.

Évariste lists the instruments connected across your team's edges and reads each one's profile commands. If the supply has no profile in the library, upload its programming manual as a PDF; Évariste generates a profile and, after you review it, deploys it to an edge and binds it to the instrument. Either way, named profile commands stand in for the SCPI strings that `measure_rail()` copied out of the VI.

It then drafts a sequence of those commands in the YAML format of `rail-check.sequence.yaml`, and for this objective the draft should match that file step for step: `source_voltage` with `value: "12.0"` on `psu`, `output_on`, a 500 ms `wait`, a `numeric_limit` step calling `measure_voltage_dc` on `dmm` with `low_limit: 4.9`, `high_limit: 5.1` and `comparison: "GELE"`, then `output_off`.

The sequence lands as a draft and does not run until an engineer approves it. Check each step's instrument, the 12 V setpoint, the settle time, both limits against the specification, and that the run ends with `output_off`, as `measure_rail()` does in its `finally` block; [how to review an AI-generated test plan](https://galoislabs.ai/blog/review-ai-generated-test-plan) lists what else to check. Ask Évariste for changes in conversation or edit in the sequence builder. Every change is a new version with history and diffs, and an approved version can be production-locked.

Wiring the supply and DMM to the DUT and bench safety stay with you, and so does the Phase 1 rule that one program owns an instrument at a time: keep the LabVIEW station off both while the run is going. Start the run; galois-edge executes it on the bench and Monitor shows the channels live. Commands you send from the conversation ask for confirmation when they are flagged as dangerous.

Each step is recorded with its measured value, limits, pass or fail, the raw command and response, instrument, operator, DUT serial and timestamps. Run the known DUTs from the Phase 2 exit criterion and compare the verdicts with the LabVIEW station. Ask Évariste which steps failed or passed close to a limit, or how this unit compares with earlier runs; its answers cite the runs and notes they draw on. "Generate a test report from the last run" produces a PDF or HTML report from a LaTeX template; add the LabVIEW station's verdicts for the same known DUTs in the report editor, so the report shows the Phase 2 comparison. Results can be shared to Slack.

Your part is the objective, the limits from the specification, the review, the approval and the bench setup. The PyVISA function, the hand-written sequence file, error handling, logging and the report script are no longer yours to write or maintain. [AI test automation for hardware benches](https://galoislabs.ai/blog/ai-test-automation-hardware) covers where agents fit in the rest of the test workflow.

| Step             | Code path (this guide)                                   | Galois with Évariste                                                              |
| ---------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------- |
| Find instruments | Resource strings from the VIs and NI MAX                 | "List connected instruments" across the team's edges                              |
| Instrument I/O   | `measure_rail()` with SCPI copied from the VI            | Library profile, or one generated from the uploaded manual and reviewed           |
| Sequence         | `rail-check.sequence.yaml` written by hand, 4.9 to 5.1 V | Plain-English objective, same limits; Évariste drafts the sequence                |
| Review           | Code review of the script and the YAML file              | Draft reviewed, edited with versioned diffs, approved                             |
| Run              | Script or sequence on the bench beside LabVIEW           | Run through galois-edge, watched live in Monitor                                  |
| Record           | Run records from the Phase 3 data layer                  | Per-step value, limits, pass/fail, raw command and response, operator, DUT serial |
| Verify           | Same verdicts as LabVIEW on known DUTs                   | Same check; Évariste flags failures and near-limit passes and compares runs       |
| Report           | Generated from the stored run record                     | PDF or HTML report generated from the run, editable in the report editor          |

## What doesn't migrate cleanly from LabVIEW

- **LabVIEW FPGA.** Python does not compile to an FPGA. Keep the FPGA VI in LabVIEW and drive the compiled bitfile from Python with NI's [nifpga](https://github.com/ni/nifpga-python) package (MIT), which reads and writes FPGA controls and registers on RIO hardware such as CompactRIO.
- **LabVIEW Real-Time on cRIO and PXI controllers.** A deterministic loop on a real-time target is not something a desktop Python process replaces. Leave the target code in place and move the host side: sequencing, logging and reports.
- **Front-panel operator UIs.** In LabVIEW the operator screen comes with the code. In Python it is a separate piece of work, whether that is a Qt app, a web page or a sequencer's run view. Check which panels operators actually use; many developer panels need no replacement.
- **NI DAQ and modular instruments.** These migrate, just not through VISA. NI maintains [nidaqmx](https://github.com/ni/nidaqmx-python), an MIT-licensed Python API for NI-DAQmx on Windows and Linux (the NI-DAQmx driver is still required), and [nimi-python](https://github.com/ni/nimi-python) for modular instrument drivers including NI-DMM, NI-DCPower, NI-SCOPE, NI-FGEN and NI-SWITCH. Port them as in Phase 1, using NI's packages instead of PyVISA.

## When to stay on LabVIEW

A full migration is not always the right call. Staying on LabVIEW, in whole or in part, makes sense when:

- **The system is FPGA- or Real-Time-heavy.** If the core of the test runs on CompactRIO or a PXI real-time controller, it stays in LabVIEW anyway, and a second toolchain for the host may not pay for itself.
- **The station is stable and validated.** If nobody edits the code, reviewability buys little, and in a regulated process a rewrite reopens validation.
- **Your team is fluent in G and is not hiring.** A team that already uses Compare VIs and LVMerge with source control has solved much of the review problem in its own way.
- **You rely on NI's integrated stack.** NI has added an agent to it. NI's documentation describes Nigel, NI's agent in LabVIEW, as [powered by Microsoft Azure OpenAI](https://www.ni.com/docs/en-US/bundle/labview/page/nigel-ai.html) and able to describe local project structures, suggest code elements based on existing code, and generate VIs. NI's [setup page](https://www.ni.com/docs/en-US/bundle/labview/page/using-nigel-ai.html) says it needs an ni.com account tied to an active subscription, multi-seat license or Software Service Program.
- **Operators depend on front panels** that would be expensive to rebuild and retrain on.

A partial migration is a legitimate end state. Many benches will settle on Python for instrument I/O, sequences and reports, with LabVIEW kept for FPGA, Real-Time targets and a few panels. The [National Instruments comparison](https://galoislabs.ai/compare/national-instruments) covers the wider NI stack.

## LabVIEW to Python migration checklist

| Phase               | Deliverable                                                 | Main risk                                               | Exit criterion                                         |
| ------------------- | ----------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------ |
| 0. Inventory        | VI list with bucket, owner and hardware class               | Hidden dependencies: subVIs, globals, SRQ handlers      | Every production VI classified                         |
| 1. Instrument I/O   | Python functions per instrument task, same resource strings | Two programs driving one instrument                     | Python and LabVIEW readings agree on a known DUT       |
| 2. Sequences        | Text sequences in git with explicit limits                  | Limits copied wrong from VI constants                   | Same verdicts on the same DUTs; limits trace to a spec |
| 3. Data and reports | Run records, generated reports, readable TDMS archive       | Reports drifting from data                              | No new run needs LabVIEW for logging or reports        |
| 4. Retirement       | Smaller LabVIEW seat count                                  | Dropping a seat that FPGA or Real-Time code still needs | Every remaining seat maps to code that needs it        |

To start Phase 1 on real instruments, install the galois-edge daemon from the [quickstart](https://docs.galoislabs.ai/getting-started/quickstart/), install `pyvisa-galois` with `GALOIS_BACKEND_URL` and `GALOIS_AUTH_TOKEN` set for your Galois Cloud backend, and point one existing PyVISA script at `@galois`.

## Frequently asked questions

### Can Python replace LabVIEW?

For most test code, yes. Instrument control over VISA, test sequencing, data handling and reporting all have mature Python tools, and NI itself publishes Python APIs for NI-DAQmx, its modular instruments and its FPGA interface. The usual exceptions are code that runs on LabVIEW FPGA and LabVIEW Real-Time targets, which tends to stay in LabVIEW while Python drives it from the host.

### Can I migrate from LabVIEW without writing Python?

For SCPI instruments, sequences and reports, yes. You give Évariste, the agent in the Galois platform, a plain-English objective with limits from your specification, such as powering the DUT from 12 V and passing the 5 V rail between 4.9 V and 5.1 V, and it drafts a versioned sequence from the instruments' profiles. You review and approve the draft, run it on the bench through galois-edge, and Évariste reads the per-step results and generates the test report. FPGA and Real-Time code stays in LabVIEW either way.

### Can I keep my NI hardware?

Yes. PyVISA uses NI-VISA as its backend when it is installed, so instruments behind NI GPIB, USB and Ethernet interfaces keep working with the same resource strings. NI maintains MIT-licensed Python packages for NI-DAQmx (nidaqmx), for modular instruments such as NI-DMM, NI-DCPower and NI-SCOPE (nimi-python), and for LabVIEW FPGA bitfiles on RIO hardware (nifpga).

### How long does a LabVIEW migration take?

It depends on what the inventory finds: how many VIs do instrument I/O, how much FPGA and Real-Time code exists, how many operator panels are in daily use, and whether stations need revalidation after a change. Run the inventory first and estimate from it. Because LabVIEW and Python run side by side, each phase can take as long as it needs without stopping the lab.

### Can AI tools read LabVIEW VIs?

Not as source text. A VI is a binary file, so tools that read code cannot parse it the way they parse a Python module. NI's agent, Nigel, works inside LabVIEW, and NI documents that it can describe local project structures, summarize code and generate VIs. Outside LabVIEW, the open-source pylabview project extracts VI resources to XML and was tested mainly against LabVIEW 2014. For a migration, working from the test specification and the instrument manual is usually more dependable than translating a block diagram.
