CDF in plain words¶
This page explains how a CDF file is organized. You don’t need to know it to read a file. You do need it to produce good files, and it makes everything else in this documentation easier to follow.
We use the ACE magnetometer file from the Quickstart as the example. To follow along, load it first:
import urllib.request
import pycdfpp
url = ("https://spdf.gsfc.nasa.gov/pub/data/ace/mag/level_2_cdaweb/"
"mfi_h0/2020/ac_h0_mfi_20200101_v07.cdf")
urllib.request.urlretrieve(url, "ac_h0_mfi_20200101_v07.cdf")
cdf = pycdfpp.load("ac_h0_mfi_20200101_v07.cdf")
The big picture¶
A CDF file holds two things:
Global attributes. Information about the whole file: mission, instrument, version, who made it, and so on.
Variables. The data. Each variable has a name, a type, a shape, its values, and its own attributes.
ac_h0_mfi_20200101_v07.cdf
├── global attributes
│ ├── TITLE "ACE> Magnetometer Parameters"
│ ├── Project "ACE>Advanced Composition Explorer", "ISTP>International ..."
│ └── ...
└── variables
├── Epoch time of each sample
├── BGSEc magnetic field, 3 components ── attributes: UNITS="nT", DEPEND_0="Epoch", ...
├── label_BGSE "Bx GSE", "By GSE", "Bz GSE"
└── ...
In pycdfpp, the file is a pycdfpp.CDF object. It works like a dictionary
of variables, and its attributes property is a dictionary of global attributes.
Records¶
Most variables are time series. Each measurement is stored as one record.
BGSEc has 5401 records, one every 16 seconds. Each record holds 3 numbers: the X, Y
and Z components of the field. So its shape is (5401, 3):
cdf["BGSEc"].shape # (5401, 3)
len(cdf["BGSEc"]) # 5401 records
The first dimension is always the record number. The other dimensions describe one
record. A spectrogram could have shape (n_records, 32) for 32 energy channels. A
particle distribution could have (n_records, 16, 32, 8).
Some files store scalar values with an extra dimension of size 1. That’s why ACE’s
Epoch has shape (5401, 1). Use .ravel() or [:, 0] if you want a flat
array.
Variables that don’t change: NRV¶
Some variables don’t depend on time. Examples: the labels of a vector’s components, or the table of energy channels of a particle instrument. Storing them in every record would waste space.
These are non-record-varying variables, or NRV. They hold a single record:
cdf["label_BGSE"].is_nrv # True
cdf["label_BGSE"].shape # (1, 3, 6): 1 record of 3 strings of 6 characters
Variables that change with time are record-varying (is_nrv is False).
Note
pycdfpp always shows the record count as the first dimension, even for NRV
variables. An NRV variable has shape (1, ...), or (0, ...) if it is empty.
Attributes¶
There are two kinds of attributes, and they behave differently.
Global attributes describe the file. Each one holds a list of entries, and each entry can have its own type. Long texts are often split into several entries, one per line:
project = cdf.attributes["Project"]
len(project) # 2 entries
project[0] # 'ACE>Advanced Composition Explorer'
project[1] # 'ISTP>International Solar-Terrestrial Physics'
Variable attributes describe one variable. Each one holds a single value. That value can be a string, a number, or an array of numbers:
attrs = cdf["BGSEc"].attributes
attrs["UNITS"].value # 'nT'
attrs["VALIDMIN"].value # [-65534.0, -65534.0, -65534.0]
Some variable attributes point to other variables by name. DEPEND_0 names the time
variable. LABL_PTR_1 names the variable holding the component labels. This is how
a CDF file ties its variables together.
Data types¶
Every variable and every attribute value has a CDF type. Here is how they map to numpy:
CDF type |
numpy dtype |
Notes |
|---|---|---|
|
|
|
|
|
|
|
|
There is no unsigned 64-bit type in CDF. |
|
|
The two names mean the same thing. |
|
|
The two names mean the same thing. |
|
|
Fixed-width strings. See below. |
|
|
Time. See below. |
|
|
Time. See below. |
|
two |
Time. See below. |
In pycdfpp, these types are listed in pycdfpp.DataType.
Strings¶
CDF strings have a fixed width. All strings in a variable use the same number of
characters. .values gives them as bytes. .values_encoded decodes them to Python
strings:
cdf["label_BGSE"].values # [[b'Bx GSE' b'By GSE' b'Bz GSE']]
cdf["label_BGSE"].values_encoded # [['Bx GSE' 'By GSE' 'Bz GSE']]
Time¶
CDF has three time types. Which one a file uses depends on who made it, and when.
Type |
What is stored |
Precision |
|---|---|---|
|
Nanoseconds since 2000-01-01 12:00 (J2000), counting leap seconds. |
1 nanosecond |
|
Milliseconds since year 0, as a float. No leap seconds. |
1 millisecond |
|
Two numbers: seconds since year 0, and picoseconds. |
1 picosecond |
Use TT2000 for new files. It is the modern standard, it handles leap seconds, and
it is what pycdfpp uses by default when you store datetime64 values.
You rarely need to handle these types yourself. pycdfpp converts all three to
numpy datetime64 in one call. See Working with time.
Fill values and valid ranges¶
Sometimes an instrument has no valid measurement: it was off, saturated, or the data was corrupted. The record is still there, so every variable keeps the same length. It holds a special fill value instead of a measurement.
A file can also leave records out entirely, to save space (“sparse records”). pycdfpp
fills those records the way NASA’s CDF library does: with the previous record when the
variable asks for it, otherwise with FILLVAL, otherwise with the variable’s pad
value. So a missing record reads like a fill value, and you can mask both at once.
The FILLVAL attribute tells you which value that is. VALIDMIN and VALIDMAX
give the range of physically meaningful values. Always mask values outside it before
you compute anything. The Cookbook shows how.
attrs["FILLVAL"].value # [-1e+31]
Majority¶
Multi-dimensional arrays can be stored in row-major order (C, Python) or column-major order (Fortran, IDL). A file uses one or the other.
You don’t need to care. pycdfpp always gives you normal row-major numpy arrays,
whatever the file uses.
Compression¶
A CDF file can be compressed as a whole, or variable by variable. pycdfpp
decompresses on the fly. It reads and writes the two codecs used in practice, gzip and
RLE. The Huffman codecs of the standard are not supported, but they are very rarely
used. When writing, you choose. See Compression.
Conventions: ISTP¶
The CDF format itself doesn’t say which attributes a file must have, or what they mean.
The space physics community agreed on conventions for that, called ISTP. They say,
for example, that each data variable must have UNITS, FILLVAL and a
DEPEND_0 pointing to its time variable.
Files that follow ISTP work out of the box with tools like CDAWeb, Speasy and SciQLop. If you produce files, read Producing ISTP-compliant files.