Reading XPS Header and Data From the Same File
hrodstein
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 https://www.wavemetrics.com/forum/general/loading-2d-data-matrix-its-fi…
For background information and links to other solutions, see https://www.wavemetrics.com/code-snippet/reading-header-and-data-same-f…
#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
endif
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
endif
fieldName = headerLine[0,pos-1]
valueStr = headerLine[pos+2,lineLength-2]
return 0 // Success
End
// 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 ""
endif
String resultStr = ""
int lineNumber = 0
do
String line
FReadLine refNum, line
if (strlen(line) == 0)
break // No more text in file
endif
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
continue
endif
resultStr += fieldName + ":" + valueStr + ";"
lineNumber += 1
if (lineNumber >= maxHeaderLines)
// There are no more header lines to examine
break
endif
while(1)
Close refNum
return resultStr
End
// 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
endif
endif
// 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
endif
// 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
End
#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
endif
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
endif
fieldName = headerLine[0,pos-1]
valueStr = headerLine[pos+2,lineLength-2]
return 0 // Success
End
// 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 ""
endif
String resultStr = ""
int lineNumber = 0
do
String line
FReadLine refNum, line
if (strlen(line) == 0)
break // No more text in file
endif
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
continue
endif
resultStr += fieldName + ":" + valueStr + ";"
lineNumber += 1
if (lineNumber >= maxHeaderLines)
// There are no more header lines to examine
break
endif
while(1)
Close refNum
return resultStr
End
// 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
endif
endif
// 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
endif
// 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
End
Forum
Support
Gallery
Igor Pro 9
Learn More
Igor XOP Toolkit
Learn More
Igor NIDAQ Tools MX
Learn More
Sample XPS data file (from https://www.wavemetrics.com/comment/9726#comment-9726).
September 13, 2021 at 01:25 pm - Permalink