Task 165: .E57 File Format

Task 165: .E57 File Format

File Format Specifications for the .E57 File Format

The .E57 file format is a standardized, vendor-neutral format for exchanging 3D imaging data, defined by the ASTM E2807 standard. It supports the storage of point clouds, associated attributes such as color and intensity, and 2D images. The format combines a binary header, optional binary data sections for efficient storage of large datasets, and an XML section for metadata. The entire file is organized into fixed-size pages (typically 1024 bytes), each consisting of 1020 bytes of payload data followed by a 4-byte cyclic redundancy check (CRC) checksum for integrity verification. Binary data is encoded in little-endian byte order. The format is extensible and supports hierarchical data structures.

List of All Properties Intrinsic to the .E57 File Format

The intrinsic properties refer to the core structural elements defined in the file header, which are essential to the format's organization and integrity. These properties are as follows:

  • File Signature: An 8-byte character array identifying the file as an .E57 format (typically "ASTME57\0").
  • Major Version: A 4-byte unsigned integer representing the major version number of the format.
  • Minor Version: A 4-byte unsigned integer representing the minor version number of the format.
  • File Physical Length: An 8-byte unsigned integer indicating the total physical size of the file in bytes (must be a multiple of the page size).
  • XML Physical Offset: An 8-byte unsigned integer specifying the byte offset to the start of the XML section.
  • XML Logical Length: An 8-byte unsigned integer indicating the logical (unpaged) length of the XML section in bytes.
  • Page Size: An 8-byte unsigned integer defining the size of each page in the file (typically 1024 bytes).

These properties are stored in the 48-byte header at the beginning of the file.

Two Direct Download Links for .E57 Files

Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .E57 File Processing

The following is a self-contained HTML snippet with embedded JavaScript suitable for embedding in a Ghost blog post. It allows users to drag and drop an .E57 file, parses the header to extract the properties, and displays them on the screen.

Drag and drop .E57 file here

Python Class for Handling .E57 Files

The following Python class can open, decode, read, write, and print the properties of an .E57 file. For writing, it creates a minimal .E57 file with a basic XML section.

import struct
import os

class E57Handler:
    HEADER_FORMAT = '<8sIIQQQQ'  # Little-endian: char[8], uint32, uint32, uint64 x4
    HEADER_SIZE = 48
    PAGE_SIZE = 1024  # Default, can be overridden

    def __init__(self, filepath):
        self.filepath = filepath
        self.properties = {}

    def read(self):
        with open(self.filepath, 'rb') as f:
            header = f.read(self.HEADER_SIZE)
            if len(header) < self.HEADER_SIZE:
                raise ValueError("Invalid .E57 file: Header too short")
            unpacked = struct.unpack(self.HEADER_FORMAT, header)
            self.properties = {
                'File Signature': unpacked[0].decode('utf-8').rstrip('\x00'),
                'Major Version': unpacked[1],
                'Minor Version': unpacked[2],
                'File Physical Length': unpacked[3],
                'XML Physical Offset': unpacked[4],
                'XML Logical Length': unpacked[5],
                'Page Size': unpacked[6]
            }

    def print_properties(self):
        for key, value in self.properties.items():
            print(f"{key}: {value}")

    def write(self, output_path, xml_content=b'<e57Root></e57Root>'):
        xml_logical_len = len(xml_content)
        # Calculate paged XML length (simple padding for minimal example)
        num_pages = (xml_logical_len + 1019) // 1020  # 1020 data bytes per page
        xml_physical_len = num_pages * self.PAGE_SIZE
        file_physical_len = self.HEADER_SIZE + xml_physical_len  # Minimal, no binary data

        header = struct.pack(self.HEADER_FORMAT,
                             b'ASTME57\0',
                             1, 0,  # Major/minor version
                             file_physical_len,
                             self.HEADER_SIZE,  # XML offset after header
                             xml_logical_len,
                             self.PAGE_SIZE)

        with open(output_path, 'wb') as f:
            f.write(header)
            # Write paged XML (minimal, without full CRC calculation)
            offset = 0
            while offset < xml_logical_len:
                page_data = xml_content[offset:offset+1020]
                f.write(page_data.ljust(1020, b'\x00'))
                f.write(b'\x00\x00\x00\x00')  # Placeholder CRC
                offset += 1020
            # Pad to full file length if needed
            f.truncate(file_physical_len)

Java Class for Handling .E57 Files

The following Java class can open, decode, read, write, and print the properties of an .E57 file. It uses NIO for efficient binary handling. For writing, it creates a minimal .E57 file.

import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.nio.file.*;

public class E57Handler {
    private static final int HEADER_SIZE = 48;
    private static final long PAGE_SIZE = 1024L;
    private String filepath;
    private ByteBuffer propertiesBuffer;

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

    public void read() throws IOException {
        try (FileChannel channel = FileChannel.open(Paths.get(filepath), StandardOpenOption.READ)) {
            ByteBuffer buffer = ByteBuffer.allocate(HEADER_SIZE).order(ByteOrder.LITTLE_ENDIAN);
            channel.read(buffer);
            buffer.flip();
            propertiesBuffer = buffer.asReadOnlyBuffer();

            // Extract properties (example print in printProperties)
        }
    }

    public void printProperties() {
        if (propertiesBuffer == null) return;
        propertiesBuffer.rewind();
        byte[] sigBytes = new byte[8];
        propertiesBuffer.get(sigBytes);
        System.out.println("File Signature: " + new String(sigBytes).trim());
        System.out.println("Major Version: " + propertiesBuffer.getInt());
        System.out.println("Minor Version: " + propertiesBuffer.getInt());
        System.out.println("File Physical Length: " + propertiesBuffer.getLong());
        System.out.println("XML Physical Offset: " + propertiesBuffer.getLong());
        System.out.println("XML Logical Length: " + propertiesBuffer.getLong());
        System.out.println("Page Size: " + propertiesBuffer.getLong());
    }

    public void write(String outputPath, byte[] xmlContent) throws IOException {
        long xmlLogicalLen = xmlContent.length;
        long numPages = (xmlLogicalLen + 1019) / 1020;
        long xmlPhysicalLen = numPages * PAGE_SIZE;
        long filePhysicalLen = HEADER_SIZE + xmlPhysicalLen;

        ByteBuffer header = ByteBuffer.allocate(HEADER_SIZE).order(ByteOrder.LITTLE_ENDIAN);
        header.put("ASTME57\0".getBytes());
        header.putInt(1);  // Major
        header.putInt(0);  // Minor
        header.putLong(filePhysicalLen);
        header.putLong(HEADER_SIZE);
        header.putLong(xmlLogicalLen);
        header.putLong(PAGE_SIZE);
        header.flip();

        try (FileChannel channel = FileChannel.open(Paths.get(outputPath),
                StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
            channel.write(header);
            // Write paged XML (minimal, placeholder CRC)
            int offset = 0;
            while (offset < xmlLogicalLen) {
                int len = Math.min(1020, (int)(xmlLogicalLen - offset));
                ByteBuffer page = ByteBuffer.allocate(1024).order(ByteOrder.LITTLE_ENDIAN);
                page.put(xmlContent, offset, len);
                page.position(1020);
                page.putInt(0);  // Placeholder CRC
                page.flip();
                channel.write(page);
                offset += 1020;
            }
        }
    }
}

JavaScript Class for Handling .E57 Files

The following JavaScript class can open (via File object), decode, read, write (using Blob for download), and print the properties of an .E57 file to the console.

class E57Handler {
    constructor(file) {
        this.file = file;
        this.properties = {};
    }

    async read() {
        const buffer = await this.file.arrayBuffer();
        const view = new DataView(buffer);

        this.properties = {
            'File Signature': new TextDecoder().decode(new Uint8Array(buffer, 0, 8)).trim(),
            'Major Version': view.getUint32(8, true),
            'Minor Version': view.getUint32(12, true),
            'File Physical Length': Number(view.getBigUint64(16, true)),
            'XML Physical Offset': Number(view.getBigUint64(24, true)),
            'XML Logical Length': Number(view.getBigUint64(32, true)),
            'Page Size': Number(view.getBigUint64(40, true))
        };
    }

    printProperties() {
        console.log(this.properties);
    }

    async write(outputFilename, xmlContent = new Uint8Array('<e57Root></e57Root>'.split('').map(c => c.charCodeAt(0)))) {
        const xmlLogicalLen = xmlContent.byteLength;
        const numPages = Math.ceil(xmlLogicalLen / 1020);
        const xmlPhysicalLen = numPages * 1024;
        const filePhysicalLen = 48 + xmlPhysicalLen;

        const header = new ArrayBuffer(48);
        const headerView = new DataView(header);
        const sig = 'ASTME57\0'.split('').map(c => c.charCodeAt(0));
        sig.forEach((b, i) => headerView.setUint8(i, b));
        headerView.setUint32(8, 1, true);
        headerView.setUint32(12, 0, true);
        headerView.setBigUint64(16, BigInt(filePhysicalLen), true);
        headerView.setBigUint64(24, BigInt(48), true);
        headerView.setBigUint64(32, BigInt(xmlLogicalLen), true);
        headerView.setBigUint64(40, BigInt(1024), true);

        const fullBuffer = new Uint8Array(filePhysicalLen);
        fullBuffer.set(new Uint8Array(header), 0);

        let offset = 0;
        let pos = 48;
        while (offset < xmlLogicalLen) {
            const pageDataLen = Math.min(1020, xmlLogicalLen - offset);
            fullBuffer.set(xmlContent.subarray(offset, offset + pageDataLen), pos);
            pos += 1020;
            fullBuffer.set(new Uint8Array(4), pos);  // Placeholder CRC
            pos += 4;
            offset += 1020;
        }

        const blob = new Blob([fullBuffer], { type: 'application/octet-stream' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = outputFilename;
        a.click();
        URL.revokeObjectURL(url);
    }
}

C Struct and Functions for Handling .E57 Files

Since C does not use classes, the following implementation uses a struct with associated functions to open, decode, read, write, and print the properties of an .E57 file.

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char fileSignature[8];
    uint32_t majorVersion;
    uint32_t minorVersion;
    uint64_t filePhysicalLength;
    uint64_t xmlPhysicalOffset;
    uint64_t xmlLogicalLength;
    uint64_t pageSize;
} E57Properties;

void readE57(const char* filepath, E57Properties* props) {
    FILE* f = fopen(filepath, "rb");
    if (!f) {
        perror("Failed to open file");
        return;
    }
    fread(props, sizeof(E57Properties), 1, f);
    fclose(f);
}

void printProperties(const E57Properties* props) {
    printf("File Signature: %.8s\n", props->fileSignature);
    printf("Major Version: %u\n", props->majorVersion);
    printf("Minor Version: %u\n", props->minorVersion);
    printf("File Physical Length: %llu\n", (unsigned long long)props->filePhysicalLength);
    printf("XML Physical Offset: %llu\n", (unsigned long long)props->xmlPhysicalOffset);
    printf("XML Logical Length: %llu\n", (unsigned long long)props->xmlLogicalLength);
    printf("Page Size: %llu\n", (unsigned long long)props->pageSize);
}

void writeE57(const char* outputPath, const char* xmlContent) {
    size_t xmlLogicalLen = strlen(xmlContent);
    size_t numPages = (xmlLogicalLen + 1019) / 1020;
    uint64_t xmlPhysicalLen = numPages * 1024ULL;
    uint64_t filePhysicalLen = 48ULL + xmlPhysicalLen;

    E57Properties header = {0};
    memcpy(header.fileSignature, "ASTME57\0", 8);
    header.majorVersion = 1;
    header.minorVersion = 0;
    header.filePhysicalLength = filePhysicalLen;
    header.xmlPhysicalOffset = 48ULL;
    header.xmlLogicalLength = xmlLogicalLen;
    header.pageSize = 1024ULL;

    FILE* f = fopen(outputPath, "wb");
    if (!f) {
        perror("Failed to create file");
        return;
    }
    fwrite(&header, sizeof(E57Properties), 1, f);

    size_t offset = 0;
    while (offset < xmlLogicalLen) {
        size_t len = (xmlLogicalLen - offset < 1020) ? (xmlLogicalLen - offset) : 1020;
        fwrite(xmlContent + offset, 1, len, f);
        for (size_t i = len; i < 1020; ++i) fputc(0, f);
        uint32_t crc = 0;  // Placeholder CRC
        fwrite(&crc, sizeof(uint32_t), 1, f);
        offset += 1020;
    }
    fclose(f);
}