Task 528: .PDM File Format

Task 528: .PDM File Format

File Format Specifications for .PDM (Portable Depth Map)

The .PDM file format is the Portable Depth Map (PDM) format, a simple hybrid text/binary image format designed for storing depth images (e.g., from depth cameras or LIDAR sensors). It is inspired by Netpbm formats and is optimized for portability, simplicity, and efficient storage of distance measurements in meters. The format supports multiple images in a single file, little-endian byte order for binary data, and optional comments. The only currently defined variant is PDM32, using 32-bit IEEE 754 single precision floating point numbers for depth values. Compression is optional and handled externally (e.g., gzip, xz).

List of all the properties of this file format intrinsic to its file system:

  • Magic number: 'PDM32' followed by a newline (\n).
  • Comments: Optional list of strings, each starting with '#' and ending with a newline; can include human-readable metadata like camera parameters.
  • Width: Unsigned 32-bit integer (range 0 to 2^32-1), formatted as ASCII decimal.
  • Height: Unsigned 32-bit integer (range 0 to 2^32-1), formatted as ASCII decimal.
  • Depth data: Array of 32-bit IEEE 754 single precision floating point numbers (little-endian), size width * height, representing distances in meters (row-major order).
  • Units: Fixed to meters.
  • Special values in depth data: 0, NaN, and negative infinity indicate invalid/missing data; positive infinity indicates measurements too far away.
  • Multi-image support: File can contain one or more concatenated images without delimiters or padding.
  • Endianness: Little-endian for binary data (no indicator).
  • Header type: ASCII text for magic, comments, and dimensions.
  • Data section type: Binary.
  • File extension: .pdm (append compression extension if used, e.g., .pdm.gz).
  • Precision: 6-7 decimal digits (single precision float).
  • Range: Supports depths from centimeters to hundreds of meters without significant loss.

Two direct download links for files of format .PDM:
Unfortunately, no direct download links for .pdm files in the Portable Depth Map format were found during the search. The format appears to be niche, and public samples are not readily available online.

Ghost blog embedded HTML JavaScript for drag n drop .PDM file to dump properties:

PDM File Viewer

Drag and Drop .PDM File to View Properties

Drop .PDM file here
  1. Python class for .PDM file:
import struct

class PdmFile:
    def __init__(self, filepath):
        self.filepath = filepath
        self.images = []  # List of dicts: {'magic': str, 'comments': list, 'width': int, 'height': int, 'depths': list}

    def read(self):
        with open(self.filepath, 'rb') as f:
            data = f.read()
        offset = 0
        while offset < len(data):
            image = {}

            # Read magic
            magic = b''
            while offset < len(data) and data[offset] != ord('\n'):
                magic += bytes([data[offset]])
                offset += 1
            offset += 1
            image['magic'] = magic.decode('ascii')
            if image['magic'] != 'PDM32':
                raise ValueError("Invalid magic number")

            # Read comments
            comments = []
            while offset < len(data) and data[offset] == ord('#'):
                comment = b''
                while offset < len(data) and data[offset] != ord('\n'):
                    comment += bytes([data[offset]])
                    offset += 1
                offset += 1
                comments.append(comment[1:].decode('ascii').strip())
            image['comments'] = comments

            # Read dimensions
            dim_str = b''
            while offset < len(data) and data[offset] != ord('\n'):
                dim_str += bytes([data[offset]])
                offset += 1
            offset += 1
            width_str, height_str = dim_str.decode('ascii').split(' ')
            image['width'] = int(width_str)
            image['height'] = int(height_str)

            # Read depths
            num_floats = image['width'] * image['height']
            depths_bytes = data[offset:offset + num_floats * 4]
            if len(depths_bytes) < num_floats * 4:
                raise ValueError("Incomplete depth data")
            image['depths'] = struct.unpack('<' + 'f' * num_floats, depths_bytes)
            offset += num_floats * 4

            self.images.append(image)

    def print_properties(self):
        for idx, image in enumerate(self.images, 1):
            print(f"Image {idx}:")
            print(f"  Magic number: {image['magic']}")
            print(f"  Comments: {', '.join(image['comments']) if image['comments'] else 'None'}")
            print(f"  Width: {image['width']}")
            print(f"  Height: {image['height']}")
            print(f"  Depth data: {image['depths']}")
            print()

    def write(self, images):
        with open(self.filepath, 'wb') as f:
            for image in images:
                f.write(b'PDM32\n')
                for comment in image.get('comments', []):
                    f.write(b'#' + comment.encode('ascii') + b'\n')
                f.write(f"{image['width']} {image['height']}\n".encode('ascii'))
                depths_bytes = struct.pack('<' + 'f' * len(image['depths']), *image['depths'])
                f.write(depths_bytes)

# Example usage:
# pdm = PdmFile('example.pdm')
# pdm.read()
# pdm.print_properties()
# To write: pdm.write([{'comments': [], 'width': 2, 'height': 2, 'depths': [1.0, 2.0, 3.0, 4.0]}])
  1. Java class for .PDM file:
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;

public class PdmFile {
    private String filepath;
    private List<Map<String, Object>> images = new ArrayList<>(); // Each map: "magic" String, "comments" List<String>, "width" Integer, "height" Integer, "depths" float[]

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

    public void read() throws IOException {
        try (FileInputStream fis = new FileInputStream(filepath);
             BufferedInputStream bis = new BufferedInputStream(fis)) {
            byte[] data = bis.readAllBytes();
            ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
            int offset = 0;

            while (offset < data.length) {
                Map<String, Object> image = new HashMap<>();

                // Read magic
                StringBuilder magicSb = new StringBuilder();
                while (offset < data.length && data[offset] != '\n') {
                    magicSb.append((char) data[offset]);
                    offset++;
                }
                offset++;
                String magic = magicSb.toString();
                if (!magic.equals("PDM32")) {
                    throw new IOException("Invalid magic number");
                }
                image.put("magic", magic);

                // Read comments
                List<String> comments = new ArrayList<>();
                while (offset < data.length && data[offset] == '#') {
                    StringBuilder commentSb = new StringBuilder();
                    while (offset < data.length && data[offset] != '\n') {
                        commentSb.append((char) data[offset]);
                        offset++;
                    }
                    offset++;
                    comments.add(commentSb.toString().substring(1).trim());
                }
                image.put("comments", comments);

                // Read dimensions
                StringBuilder dimSb = new StringBuilder();
                while (offset < data.length && data[offset] != '\n') {
                    dimSb.append((char) data[offset]);
                    offset++;
                }
                offset++;
                String[] dims = dimSb.toString().split(" ");
                int width = Integer.parseInt(dims[0]);
                int height = Integer.parseInt(dims[1]);
                image.put("width", width);
                image.put("height", height);

                // Read depths
                int numFloats = width * height;
                float[] depths = new float[numFloats];
                for (int i = 0; i < numFloats; i++) {
                    if (offset + 4 > data.length) {
                        throw new IOException("Incomplete depth data");
                    }
                    depths[i] = buffer.getFloat(offset);
                    offset += 4;
                }
                image.put("depths", depths);

                images.add(image);
            }
        }
    }

    public void printProperties() {
        for (int idx = 0; idx < images.size(); idx++) {
            Map<String, Object> image = images.get(idx);
            System.out.println("Image " + (idx + 1) + ":");
            System.out.println("  Magic number: " + image.get("magic"));
            System.out.println("  Comments: " + ( ((List<String>) image.get("comments")).isEmpty() ? "None" : String.join(", ", (List<String>) image.get("comments")) ));
            System.out.println("  Width: " + image.get("width"));
            System.out.println("  Height: " + image.get("height"));
            System.out.println("  Depth data: " + Arrays.toString((float[]) image.get("depths")));
            System.out.println();
        }
    }

    public void write(List<Map<String, Object>> images) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(filepath)) {
            for (Map<String, Object> image : images) {
                fos.write(("PDM32\n").getBytes("ASCII"));
                List<String> comments = (List<String>) image.getOrDefault("comments", new ArrayList<>());
                for (String comment : comments) {
                    fos.write(("#" + comment + "\n").getBytes("ASCII"));
                }
                int width = (int) image.get("width");
                int height = (int) image.get("height");
                fos.write((width + " " + height + "\n").getBytes("ASCII"));
                float[] depths = (float[]) image.get("depths");
                ByteBuffer buffer = ByteBuffer.allocate(depths.length * 4).order(ByteOrder.LITTLE_ENDIAN);
                for (float d : depths) {
                    buffer.putFloat(d);
                }
                fos.write(buffer.array());
            }
        }
    }

    // Example usage:
    // PdmFile pdm = new PdmFile("example.pdm");
    // pdm.read();
    // pdm.printProperties();
    // List<Map<String, Object>> newImages = new ArrayList<>();
    // Map<String, Object> img = new HashMap<>();
    // img.put("comments", Arrays.asList("Test"));
    // img.put("width", 2);
    // img.put("height", 2);
    // img.put("depths", new float[]{1.0f, 2.0f, 3.0f, 4.0f});
    // newImages.add(img);
    // pdm.write(newImages);
}
  1. JavaScript class for .PDM file:
class PdmFile {
  constructor(filepath) {
    this.filepath = filepath;
    this.images = []; // Array of objects: {magic: string, comments: array, width: number, height: number, depths: Float32Array}
  }

  // Read requires Node.js fs module
  async read() {
    const fs = require('fs');
    const data = fs.readFileSync(this.filepath);
    const view = new DataView(data.buffer);
    let offset = 0;
    while (offset < data.length) {
      const image = {};

      // Read magic
      let magic = '';
      while (offset < data.length && String.fromCharCode(data[offset]) !== '\n') {
        magic += String.fromCharCode(data[offset]);
        offset++;
      }
      offset++;
      if (magic !== 'PDM32') {
        throw new Error('Invalid magic number');
      }
      image.magic = magic;

      // Read comments
      image.comments = [];
      while (offset < data.length && String.fromCharCode(data[offset]) === '#') {
        let comment = '';
        while (offset < data.length && String.fromCharCode(data[offset]) !== '\n') {
          comment += String.fromCharCode(data[offset]);
          offset++;
        }
        offset++;
        image.comments.push(comment.substring(1).trim());
      }

      // Read dimensions
      let dimStr = '';
      while (offset < data.length && String.fromCharCode(data[offset]) !== '\n') {
        dimStr += String.fromCharCode(data[offset]);
        offset++;
      }
      offset++;
      const [widthStr, heightStr] = dimStr.split(' ');
      image.width = parseInt(widthStr, 10);
      image.height = parseInt(heightStr, 10);

      // Read depths
      const numFloats = image.width * image.height;
      image.depths = new Float32Array(numFloats);
      for (let i = 0; i < numFloats; i++) {
        if (offset + 4 > data.length) {
          throw new Error('Incomplete depth data');
        }
        image.depths[i] = view.getFloat32(offset, true); // little-endian
        offset += 4;
      }

      this.images.push(image);
    }
  }

  printProperties() {
    this.images.forEach((image, idx) => {
      console.log(`Image ${idx + 1}:`);
      console.log(`  Magic number: ${image.magic}`);
      console.log(`  Comments: ${image.comments.length ? image.comments.join(', ') : 'None'}`);
      console.log(`  Width: ${image.width}`);
      console.log(`  Height: ${image.height}`);
      console.log(`  Depth data: ${Array.from(image.depths).join(', ')}`);
      console.log('');
    });
  }

  write(images) {
    const fs = require('fs');
    let buffer = Buffer.alloc(0);
    images.forEach(image => {
      buffer = Buffer.concat([buffer, Buffer.from('PDM32\n')]);
      image.comments.forEach(comment => {
        buffer = Buffer.concat([buffer, Buffer.from('#' + comment + '\n')]);
      });
      buffer = Buffer.concat([buffer, Buffer.from(`${image.width} ${image.height}\n`)]);
      const depthsBuffer = new Buffer.alloc(image.depths.length * 4);
      const view = new DataView(depthsBuffer.buffer);
      image.depths.forEach((d, i) => view.setFloat32(i * 4, d, true));
      buffer = Buffer.concat([buffer, depthsBuffer]);
    });
    fs.writeFileSync(this.filepath, buffer);
  }
}

// Example usage (Node.js):
// const pdm = new PdmFile('example.pdm');
// await pdm.read();
// pdm.printProperties();
// const newImages = [{comments: [], width: 2, height: 2, depths: new Float32Array([1.0, 2.0, 3.0, 4.0])}];
// pdm.write(newImages);
  1. C class for .PDM file:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

typedef struct {
    char *magic;
    char **comments;
    size_t num_comments;
    uint32_t width;
    uint32_t height;
    float *depths;
} PdmImage;

typedef struct {
    char *filepath;
    PdmImage *images;
    size_t num_images;
} PdmFile;

PdmFile* pdm_create(const char *filepath) {
    PdmFile *pdm = malloc(sizeof(PdmFile));
    pdm->filepath = strdup(filepath);
    pdm->images = NULL;
    pdm->num_images = 0;
    return pdm;
}

void pdm_destroy(PdmFile *pdm) {
    for (size_t i = 0; i < pdm->num_images; i++) {
        free(pdm->images[i].magic);
        for (size_t j = 0; j < pdm->images[i].num_comments; j++) {
            free(pdm->images[i].comments[j]);
        }
        free(pdm->images[i].comments);
        free(pdm->images[i].depths);
    }
    free(pdm->images);
    free(pdm->filepath);
    free(pdm);
}

int pdm_read(PdmFile *pdm) {
    FILE *f = fopen(pdm->filepath, "rb");
    if (!f) return -1;
    fseek(f, 0, SEEK_END);
    long file_size = ftell(f);
    fseek(f, 0, SEEK_SET);
    char *data = malloc(file_size);
    fread(data, 1, file_size, f);
    fclose(f);

    size_t offset = 0;
    while (offset < (size_t) file_size) {
        pdm->images = realloc(pdm->images, (pdm->num_images + 1) * sizeof(PdmImage));
        PdmImage *image = &pdm->images[pdm->num_images];
        image->comments = NULL;
        image->num_comments = 0;
        image->depths = NULL;

        // Read magic
        char *magic_start = data + offset;
        char *magic_end = strchr(magic_start, '\n');
        if (!magic_end) {
            free(data);
            return -1;
        }
        size_t magic_len = magic_end - magic_start;
        image->magic = malloc(magic_len + 1);
        strncpy(image->magic, magic_start, magic_len);
        image->magic[magic_len] = '\0';
        offset += magic_len + 1;
        if (strcmp(image->magic, "PDM32") != 0) {
            free(data);
            return -1;
        }

        // Read comments
        while (offset < (size_t) file_size && data[offset] == '#') {
            char *comment_start = data + offset + 1;
            char *comment_end = strchr(comment_start, '\n');
            if (!comment_end) {
                free(data);
                return -1;
            }
            size_t comment_len = comment_end - comment_start;
            char *comment = malloc(comment_len + 1);
            strncpy(comment, comment_start, comment_len);
            comment[comment_len] = '\0';
            image->comments = realloc(image->comments, (image->num_comments + 1) * sizeof(char*));
            image->comments[image->num_comments] = comment;
            image->num_comments++;
            offset += comment_len + 2; // # and \n
        }

        // Read dimensions
        char *dim_start = data + offset;
        char *dim_end = strchr(dim_start, '\n');
        if (!dim_end) {
            free(data);
            return -1;
        }
        char dim_str[256];
        strncpy(dim_str, dim_start, dim_end - dim_start);
        dim_str[dim_end - dim_start] = '\0';
        sscanf(dim_str, "%u %u", &image->width, &image->height);
        offset += (dim_end - dim_start) + 1;

        // Read depths
        size_t num_floats = (size_t)image->width * image->height;
        size_t data_size = num_floats * sizeof(float);
        if (offset + data_size > (size_t) file_size) {
            free(data);
            return -1;
        }
        image->depths = malloc(data_size);
        memcpy(image->depths, data + offset, data_size);
        offset += data_size;

        pdm->num_images++;
    }
    free(data);
    return 0;
}

void pdm_print_properties(PdmFile *pdm) {
    for (size_t i = 0; i < pdm->num_images; i++) {
        PdmImage *image = &pdm->images[i];
        printf("Image %zu:\n", i + 1);
        printf("  Magic number: %s\n", image->magic);
        printf("  Comments: ");
        if (image->num_comments == 0) {
            printf("None\n");
        } else {
            for (size_t j = 0; j < image->num_comments; j++) {
                printf("%s%s", image->comments[j], j < image->num_comments - 1 ? ", " : "\n");
            }
        }
        printf("  Width: %u\n", image->width);
        printf("  Height: %u\n", image->height);
        printf("  Depth data: ");
        for (size_t j = 0; j < (size_t)image->width * image->height; j++) {
            printf("%f%s", image->depths[j], j < (size_t)image->width * image->height - 1 ? ", " : "\n");
        }
        printf("\n");
    }
}

int pdm_write(PdmFile *pdm, PdmImage *images, size_t num_images) {
    FILE *f = fopen(pdm->filepath, "wb");
    if (!f) return -1;

    for (size_t i = 0; i < num_images; i++) {
        PdmImage *image = &images[i];
        fprintf(f, "PDM32\n");
        for (size_t j = 0; j < image->num_comments; j++) {
            fprintf(f, "#%s\n", image->comments[j]);
        }
        fprintf(f, "%u %u\n", image->width, image->height);
        fwrite(image->depths, sizeof(float), (size_t)image->width * image->height, f);
    }

    fclose(f);
    return 0;
}

// Example usage:
 // PdmFile *pdm = pdm_create("example.pdm");
 // pdm_read(pdm);
 // pdm_print_properties(pdm);
 // pdm_destroy(pdm);