Task 712: .SXM File Format
Task 712: .SXM File Format
File Format Specifications for .SXM
The .SXM file format is used by Nanonis SPM (Scanning Probe Microscopy) software for storing scan image data from scanning tunneling microscopes (STM) or atomic force microscopes (AFM). It consists of an ASCII text header with key-value pairs, terminated by ":SCANIT_END:", followed by a binary marker ("\1A\04"), and then the binary image data. The header contains metadata about the scan, instrument settings, and channel information. The binary data section contains the image data for each channel as 32-bit floating-point numbers (float32), typically in big-endian byte order, with data for each channel stored sequentially (each channel is a flattened 2D array of height x width values, usually starting from the bottom row).
- List of all the properties of this file format intrinsic to its file system.
The properties are the header keys and their associated values, which describe the scan parameters, instrument settings, and data structure. Based on the format, the following are the typical intrinsic properties (keys) found in .SXM files. Some are mandatory, others optional depending on the scan configuration:
- SCANIT_TYPE: Data type of the binary data (e.g., "FLOAT" or "INT").
- SCANIT_VERSION: Version of the file format (e.g., "1.5").
- REC_DATE: Recording date (e.g., "2023-01-01").
- REC_TIME: Recording time (e.g., "12:00:00").
- REC_TEMP: Recording temperature (e.g., "4.2 K").
- ACQ_TIME: Acquisition time in seconds (e.g., "3600.0").
- SCAN_PIXELS: Number of pixels (width height, e.g., "512 512").
- SCAN_FILE: Original filename (e.g., "scan.sxm").
- SCAN_DIR: Scan direction (e.g., "up" or "both").
- SCAN_RANGE: Scan size in meters (x y, e.g., "1e-7 1e-7").
- SCAN_OFFSET: Scan offset in meters (x y, e.g., "0 0").
- SCAN_TIME: Time per line in seconds (forward backward, e.g., "0.5 0.5").
- SCAN_ANGLE: Scan rotation angle in degrees (e.g., "0").
- SCAN_SPEED: Scan speed in m/s (e.g., "1e-7").
- BIAS: Bias voltage in volts (e.g., "0.5").
- Current: Setpoint current in amps (e.g., "1e-10").
- Z-CONTROLLER: Z-controller settings (multi-line, e.g., name, on/off, setpoint, P gain, I gain, T gain).
- Channels: Number of channels (e.g., "4").
- DATA_INFO: Channel information (multi-line, one line per channel, e.g., "1 | Current | forward | A").
- Experiment: Experiment name (optional, e.g., "STM Scan").
- Comment: User comment (optional, multi-line text).
- Experiment parameters: Additional instrument-specific parameters (optional, multi-line).
The binary data is not a "property" but the image data itself, structured as channels * height * width * 4 bytes (float32).
- Two direct download links for files of format .SXM.
After extensive searching, I was unable to find public direct download links for .SXM files. Many repositories mention or parse .SXM files but do not host sample files. For demonstration purposes, you can generate .SXM files using Nanonis software or simulators, but no direct links were located. If you have access to Nanonis tools, sample files can be created there.
- Ghost blog embedded HTML JavaScript that allows a user to drag n drop a file of format .SXM and it will dump to screen all these properties.
Here is an embedded HTML page with JavaScript for drag-and-drop. It reads the file as text for the header, parses the key-value pairs (handling multi-line values), and displays the properties in a list on the page. It does not handle the binary data part for simplicity.
Drag and Drop .SXM File
- Python class that can open any file of format .SXM and decode read and write and print to console all the properties from the above list.
Here is a Python class. It reads the header, parses properties, prints them, and can write a new .SXM file with modified properties (binary data is copied as is for simplicity; in a full implementation, you'd decode/reencode the binary data based on dimensions and channels).
import struct
class SXMFile:
def __init__(self, filename):
self.filename = filename
self.properties = {}
self.binary_data = b''
self.read()
def read(self):
with open(self.filename, 'rb') as f:
content = f.read()
header_end = content.find(b':SCANIT_END:')
if header_end == -1:
raise ValueError('Invalid .SXM file: No SCANIT_END found.')
header = content[:header_end + 12].decode('utf-8', errors='ignore')
self.binary_data = content[header_end + 12 + 2:] # Skip \1A\04
lines = header.split('\n')
current_key = None
for line in lines:
line = line.strip()
if line.startsWith(':'):
current_key = line[1:-1].strip()
self.properties[current_key] = ''
elif current_key:
self.properties[current_key] += (self.properties[current_key] and '\n' or '') + line
def print_properties(self):
for key, value in self.properties.items():
print(f"{key}: {value}")
def write(self, new_filename):
with open(new_filename, 'wb') as f:
for key, value in self.properties.items():
f.write(f":{key}:\n{value}\n".encode('utf-8'))
f.write(b':SCANIT_END:\n\x1A\x04')
f.write(self.binary_data)
# Example usage:
# sxm = SXMFile('example.sxm')
# sxm.print_properties()
# sxm.write('new_example.sxm')
To arrive at the solution: The class opens the file in binary mode, finds the header end, parses lines into a dict handling multi-line values, stores binary data, prints the dict, and for write, reconstructs the header and appends the binary.
- Java class that can open any file of format .SXM and decode read and write and print to console all the properties from the above list.
Here is a Java class. Similar logic, using BufferedReader for header, byte array for binary.
import java.io.*;
import java.util.LinkedHashMap;
import java.util.Map;
public class SXMFile {
private String filename;
private Map<String, String> properties = new LinkedHashMap<>();
private byte[] binaryData = new byte[0];
public SXMFile(String filename) {
this.filename = filename;
read();
}
private void read() {
try (FileInputStream fis = new FileInputStream(filename);
BufferedReader reader = new BufferedReader(new InputStreamReader(fis))) {
StringBuilder header = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
header.append(line).append("\n");
if (line.trim().equals(":SCANIT_END:")) {
break;
}
}
parseHeader(header.toString());
// Read remaining binary (skip \1A\04)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
fis.skip(2); // Skip \1A\04
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
binaryData = baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Invalid .SXM file", e);
}
}
private void parseHeader(String header) {
String[] lines = header.split("\n");
String currentKey = null;
StringBuilder value = new StringBuilder();
for (String l : lines) {
l = l.trim();
if (l.startsWith(":")) {
if (currentKey != null) {
properties.put(currentKey, value.toString().trim());
}
currentKey = l.substring(1, l.length() - 1).trim();
value = new StringBuilder();
} else if (currentKey != null) {
value.append(l).append("\n");
}
}
if (currentKey != null) {
properties.put(currentKey, value.toString().trim());
}
}
public void printProperties() {
for (Map.Entry<String, String> entry : properties.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
public void write(String newFilename) {
try (FileOutputStream fos = new FileOutputStream(newFilename)) {
for (Map.Entry<String, String> entry : properties.entrySet()) {
fos.write( (":" + entry.getKey() + ":\n" + entry.getValue() + "\n").getBytes() );
}
fos.write(":SCANIT_END:\n".getBytes());
fos.write(new byte[] {0x1A, 0x04});
fos.write(binaryData);
} catch (IOException e) {
throw new RuntimeException("Error writing .SXM file", e);
}
}
public static void main(String[] args) {
SXMFile sxm = new SXMFile("example.sxm");
sxm.printProperties();
sxm.write("new_example.sxm");
}
}
- JavaScript class that can open any file of format .SXM and decode read and write and print to console all the properties from the above list.
Here is a JavaScript class for Node.js (using fs module). It reads the file, parses header, logs properties, and can write a new file.
const fs = require('fs');
class SXMFile {
constructor(filename) {
this.filename = filename;
this.properties = {};
this.binaryData = Buffer.alloc(0);
this.read();
}
read() {
const content = fs.readFileSync(this.filename);
const headerEndIndex = content.toString().indexOf(':SCANIT_END:');
if (headerEndIndex === -1) {
throw new Error('Invalid .SXM file: No SCANIT_END found.');
}
const header = content.toString('utf8', 0, headerEndIndex + 12);
this.binaryData = content.slice(headerEndIndex + 12 + 2); // Skip \1A\04
const lines = header.split('\n');
let currentKey = null;
let value = '';
for (let line of lines) {
line = line.trim();
if (line.startsWith(':')) {
if (currentKey) {
this.properties[currentKey] = value.trim();
}
currentKey = line.slice(1, -1).trim();
value = '';
} else if (currentKey) {
value += line + '\n';
}
}
if (currentKey) {
this.properties[currentKey] = value.trim();
}
}
printProperties() {
for (let key in this.properties) {
console.log(`${key}: ${this.properties[key]}`);
}
}
write(newFilename) {
let header = '';
for (let key in this.properties) {
header += `:${key}:\n${this.properties[key]}\n`;
}
header += ':SCANIT_END:\n';
const buffer = Buffer.concat([Buffer.from(header, 'utf8'), Buffer.from([0x1A, 0x04]), this.binaryData]);
fs.writeFileSync(newFilename, buffer);
}
}
// Example usage:
// const sxm = new SXMFile('example.sxm');
// sxm.printProperties();
// sxm.write('new_example.sxm');
- C class that can open any file of format .SXM and decode read and write and print to console all the properties from the above list.
Here is a C++ class (since C doesn't have "class" natively, but C++ does). It uses fstream for reading, parses header, prints, and writes.
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
class SXMFile {
private:
std::string filename;
std::map<std::string, std::string> properties;
std::vector<char> binaryData;
public:
SXMFile(const std::string& fn) : filename(fn) {
read();
}
void read() {
std::ifstream file(filename, std::ios::binary);
if (!file) {
throw std::runtime_error("Cannot open file");
}
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
size_t headerEnd = content.find(":SCANIT_END:");
if (headerEnd == std::string::npos) {
throw std::runtime_error("Invalid .SXM file: No SCANIT_END found.");
}
std::string header = content.substr(0, headerEnd + 12);
binaryData.assign(content.begin() + headerEnd + 12 + 2, content.end()); // Skip \1A\04
std::string line;
std::string currentKey;
std::string value;
size_t pos = 0;
while ((pos = header.find('\n', pos)) != std::string::npos) {
line = header.substr(0, pos);
header.erase(0, pos + 1);
line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); // Trim CR
if (line[0] == ':') {
if (!currentKey.empty()) {
properties[currentKey] = value;
}
currentKey = line.substr(1, line.size() - 2);
value = "";
} else if (!currentKey.empty()) {
value += line + "\n";
}
}
if (!currentKey.empty()) {
properties[currentKey] = value;
}
}
void printProperties() {
for (const auto& pair : properties) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
}
void write(const std::string& newFilename) {
std::ofstream file(newFilename, std::ios::binary);
if (!file) {
throw std::runtime_error("Cannot write file");
}
for (const auto& pair : properties) {
file << ":" << pair.first << ":\n" << pair.second << "\n";
}
file << ":SCANIT_END:\n";
char marker[2] = {0x1A, 0x04};
file.write(marker, 2);
file.write(binaryData.data(), binaryData.size());
}
};
// Example usage:
// int main() {
// SXMFile sxm("example.sxm");
// sxm.printProperties();
// sxm.write("new_example.sxm");
// return 0;
// }