Task 530: .PDS File Format

Task 530: .PDS File Format

File Format Specifications for .PDS

The .PDS file format primarily refers to the Planetary Data System (PDS) format developed by NASA for storing and archiving planetary, solar, and lunar data. It is a structured format consisting of an ASCII-based label using Object Description Language (ODL) to describe metadata, followed by (or referencing) data objects (which can be binary or text). The label provides self-describing properties about the file's structure, content, and data objects. PDS supports both attached labels (label + data in one file) and detached labels (label points to separate data files). The format is defined in PDS3 standards (older ODL-based) and PDS4 (XML-based), but based on available specifications, this focuses on PDS3 as it aligns with classic .PDS usage in archival data. Key references include NASA's PDS Standards Reference and the Encyclopedia of Graphics File Formats summary.

List of All Properties Intrinsic to This File Format
The properties are metadata keywords defined in the ODL label, which describe the file's structure, records, and data objects. These are intrinsic to the format's "file system" (i.e., its logical structure for organizing records, pointers, and objects, enabling portable access across systems). Not all are required in every file, but they form the core set from PDS specifications. Properties are hierarchical (e.g., under OBJECT blocks) and can include scalars, arrays, or units.

File-Level Properties (Global Label Keywords):

  • SFDU_LABEL: Optional identifier for Space Data Systems standards (e.g., "CCSD3ZF0000100000001NJPL3IF0PDS200000001 = SFDU_LABEL").
  • RECORD_TYPE: Type of records (e.g., FIXED_LENGTH, STREAM).
  • RECORD_BYTES: Bytes per record.
  • FILE_RECORDS: Total number of records in the file.
  • LABEL_RECORDS: Number of records used by the label.
  • FILE_TYPE: Overall file type (e.g., IMAGE, TABLE).
  • HEADER_RECORDS: Number of header records.
  • HEADER_RECORD_BYTES: Bytes in header records.
  • END: Mandatory terminator for the label.

Pointer Properties (References to Objects or External Files):

  • ^OBJECT_NAME: Pointer to a data object (e.g., ^IMAGE = 10 for record number, or ^IMAGE = "SAMPLE.IMG" for external file).

Object-Specific Properties (Within OBJECT =  ... END_OBJECT blocks):

  • OBJECT: Starts an object description (e.g., OBJECT = IMAGE).
  • NAME: Name of the object.
  • LINES: Number of lines in an array (e.g., image height).
  • LINE_SAMPLES: Samples per line (e.g., image width).
  • SAMPLE_BITS: Bits per sample (e.g., 8, 16).
  • SAMPLE_TYPE: Data type (e.g., MSB_INTEGER, IEEE_REAL).
  • BANDS: Number of bands (for multi-dimensional data).
  • BAND_STORAGE_TYPE: How bands are stored (e.g., BAND_SEQUENTIAL).
  • AXIS_NAME: Axis identifiers for arrays (e.g., LINE, SAMPLE).
  • SEQUENCE_NUMBER: Order in multi-object files.
  • BIT_MASK: Mask for bit-packed data.
  • UNIT: Units for values (e.g., METERS).
  • DESCRIPTION: Text description of the object.
  • END_OBJECT: Terminates the object block.

Additional Intrinsic Properties (Common in PDS for Data Integrity and Context):

  • DATA_SET_ID: Identifier for the dataset.
  • PRODUCT_ID: Unique product identifier.
  • PRODUCER_ID: Producer of the data.
  • MISSION_NAME: Associated mission (e.g., PIONEER VENUS).
  • INSTRUMENT_NAME: Instrument used.
  • TARGET_NAME: Target body (e.g., VENUS).
  • START_TIME/STOP_TIME: Time range of data.
  • INTERCHANGE_FORMAT: Format for data exchange (e.g., BINARY, ASCII).
  • COMMENTS: Embedded comments (/* ... */).

These properties enable the format's self-describing nature, allowing software to locate and interpret data without external knowledge. The "file system" aspect refers to the record-based structure (fixed or variable length) and pointers, which act like a virtual directory for accessing objects.

Two Direct Download Links for .PDS Files

Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .PDS File Dump
Here's a self-contained HTML page with embedded JavaScript that allows dragging and dropping a .PDS file. It reads the file as text (assuming the label is ASCII), parses the ODL label to extract all properties (key-value pairs, including nested objects), and dumps them to the screen in a readable format. It stops parsing at "END" and handles basic ODL syntax (ignores comments, handles multi-line values).

PDS File Property Dumper

Drag and Drop .PDS File to Dump Properties

Drop .PDS file here
  1. Python Class for .PDS File Handling
    Here's a Python class that opens a .PDS file, decodes the ODL label, reads and prints all properties, and supports writing a new .PDS file with modified properties (assuming attached label + simple binary data stub for demo; extend for full data write).
import re

class PDSFile:
    def __init__(self, filepath):
        self.filepath = filepath
        self.properties = {}
        self.data = b''  # For attached data

    def read(self):
        with open(self.filepath, 'rb') as f:
            content = f.read()
            label_end = content.find(b'END')
            if label_end != -1:
                label = content[:label_end + 3].decode('ascii', errors='ignore')
                self.data = content[label_end + 3:]
                self.properties = self._parse_odl(label)
            else:
                raise ValueError("No 'END' found in label.")

    def _parse_odl(self, label):
        props = {}
        current = props
        stack = [props]
        for line in label.splitlines():
            line = line.strip()
            if not line or line.startswith('/*'): continue
            if re.match(r'OBJECT\s*=\s*', line):
                obj_name = re.split(r'=\s*', line)[1].strip()
                current[obj_name] = {}
                stack.append(current[obj_name])
                current = current[obj_name]
            elif line == 'END_OBJECT':
                stack.pop()
                current = stack[-1]
            elif '=' in line and line != 'END':
                key, value = [part.strip() for part in re.split(r'=\s*', line, 1)]
                current[key] = value
            elif line == 'END':
                break
        return props

    def print_properties(self):
        import json
        print(json.dumps(self.properties, indent=4))

    def write(self, new_filepath, new_properties=None, new_data=b''):
        if new_properties:
            self.properties = new_properties
        label = self._build_odl(self.properties)
        with open(new_filepath, 'wb') as f:
            f.write(label.encode('ascii'))
            f.write(new_data or self.data)

    def _build_odl(self, props, indent=0):
        label = ''
        for key, value in props.items():
            if isinstance(value, dict):
                label += '  ' * indent + f'OBJECT = {key}\n'
                label += self._build_odl(value, indent + 1)
                label += '  ' * indent + 'END_OBJECT\n'
            else:
                label += '  ' * indent + f'{key} = {value}\n'
        if indent == 0:
            label += 'END\n'
        return label

# Example usage:
# pds = PDSFile('example.pds')
# pds.read()
# pds.print_properties()
# pds.write('new.pds')
  1. Java Class for .PDS File Handling
    Here's a Java class that opens a .PDS file, decodes the ODL label, reads and prints all properties (using JSON for output), and supports writing a new .PDS file.
import java.io.*;
import java.util.*;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class PDSFile {
    private String filepath;
    private Map<String, Object> properties = new HashMap<>();
    private byte[] data = new byte[0];

    public PDSFile(String filepath) {
        this.filepath = filepath;
    }

    public void read() throws IOException {
        byte[] content = Files.readAllBytes(Paths.get(filepath));
        int labelEnd = new String(content).indexOf("END");
        if (labelEnd != -1) {
            String label = new String(content, 0, labelEnd + 3);
            data = Arrays.copyOfRange(content, labelEnd + 3, content.length);
            properties = parseODL(label);
        } else {
            throw new IOException("No 'END' found in label.");
        }
    }

    private Map<String, Object> parseODL(String label) {
        Map<String, Object> props = new LinkedHashMap<>();
        Stack<Map<String, Object>> stack = new Stack<>();
        stack.push(props);
        Map<String, Object> current = props;
        for (String line : label.split("\n")) {
            line = line.trim();
            if (line.isEmpty() || line.startsWith("/*")) continue;
            if (line.matches("OBJECT\\s*=\\s*.*")) {
                String objName = line.split("=")[1].trim();
                Map<String, Object> newObj = new LinkedHashMap<>();
                current.put(objName, newObj);
                stack.push(newObj);
                current = newObj;
            } else if (line.equals("END_OBJECT")) {
                stack.pop();
                current = stack.peek();
            } else if (line.contains("=") && !line.equals("END")) {
                String[] parts = line.split("=", 2);
                current.put(parts[0].trim(), parts[1].trim());
            } else if (line.equals("END")) {
                break;
            }
        }
        return props;
    }

    public void printProperties() {
        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        System.out.println(gson.toJson(properties));
    }

    public void write(String newFilepath, Map<String, Object> newProperties, byte[] newData) throws IOException {
        if (newProperties != null) properties = newProperties;
        if (newData != null) data = newData;
        String label = buildODL(properties, 0);
        try (FileOutputStream fos = new FileOutputStream(newFilepath)) {
            fos.write(label.getBytes("ASCII"));
            fos.write(data);
        }
    }

    private String buildODL(Map<String, Object> props, int indent) {
        StringBuilder label = new StringBuilder();
        for (Map.Entry<String, Object> entry : props.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            if (value instanceof Map) {
                label.append("  ".repeat(indent)).append("OBJECT = ").append(key).append("\n");
                label.append(buildODL((Map<String, Object>) value, indent + 1));
                label.append("  ".repeat(indent)).append("END_OBJECT\n");
            } else {
                label.append("  ".repeat(indent)).append(key).append(" = ").append(value).append("\n");
            }
        }
        if (indent == 0) label.append("END\n");
        return label.toString();
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     PDSFile pds = new PDSFile("example.pds");
    //     pds.read();
    //     pds.printProperties();
    //     pds.write("new.pds", null, null);
    // }
}
  1. JavaScript Class for .PDS File Handling
    Here's a JavaScript class (Node.js compatible) that opens a .PDS file, decodes the ODL label, reads and prints all properties, and supports writing a new .PDS file. Use fs module.
const fs = require('fs');

class PDSFile {
    constructor(filepath) {
        this.filepath = filepath;
        this.properties = {};
        this.data = Buffer.alloc(0);
    }

    read() {
        const content = fs.readFileSync(this.filepath);
        const labelEnd = content.indexOf('END');
        if (labelEnd !== -1) {
            const label = content.slice(0, labelEnd + 3).toString('ascii');
            this.data = content.slice(labelEnd + 3);
            this.properties = this._parseODL(label);
        } else {
            throw new Error("No 'END' found in label.");
        }
    }

    _parseODL(label) {
        const props = {};
        const stack = [props];
        let current = props;
        label.split(/\r?\n/).forEach(line => {
            line = line.trim();
            if (!line || line.startsWith('/*')) return;
            if (/OBJECT\s*=\s*/.test(line)) {
                const objName = line.split('=')[1].trim();
                current[objName] = {};
                stack.push(current[objName]);
                current = current[objName];
            } else if (line === 'END_OBJECT') {
                stack.pop();
                current = stack[stack.length - 1];
            } else if (line.includes('=') && line !== 'END') {
                const [key, value] = line.split('=').map(s => s.trim());
                current[key] = value;
            } else if (line === 'END') {
                return;
            }
        });
        return props;
    }

    printProperties() {
        console.log(JSON.stringify(this.properties, null, 2));
    }

    write(newFilepath, newProperties = null, newData = Buffer.alloc(0)) {
        if (newProperties) this.properties = newProperties;
        if (newData) this.data = newData;
        const label = this._buildODL(this.properties, 0);
        fs.writeFileSync(newFilepath, Buffer.concat([Buffer.from(label, 'ascii'), this.data]));
    }

    _buildODL(props, indent) {
        let label = '';
        for (const [key, value] of Object.entries(props)) {
            if (typeof value === 'object') {
                label += '  '.repeat(indent) + `OBJECT = ${key}\n`;
                label += this._buildODL(value, indent + 1);
                label += '  '.repeat(indent) + 'END_OBJECT\n';
            } else {
                label += '  '.repeat(indent) + `${key} = ${value}\n`;
            }
        }
        if (indent === 0) label += 'END\n';
        return label;
    }
}

// Example usage:
// const pds = new PDSFile('example.pds');
// pds.read();
// pds.printProperties();
// pds.write('new.pds');
  1. C Class (Using C++ for Class Support) for .PDS File Handling
    Here's a C++ class that opens a .PDS file, decodes the ODL label, reads and prints all properties (using simple recursion for nesting), and supports writing a new .PDS file. Uses std::map<std::string, std::variant<std::string, std::map<...>>> for properties (requires C++17).
#include <iostream>
#include <fstream>
#include <sstream>
#include <map>
#include <variant>
#include <vector>
#include <regex>
#include <string>

using PropertyMap = std::map<std::string, std::variant<std::string, std::shared_ptr<PropertyMap>>>;

class PDSFile {
private:
    std::string filepath;
    PropertyMap properties;
    std::vector<char> data;

public:
    PDSFile(const std::string& fp) : filepath(fp) {}

    void read() {
        std::ifstream file(filepath, std::ios::binary);
        if (!file) throw std::runtime_error("Failed to open file.");
        std::vector<char> content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        auto it = std::search(content.begin(), content.end(), {'E', 'N', 'D'});
        if (it != content.end()) {
            std::string label(content.begin(), it + 3);
            data.assign(it + 3, content.end());
            properties = parseODL(label);
        } else {
            throw std::runtime_error("No 'END' found in label.");
        }
    }

    PropertyMap parseODL(const std::string& label) {
        PropertyMap props;
        std::vector<PropertyMap*> stack = {&props};
        PropertyMap* current = &props;
        std::istringstream iss(label);
        std::string line;
        while (std::getline(iss, line)) {
            line = std::regex_replace(line, "^\\s+|\\s+$", "");
            if (line.empty() || line.rfind("/*", 0) == 0) continue;
            if (std::regex_match(line, std::regex("OBJECT\\s*=\\s*.*"))) {
                size_t pos = line.find('=');
                std::string objName = std::regex_replace(line.substr(pos + 1), "^\\s+|\\s+$", "");
                auto newObj = std::make_shared<PropertyMap>();
                (*current)[objName] = newObj;
                stack.push_back(newObj.get());
                current = newObj.get();
            } else if (line == "END_OBJECT") {
                stack.pop_back();
                current = stack.back();
            } else if (line.find('=') != std::string::npos && line != "END") {
                size_t pos = line.find('=');
                std::string key = std::regex_replace(line.substr(0, pos), "^\\s+|\\s+$", "");
                std::string value = std::regex_replace(line.substr(pos + 1), "^\\s+|\\s+$", "");
                (*current)[key] = value;
            } else if (line == "END") {
                break;
            }
        }
        return props;
    }

    void printProperties(const PropertyMap& props, int indent = 0) const {
        for (const auto& [key, value] : props) {
            std::cout << std::string(indent * 2, ' ') << key << ": ";
            if (std::holds_alternative<std::string>(value)) {
                std::cout << std::get<std::string>(value) << std::endl;
            } else {
                std::cout << "{\n";
                printProperties(*std::get<std::shared_ptr<PropertyMap>>(value), indent + 1);
                std::cout << std::string(indent * 2, ' ') << "}\n";
            }
        }
    }

    void printProperties() const {
        printProperties(properties);
    }

    void write(const std::string& newFilepath, const PropertyMap& newProps = {}, const std::vector<char>& newData = {}) {
        PropertyMap writeProps = newProps.empty() ? properties : newProps;
        std::vector<char> writeData = newData.empty() ? data : newData;
        std::ofstream file(newFilepath, std::ios::binary);
        if (!file) throw std::runtime_error("Failed to write file.");
        file << buildODL(writeProps, 0);
        file.write(writeData.data(), writeData.size());
    }

    std::string buildODL(const PropertyMap& props, int indent) const {
        std::ostringstream oss;
        for (const auto& [key, value] : props) {
            oss << std::string(indent * 2, ' ');
            if (std::holds_alternative<std::string>(value)) {
                oss << key << " = " << std::get<std::string>(value) << "\n";
            } else {
                oss << "OBJECT = " << key << "\n";
                oss << buildODL(*std::get<std::shared_ptr<PropertyMap>>(value), indent + 1);
                oss << std::string(indent * 2, ' ') << "END_OBJECT\n";
            }
        }
        if (indent == 0) oss << "END\n";
        return oss.str();
    }
};

// Example usage:
// int main() {
//     PDSFile pds("example.pds");
//     pds.read();
//     pds.printProperties();
//     pds.write("new.pds");
//     return 0;
// }