How to open a TDMS file on a Mac
NI’s Excel add-in and DIAdem are Windows only, but a browser viewer or the open-source npTDMS Python library reads .tdms files on macOS and Linux.
Double-clicking a .tdms file from a LabVIEW test stand does nothing useful on a Mac, because the tools NI provides for opening TDMS outside LabVIEW are Windows applications.
Inside a .tdms file: groups, channels and segments
TDMS (Technical Data Management Streaming) is the binary format LabVIEW, FlexLogger, DIAdem and NI-DAQmx logging write. Data is organized in three levels (the file, groups and channels), and each level can carry properties such as units, sample interval, sensor serial number or test operator.
According to NI’s format description, every write appends a segment, and each segment has a lead-in (a TDMS tag, a version number and lengths), metadata (object names, properties and indexing information) and a block of raw data. A file written in many small chunks therefore contains many segments, each with its own lead-in and metadata.
Next to the data file you will often see a .tdms_index file. NI describes it as an exact copy of the .tdms file without the raw data. It is optional. NI applications recreate it when it is missing, and the readers below work from the .tdms file alone, so when you copy data around, the .tdms file is the one to keep.
NI’s TDMS tools are Windows only
On Windows, most people open TDMS through the TDM Excel Add-In, which NI lists as Windows only (current version 2026 Q2), or through DIAdem, NI’s analysis package, whose release notes list Windows 10 and 11 and Windows Server as supported systems. LabVIEW can read TDMS, but buying a LabVIEW seat to read files is a heavy fix. Excel for Mac has no TDMS importer, and Excel stops at 1,048,576 rows anyway, which a few minutes of kilohertz data exceeds. If the file came from NI hardware you also need to work with, see using NI DAQ hardware with a Mac.
Option 1: The in-browser data file viewer
Our data file viewer opens a .tdms file in the browser so you can see what it contains before you write any code. It also opens HDF5, Parquet, MATLAB .mat and CSV, so you can check a converted file against the original in the same place.
Option 2: Python with npTDMS
npTDMS is an open-source Python library that reads and writes TDMS on any operating system. Install it with pandas support:
pip install "npTDMS[pandas]"
Open a file and look at its structure:
from nptdms import TdmsFile
tdms_file = TdmsFile.read("run.tdms")
print(tdms_file.properties) # file-level properties
for group in tdms_file.groups():
print("Group:", group.name)
for channel in group.channels():
unit = channel.properties.get("unit_string", "")
print(" ", channel.name, len(channel), unit)
Channels behave like numpy arrays, and properties are dictionaries on the file, group and channel objects:
ch = tdms_file["Measurements"]["AIN0"]
data = ch[:] # numpy array of all samples
t = ch.time_track() # seconds, from wf_start_offset and wf_increment
dt = ch.properties["wf_increment"]
Pass absolute_time=True to time_track() to get UTC timestamps instead of seconds from the start. Data logged with a separate timestamp channel is read like any other channel.
To get a pandas DataFrame, call as_dataframe() on the file, a group or a channel. On a group with time_index=True, the index is the time track:
df = tdms_file["Measurements"].as_dataframe(time_index=True)
print(df.describe())
Pitfalls when a TDMS file becomes a DataFrame
A group’s DataFrame lines channels up by sample index, not by time. If a group holds a 1 kS/s vibration channel and a 1 S/s temperature channel, as_dataframe() puts sample 10 of each in the same row and pads the shorter column with NaN, so row 10 is 10 ms into one channel and 10 s into the other. Build one DataFrame per rate, or give each channel its own time track and join on time.
time_track(), and as_dataframe(time_index=True) with it, raises a KeyError when a channel lacks wf_start_offset or wf_increment, which is common in files written by hand-built LabVIEW code rather than DAQmx logging. In that case look for a timestamp channel in the same group, or a sample rate stored as a custom property.
Absolute times are stored as seconds since 1 January 1904 UTC plus a 64-bit binary fraction, the LabVIEW epoch, according to NI’s format description. npTDMS converts timestamp properties such as wf_start_time to numpy.datetime64, so you don’t handle the 1904 offset yourself. Code that reads raw bytes, or a spreadsheet that receives a raw number, will show dates in 1904, or be off by about 66 years if it assumes the Unix epoch.
Reading large files without loading them
TdmsFile.read() loads every sample into memory. For files of several gigabytes, use TdmsFile.open(), which reads metadata up front and data only when you ask for it, and process the file in chunks:
import numpy as np
from nptdms import TdmsFile
with TdmsFile.open("long_run.tdms") as tdms_file:
ch = tdms_file["Measurements"]["AIN0"]
first_second = ch.read_data(offset=0, length=1000) # just a slice
peak, count = 0.0, 0
for chunk in ch.data_chunks(): # one chunk at a time
block = chunk[:]
peak = max(peak, float(np.abs(block).max()))
count += len(block)
print(count, "samples, peak", peak)
If you only need properties, TdmsFile.read_metadata("long_run.tdms") skips the data entirely. The handle from open() is only valid inside the with block, and npTDMS notes it is not thread-safe, so give each thread its own.
DAQmx raw data and scaling
Files written by NI-DAQmx’s built-in TDMS logging, including FlexLogger runs and LabVIEW tasks set to log, often store raw ADC counts plus scaling information instead of volts. npTDMS reads this DAQmx raw format and applies the stored scaling, so ch[:] returns engineering units. If you need the unscaled values, channel.raw_scaler_data (after read()) returns a dictionary of raw arrays per scaler, and read_data(scaled=False) does the same job on a file opened with open(). Thermocouple scaling needs an optional extra, pip install "npTDMS[thermocouple_scaling]". The npTDMS README lists two things it cannot read: files with XML headers and extended-precision floating-point data.
Option 3: MATLAB
Data Acquisition Toolbox includes tdmsread (since R2022a), which returns a cell array of tables or timetables, one per channel group, along with tdmsinfo, tdmsreadprop, tdmswrite and a tdmsDatastore for collections of files. MathWorks lists the TDMS functions on Windows and, since R2026a, the Debian distribution of Linux, with no macOS support. The same page says the TDMS functions work in MATLAB Online from R2026a, which is one way to use them from a Mac if you have a license that includes the toolbox. Otherwise, convert the file in Python and load the result into MATLAB on your Mac.
Option 4: Convert TDMS to CSV, Parquet or HDF5
If the people downstream don’t want TDMS, convert once and share the result. From the DataFrame above:
df = tdms_file["Measurements"].as_dataframe(time_index=True)
df.to_csv("run.csv") # opens anywhere, large and slow
df.to_parquet("run.parquet") # needs pyarrow; compact, fast for pandas/Polars/DuckDB
tdms_file.as_hdf("run.h5") # needs h5py; keeps groups and properties as attributes
CSV drops the properties, so units and sample rates are lost unless you write them into the header yourself. Parquet keeps types and compresses well. HDF5 preserves the group and channel hierarchy most closely. CSV, MATLAB, TDMS, Parquet or HDF5 compares the formats in more detail, and the DAQ file size calculator estimates how big each output will be.
For files that arrive regularly, convert a whole folder at once. This writes one Parquet file per group next to each .tdms file, and because each file is opened lazily, only one group’s data is in memory at a time:
from pathlib import Path
from nptdms import TdmsFile
for path in sorted(Path("incoming").glob("*.tdms")):
with TdmsFile.open(path) as tdms_file:
for group in tdms_file.groups():
try:
df = group.as_dataframe(time_index=True)
except KeyError: # no wf_increment / wf_start_offset
df = group.as_dataframe()
out = path.with_name(f"{path.stem}_{group.name}.parquet")
df.to_parquet(out)
print("wrote", out, len(df), "rows")
as_dataframe() on an opened file still reads that group’s data into memory, so for single groups larger than your RAM, loop over data_chunks() and append to the output file in pieces. The file name scheme also assumes group names are safe in a path, which breaks for groups named after dates with slashes in them.
Producing TDMS from a Mac
Sometimes the data was recorded on a Mac and a colleague’s DIAdem workflow expects TDMS. npTDMS can write files with TdmsWriter. DAQAtlas, which is in development, exports recordings from MCC, LabJack, Digilent and SCPI devices directly to TDMS as well as CSV, MATLAB, Parquet and HDF5. The beta list is open.