Skip to content

Instantly share code, notes, and snippets.

@jrudolph
Last active October 31, 2023 13:01
Show Gist options
  • Save jrudolph/de2105928a1f57d86bcf944f87061703 to your computer and use it in GitHub Desktop.
Save jrudolph/de2105928a1f57d86bcf944f87061703 to your computer and use it in GitHub Desktop.
Volume Cartographer PPM file format

Volume Cartographer PPM file format

PPM files map U/V coordinates from flattened surfaces back to the original 3D volume coordinates x/y/z and also provide a normal for every point.

The file has a small header and is otherwise a huge array of double values (in the common case, but see header).

Header

One key/value pair per line. Lines are separated by \n, key and value by ": ".

E.g.

width: 8882
height: 3476
dim: 6
ordered: true
type: double
version: 1
<>

Content

After reading "<>\n" the array begins immediately. In the common case, for each point in a flattened surface u/v, 6 little endian double values can be read sequentially, x/y/z, and n_x/n_y/n_z.

C-struct similar to this:

struct Mapping {
  double x, y, z
  double n_x, n_y, n_z;
};

It is possible to memory map the file after the header and access the data directly. E.g.

void *ptr = // mmap ...
Mapping *mapping = (Mapping*) ptr;

// get mapping for a certain point u/v
// int u, v;
Mappping *uvMapping = mapping[v * width + u];
int x = uvMapping->x;
// ...

E.g. to get the x-value for a certain u/v, you can find the offset like this:

int offset = 73; // in the example below, depends on actual header
// int u, v;
long offset_x_uv = offset + 48 * (v * width + u);
long offset_y_uv = offset + 48 * (v * width + u) + 8;
long offset_z_uv = offset + 48 * (v * width + u) + 16;
// ...

Example file (beginning)

00000000  77 69 64 74 68 3a 20 38  38 38 32 0a 68 65 69 67  |width: 8882.heig|
00000010  68 74 3a 20 33 34 37 36  0a 64 69 6d 3a 20 36 0a  |ht: 3476.dim: 6.|
00000020  6f 72 64 65 72 65 64 3a  20 74 72 75 65 0a 74 79  |ordered: true.ty|
00000030  70 65 3a 20 64 6f 75 62  6c 65 0a 76 65 72 73 69  |pe: double.versi|
00000040  6f 6e 3a 20 31 0a 3c 3e  0a 00 00 00 00 00 00 00  |on: 1.<>........|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000842e0  00 00 00 00 00 00 00 00  00 96 4c 4a 68 85 6c aa  |..........LJh.l.|
000842f0  40 76 6b ea d7 38 4f a7  40 5c 3e d1 b0 bc 2d c2  |@vk..8O.@\>...-.|
00084300  40 0f 49 73 a2 33 ca c0  3f 88 bc fe 2e 57 42 ef  |@.Is.3..?....WB.|
00084310  bf d5 e2 3f f8 de a1 c5  3f c0 0c 48 78 79 6e aa  |...?....?..Hxyn.|
00084320  40 12 90 60 f3 80 4f a7  40 84 0c 2f 86 c3 2d c2  |@..`..O.@../..-.|
00084330  40 73 71 fa 07 94 b0 c0  3f fe d9 37 7c a0 40 ef  |@sq.....?..7|.@.|
00084340  bf 00 29 dd f7 ff dc c5  3f e9 cc 45 88 6d 70 aa  |..).....?..E.mp.|
00084350  40 ad b4 d6 0e c9 4f a7  40 ac da 8c 5b ca 2d c2  |@.....O.@...[.-.|
00084360  40 c0 72 67 3e 19 8c c0  3f bb 60 81 9b 22 3e ef  |@.rg>...?.`..">.|
00084370  bf b9 22 73 91 13 31 c6  3f 12 8d 43 98 61 72 aa  |.."s..1.?..C.ar.|
00084380  40 48 d9 4c 2a 11 50 a7  40 d3 a8 ea 30 d1 2d c2  |@H.L*[email protected].|
00084390  40 76 bd 99 cd 02 54 c0  3f 43 f8 d5 73 2f 3a ef  |@v....T.?C..s/:.|
000843a0  bf 37 ea e6 4b 1b b2 c6  3f 3a 4d 41 a8 55 74 aa  |.7..K...?:MA.Ut.|
000843b0  40 e4 fd c2 45 59 50 a7  40 fb 76 48 06 d8 2d c2  |@[email protected].|
000843c0  40 f8 f0 e7 cc 68 e5 bf  3f 94 11 73 2d fe 32 ef  |@....h..?..s-.2.|
000843d0  bf 5d fd 80 8f 47 91 c7  3f 64 0d 3f b8 49 76 aa  |.]...G..?d.?.Iv.|
000843e0  40 80 22 39 61 a1 50 a7  40 22 45 a6 db de 2d c2  |@."9a.P.@"E...-.|
000843f0  40 3b 71 38 b3 d7 40 be  3f 9b fd d4 45 fd 21 ef  |@;q8..@.?...E.!.|

Random Notes

Data for neighbors is highly correlated, so the data could be serialized much more efficiently. If you assume quantizing to integer x/y/z is acceptable, neighboring mappings differ in most cases (e.g. > 97% in one example I tested) by 0, 1, or -1. Since there's usually a prevalent direction, one of 1 and -1 will be much more likely.

E.g. in this example this is the distribution of diffs of neighboring points in a ppm:

Total points 2068x4102: 8482936 uvs
dxs
     0: 7040446 83.00%
    -1: 1187540 14.00%
     1: 244761   2.89%
    -2:   1230   0.01%
     2:    558   0.01%
  3430:     57   0.00%
  3431:     49   0.00%
 -3173:     47   0.00%
 -3171:     47   0.00%
 -3172:     45   0.00%
 ...

If you calculate the entropy, you get a value of ~ 1.2 bits per pixel. That's only achievable with an optimal entropy coder, but even a simple(Huffman) scheme such as

dx Code Num bits
0 0 1
-1 10 2
1 110 3
rest 111 + 16 bit full value 19

will be able to reap most of the benefits (in the above example this leads to 1.22 bits per pixel, down from 64 bit double, a compression factor of ~50x). This compression will be lossy due to the quantization to integer values.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment