Reading XPS Header and Data From the Same File

This snippet is a solution for an Igor user who wanted to use header information to set the scaling of a matrix loaded from an XPS file. This solution uses the technique of storing the header information in a list of keyword-value pairs and later querying that list for specific fields.

This simplified solution stems from…

For background information and links to other solutions, see…

#pragma TextEncoding = "UTF-8"
#pragma rtGlobals=3             // Use modern global access method and strict wave access
#pragma DefaultTab={3,20,4}     // Set default tab width in Igor Pro 9 and later

// Reads header information and numeric data from a text file with a format like this:
//      #
//      #  ImageWidth: 1376 pixel
//      #  ImageHeight: 1040 pixel
//      #  KineticEnergy: 14 eV
//      #  PassEnergy: 160 eV
//      #  EnergyScalingOffset: 3.44767 eV
//      #  EnergyScalingFactor: 0.0153488 eV/pixel
//      #  AzimuthalScalingOffset: 6.99327 deg
//      #  AzimuthalScalingFactor: -0.0134615 deg/pixel
//      #  
//      00003800    00002400    00001200    00003800    00002400    ... (start of numeric data)

// The numeric data is matrix data to be loaded into a 2D wave.
// The user wanted to use the AzimuthalScalingOffset and AzimuthalScalingFactor
// fields to set the matrix X scaling and the EnergyScalingOffset and EnergyScalingFactor
// fields to set the matrix Y scaling. He also wanted to store all header information
// in the wave note.
// To understand this file it is best to read it from the bottom (high-level) up.

// GetFieldNameAndValueStrFromHeaderLine(headerLine, fieldName, valueStr)
// headerLine is a possible line of header from the file.
static Function GetFieldNameAndValueStrFromHeaderLine(headerLine, fieldName, valueStr)
    String headerLine       // Input
    String& fieldName       // Output
    String& valueStr        // Output
    int lineLength = strlen(headerLine)
    String prefix = "#  "       // Header lines start with # followed by two spaces
    if (CmpStr(headerLine[0,2],prefix) != 0)
        return -1                   // Not a header line
    headerLine = headerLine[3,lineLength]       // Strip prefix
    lineLength -= 3
    // Header lines after prefix start with "<Field Name>: "
    int pos = strsearch(headerLine, ": ", 0)
    if (pos < 0)
        // Did not find ": "
        return -1                   // Failure
    fieldName = headerLine[0,pos-1]
    valueStr = headerLine[pos+2,lineLength-2]

    return 0                    // Success

// ReadHeaderInfo(pathName, fileName, maxHeaderLines)
// Returns a list of field names and values as colon-separated keyword-value pairs.
static Function/S ReadHeaderInfo(pathName, fileName, maxHeaderLines)
    String pathName         // Name of symbolic path. Ignored if filePath is full path.
    String fileName         // File name, relative path or full path
    int maxHeaderLines      // Maximum lines of header at start of file

    Variable refNum
    Open/R/P=$pathName refNum as fileName
    if (refNum == 0)        // File not opened - bad pathName or fileName
        return ""
    String resultStr = ""
    int lineNumber = 0
        String line
        FReadLine refNum, line
        if (strlen(line) == 0)
            break                   // No more text in file
        String fieldName        // e.g., "EnergyScalingOffset"
        String valueStr         // e.g., "3.44767 eV"
        if (GetFieldNameAndValueStrFromHeaderLine(line, fieldName, valueStr) != 0)
            // No field on this line
            lineNumber += 1
        resultStr += fieldName + ":" + valueStr + ";"
        lineNumber += 1
        if (lineNumber >= maxHeaderLines)
            // There are no more header lines to examine
    Close refNum
    return resultStr

// DemoReadXPSHeaderAndData(pathName, fileName)
// Demonstrates reading header information and data from a file like the "XPS Sample Data.txt" file.
// In this case, the file contains about 30 header lines followed by one column
// of numeric data. The user wants to use certain header fields to form the wave name.
// To display an Open File dialog, execute:
//      DemoReadXPSHeaderAndData("", "")
// To load a file without an Open File dialog dialog, execute:
//      Create a symbolic path named "XPSData" and then execute
//      DemoReadXPSHeaderAndData("XPSData", "XPS Sample Data.txt")
// If you are not familiar with Igor's "symbolic path" concept, execute this:
//      DisplayHelpTopic "Symbolic Paths"
// If the wave already exists, this routine overwrites it.
Function DemoReadXPSHeaderAndData(pathName, fileName)
    String pathName     // Name of an Igor symbolic path or "" to get an Open File dialog
    String fileName     // Name of file or full path to file or "" to get an Open File dialog

    // If necessary, display an Open File dialog to get a valid reference to a file
    if ((strlen(pathName)==0) || (strlen(fileName)==0))
        // Display dialog looking for file
        Variable refNum
        String fileFilters = "Data Files (*.txt,*.dat,*.csv):.txt,.dat,.csv;"
        fileFilters += "All Files:.*;"
        Open /D /R  /F=fileFilters /P=$pathName refNum as fileName
        fileName = S_fileName               // S_fileName is set by Open/D
        if (strlen(fileName) == 0)      // User cancelled?
            return -1
    // Read header information
    String headerInfo = ReadHeaderInfo(pathName, fileName, 100)
    // Printf "headerInfo=%s\r"     // For debugging only
    // Load matrix wave
    LoadWave/G/P=$pathName/A=image/O/M/Q fileName
    if (V_Flag != 1)        // We expect 1 wave to be created
        return -1               // Failure
    // Create wave reference
    String name = StringFromList(0, S_waveNames)    // S_waveNames is created by LoadWave
    WAVE w = $name
    // Store header information in wave note
    String noteText = ReplaceString(";", headerInfo, "\r")  // Each field on separate line
    Note w, noteText
    // Set X scaling
    double x0 = NumberByKey("AzimuthalScalingOffset", headerInfo)
    double dx = NumberByKey("AzimuthalScalingFactor", headerInfo)
    SetScale/P x x0, dx, "eV", w
    // Set Y scaling
    double y0 = NumberByKey("EnergyScalingOffset", headerInfo)
    double dy = NumberByKey("EnergyScalingFactor", headerInfo)
    SetScale/P y y0, dy, "°", w
    return 0                    // Success





Igor Pro 9

Learn More

Igor XOP Toolkit

Learn More

Igor NIDAQ Tools MX

Learn More