├── img ├── C1631738.png └── C1631738_squared.jpg ├── voyager_decomp.h ├── cdcomp.c ├── README.adoc ├── cdcomp.vcxproj └── voyager_decomp.c /img/C1631738.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Photosounder/Voyager-Image-Decoder/HEAD/img/C1631738.png -------------------------------------------------------------------------------- /img/C1631738_squared.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Photosounder/Voyager-Image-Decoder/HEAD/img/C1631738_squared.jpg -------------------------------------------------------------------------------- /voyager_decomp.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define RECORD_BYTES 836 4 | 5 | typedef struct leaf 6 | { 7 | struct leaf *right; 8 | short int dn; 9 | struct leaf *left; 10 | } voy_node_t; 11 | 12 | typedef struct 13 | { 14 | voy_node_t *tree; 15 | buffer_t inbuf; 16 | size_t inpos; 17 | } voy_image_t; 18 | 19 | extern uint8_t *voyager_decompress_buffer_to_array(buffer_t inbuf); 20 | -------------------------------------------------------------------------------- /cdcomp.c: -------------------------------------------------------------------------------- 1 | #include "voyager_decomp.h" 2 | 3 | int main(int argc, char **argv) 4 | { 5 | int i; 6 | char outpath[PATH_MAX*4]; 7 | 8 | fprintf_rl(stdout, "Voyager Image Decompression Program (modernised)\n"); 9 | 10 | if (argc == 1) 11 | { 12 | fprintf_rl(stderr, "Provide a list of files to convert as arguments, drag-and-drop works too. The output files will be given a .tif extension.\n"); 13 | return 0; 14 | } 15 | 16 | // Convert each file 17 | for (int ia=1; ia < argc; ia++) 18 | { 19 | fprintf_rl(stdout, "Converting %s", argv[ia]); 20 | 21 | // Decompress into array 22 | buffer_t inbuf = buf_load_raw_file(argv[ia]); 23 | uint8_t *data = voyager_decompress_buffer_to_array(inbuf); 24 | free_buf(&inbuf); 25 | 26 | // Convert to raster structure 27 | raster_t r = make_raster(NULL, xyi(800, 800), XYI0, IMAGE_USE_FRGB); 28 | for (i=0; i < 800*800; i++) 29 | r.f[i] = make_grey_f((double) data[i] * (1./255.)); 30 | 31 | // Save to TIFF file 32 | remove_extension_from_path(outpath, argv[ia]); 33 | sprintf(&outpath[strlen(outpath)], ".tif"); 34 | save_image_tiff(outpath, r.f, r.dim, 4, 1, 32); 35 | 36 | free_raster(&r); 37 | free(data); 38 | fprintf_rl(stdout, "\n"); 39 | } 40 | 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Voyager Image Decoder 2 | 3 | === What I changed 4 | NASA offers space probes Voyager 1 and 2's images in a compressed raw format, however https://pds-imaging.jpl.nasa.gov/data/vg1_vg2-j-iss-2-edr-v3.0/vg_0006/software/cdcomp.c[their decompression program] hasn't been updated since August 1989, so it doesn't come close to compiling. I modernised it, simplified it, fixed bugs, librarified it, made it re-entrant, made it output 32-bit float TIFF images and made it work on any number of files you drag-and-drop onto it. So you can select a whole folder worth of Voyager's .imq files, drop them on cdcomp.exe and it will convert them all into .tif files in the same folder. 5 | 6 | === How to use 7 | You can download https://pds-rings.seti.org/viewmaster/archives-volumes/VG_0xxx/[whole volumes of those .imq images] to decode. Either get the Windows binary from the https://github.com/Photosounder/Voyager-Image-Decoder/releases[releases] or compile the program yourself. The only dependency is https://github.com/Photosounder/rouziclib[rouziclib], just put the whole rouziclib folder in your include path. 8 | 9 | === As a C library 10 | I structured the files link:voyager_decomp.h[voyager_decomp.h] and link:voyager_decomp.c[voyager_decomp.c] so that they can be used as a library, so that we can call the code from another program (like a viewer) if we so desire. With one rather important caveat, that code relies on many of rouziclib's features. It's possible to remove the need for the whole of rouziclib by copying over only the needed features, but I for one am not going to do that. 11 | 12 | === Output 13 | :imagesdir: img 14 | The raw images are 800x800 with 8 bits per pixel. However those 8 bits are on a linear scale and not sRGB (gamma compressed) like 8 bit per channel images typically are. So to conserve the superior accuracy that these images have in the brights we need a higher precision, that's why I chose to convert to 32-bit floating point TIFF. Here's a photo of Jupiter in March 1979 (https://pds-imaging.jpl.nasa.gov/data/vg1_vg2-j-iss-2-edr-v3.0/vg_0006/jupiter/c1631xxx/c1631738.imq[c1631738.imq]) taken through a violet filter, as converted by this program: 15 | 16 | image::C1631738.png[c1631738.png,float="right",align="center"] 17 | 18 | == Does it look actually correct though? 19 | https://pds-imaging.jpl.nasa.gov/data/vg1_vg2-j-iss-2-edr-v3.0/vg_0006/document/volinfo.txt[NASA claims] that "Each picture element (pixel) in the two dimensional image array is represented as an 8-bit value, and the value--in the range 0 to 255--is proportional to the amount of light detected at that point", and I decoded the images accordingly. But is this really correct? From the way the pictures look overly bright, washed out, with an overly bright sky (which should be much closer to black), it's tempting to think that NASA's description is actually wrong. Applying an arbitrary gamma correction (a gamma of 0.5, like squaring every pixel value) seems to drive that point home: 20 | 21 | image::C1631738_squared.jpg[C1631738_squared.jpg,float="right",align="center"] 22 | 23 | If anyone knows anything about the topic I would like to hear about it (open an issue). In the meantime it's probably sensible to apply a gamma when processing the images. 24 | -------------------------------------------------------------------------------- /cdcomp.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | {82CC2990-1633-4461-B466-CE8BEAB95C81} 24 | Win32Proj 25 | cdcomp 26 | 10.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v142 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v142 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v142 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v142 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | true 78 | 79 | 80 | false 81 | 82 | 83 | false 84 | 85 | 86 | 87 | 88 | 89 | Level3 90 | Disabled 91 | true 92 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 93 | true 94 | 95 | 96 | Console 97 | true 98 | 99 | 100 | 101 | 102 | 103 | 104 | Level3 105 | Disabled 106 | true 107 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 108 | true 109 | 4996 110 | "E:\VC_libs\include" 111 | 112 | 113 | Console 114 | true 115 | 116 | 117 | 118 | 119 | 120 | 121 | Level3 122 | MaxSpeed 123 | true 124 | true 125 | true 126 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 127 | true 128 | 129 | 130 | Console 131 | true 132 | true 133 | true 134 | 135 | 136 | 137 | 138 | 139 | 140 | Level3 141 | MaxSpeed 142 | true 143 | true 144 | true 145 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 146 | true 147 | "E:\VC_libs\include" 148 | 4996 149 | 150 | 151 | Console 152 | true 153 | true 154 | true 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /voyager_decomp.c: -------------------------------------------------------------------------------- 1 | #include "voyager_decomp.h" 2 | #include 3 | 4 | _Thread_local alloc_list_t voy_alloc_list={0}; 5 | 6 | int voy_read_var(voy_image_t *s, char *ibuf) // read variable length records from input file 7 | { 8 | int length, lim_len; 9 | 10 | length = read_LE16(&s->inbuf.buf[s->inpos], &s->inpos); // read the length of the record 11 | 12 | lim_len = MINN(MINN(length, RECORD_BYTES), s->inbuf.len-s->inpos); 13 | memcpy(ibuf, &s->inbuf.buf[s->inpos], lim_len); // copy the record data 14 | ibuf[lim_len] = '\0'; 15 | s->inpos += length + (length & 1); // advance the reading position 16 | 17 | return lim_len; 18 | } 19 | 20 | void voy_skip_labels(voy_image_t *s) // skip label records 21 | { 22 | int length, i=0; 23 | char ibuf[2048]; 24 | 25 | do 26 | { 27 | length = voy_read_var(s, ibuf); // read each record 28 | 29 | //fprintf_rl(stdout, "Label #%d: %s\n", i, ibuf); 30 | i++; 31 | 32 | if (strcmp_len2(ibuf, "END")==0 && length == 3) // look for the END record 33 | break; 34 | } 35 | while (length > 0); 36 | } 37 | 38 | void voy_dcmprs(char *ibuf, char *obuf, int *nin, int *nout, voy_node_t *root) // Decompress a record 39 | { 40 | voy_node_t *ptr = root; // pointer to position in s->tree 41 | unsigned char test; // test byte for bit set 42 | unsigned char idn; // input compressed byte 43 | char odn; // last dn value decompressed 44 | char *ilim = ibuf + *nin; // end of compressed bytes 45 | char *olim = obuf + *nout; // end of output buffer 46 | 47 | /************************************************************************** 48 | Check for valid input values for nin, nout and make initial assignments. 49 | ***************************************************************************/ 50 | 51 | if (ilim > ibuf && olim > obuf) 52 | odn = *obuf++ = *ibuf++; 53 | else 54 | { 55 | fprintf_rl(stderr, "Invalid byte count in voy_dcmprs()\n"); 56 | return; 57 | } 58 | 59 | /************************************************************************** 60 | Decompress the input buffer. Assign the first byte to the working 61 | variable, idn. An arithmetic and (&) is performed using the variable 62 | 'test' that is bit shifted to the right. If the result is 0, then 63 | go to right else go to left. 64 | ***************************************************************************/ 65 | 66 | for (idn=(*ibuf) ; ibuf < ilim ; idn =(*++ibuf)) 67 | { 68 | for (test=0x80 ; test ; test >>= 1) 69 | { 70 | ptr = (test & idn) ? ptr->left : ptr->right; 71 | 72 | if (ptr->dn != -1) 73 | { 74 | if (obuf >= olim) return; 75 | odn -= ptr->dn + 256; 76 | *obuf++ = odn; 77 | ptr = root; 78 | } 79 | } 80 | } 81 | } 82 | 83 | voy_node_t *voy_new_node(short int value) 84 | { 85 | voy_node_t *temp; // Pointer to the memory block 86 | 87 | // Allocate the memory and intialize the fields. 88 | temp = malloc_list(sizeof(voy_node_t), &voy_alloc_list); 89 | 90 | temp->right = NULL; 91 | temp->dn = value; 92 | temp->left = NULL; 93 | 94 | return temp; 95 | } 96 | 97 | void voy_sort_freq(int *freq_list, voy_node_t **node_list, int num_freq) 98 | { 99 | // Local Variables 100 | int *i; // primary pointer into freq_list 101 | int *j; // secondary pointer into freq_list 102 | 103 | voy_node_t **k; // primary pointer to node_list 104 | voy_node_t **l; // secondary pointer into node_list 105 | 106 | int temp1; // temporary storage for freq_list 107 | voy_node_t *temp2; // temporary storage for node_list 108 | 109 | int cnt; // count of list elements 110 | 111 | /************************************************************************ 112 | Save the current element - starting with the second - in temporary 113 | storage. Compare with all elements in first part of list moving 114 | each up one element until the element is larger. Insert current 115 | element at this point in list. 116 | *************************************************************************/ 117 | 118 | if (num_freq <= 0) 119 | return; // If no elements or invalid, return 120 | 121 | for (i=freq_list, k=node_list, cnt=num_freq ; --cnt ; *j=temp1, *l=temp2) 122 | { 123 | temp1 = *(++i); 124 | temp2 = *(++k); 125 | 126 | for (j = i, l = k ; *(j-1) > temp1 ; ) 127 | { 128 | *j = *(j-1); 129 | *l = *(l-1); 130 | j--; 131 | l--; 132 | if ( j <= freq_list) 133 | break; 134 | } 135 | 136 | } 137 | } 138 | 139 | voy_node_t *voy_huff_tree(int *hist) 140 | { 141 | // Local variables used 142 | int freq_list[512]; // Histogram frequency list 143 | voy_node_t **node_list; // DN pointer array list 144 | 145 | int *fp; // Frequency list pointer 146 | voy_node_t **np; // Node list pointer 147 | 148 | int num_freq; // Number non-zero frequencies in histogram 149 | int sum; // Sum of all frequencies 150 | 151 | short int num_nodes; // Counter for DN initialization 152 | short int cnt; // Miscellaneous counter 153 | 154 | short int znull = -1; // Null node value 155 | 156 | voy_node_t *temp; // Temporary node pointer 157 | 158 | /*************************************************************************** 159 | Allocate the array of nodes from memory and initialize these with numbers 160 | corresponding with the frequency list. There are only 511 possible 161 | permutations of first difference histograms. There are 512 allocated 162 | here to adhere to the FORTRAN version. 163 | ****************************************************************************/ 164 | 165 | fp = freq_list; 166 | node_list = malloc_list(512*sizeof(temp), &voy_alloc_list); 167 | np = node_list; 168 | 169 | for (num_nodes=1, cnt=512 ; cnt-- ; num_nodes++) 170 | { 171 | /************************************************************************** 172 | The following code has been added to standardize the VAX byte order 173 | for the "int" type. This code is intended to make the routine 174 | as machine independant as possible. 175 | ***************************************************************************/ 176 | unsigned char *cp = (unsigned char *) hist++; 177 | unsigned int j=0; 178 | short int i; 179 | for (i=4 ; --i >= 0 ; j = (j << 8) | *(cp+i)); 180 | 181 | // Now make the assignment 182 | *fp++ = j; 183 | temp = voy_new_node(num_nodes); 184 | *np++ = temp; 185 | } 186 | 187 | (*--fp) = 0; // Ensure the last element is zeroed out. 188 | 189 | /*************************************************************************** 190 | Now, sort the frequency list and eliminate all frequencies of zero. 191 | ****************************************************************************/ 192 | 193 | num_freq = 512; 194 | voy_sort_freq(freq_list, node_list, num_freq); 195 | 196 | fp = freq_list; 197 | np = node_list; 198 | 199 | for (num_freq=512 ; (*fp) == 0 && (num_freq) ; fp++, np++, num_freq--); 200 | 201 | 202 | /*************************************************************************** 203 | Now create the s->tree. Note that if there is only one difference value, 204 | it is returned as the root. On each interation, a new node is created 205 | and the least frequently occurring difference is assigned to the right 206 | pointer and the next least frequency to the left pointer. The node 207 | assigned to the left pointer now becomes the combination of the two 208 | nodes and it's frequency is the sum of the two combining nodes. 209 | ****************************************************************************/ 210 | 211 | for (temp=(*np) ; (num_freq--) > 1 ; ) 212 | { 213 | temp = voy_new_node(znull); 214 | temp->right = (*np++); 215 | temp->left = (*np); 216 | *np = temp; 217 | *(fp+1) = *(fp+1) + *fp; 218 | *fp++ = 0; 219 | voy_sort_freq(fp, np, num_freq); 220 | } 221 | 222 | return temp; 223 | } 224 | 225 | uint8_t *voyager_decompress_buffer_to_array(buffer_t inbuf) 226 | { 227 | voy_image_t ss={0}, *s; 228 | int hist[RECORD_BYTES*4]; 229 | int out_bytes = RECORD_BYTES; 230 | char ibuf[2048]; 231 | uint8_t *data = calloc(800*800+RECORD_BYTES, sizeof(uint8_t)); 232 | 233 | s = &ss; 234 | s->inbuf = inbuf; 235 | 236 | // Skip labels 237 | voy_skip_labels(s); 238 | 239 | // Load image histogram 240 | voy_read_var(s, hist); 241 | voy_read_var(s, (char *)hist+RECORD_BYTES); 242 | 243 | // Load encoding histogram 244 | voy_read_var(s, hist); 245 | voy_read_var(s, (char *)hist+RECORD_BYTES); 246 | voy_read_var(s, (char *)hist+RECORD_BYTES*2); 247 | 248 | // Load engineering summary 249 | voy_read_var(s, ibuf); 250 | 251 | // Initialise the decompression 252 | s->tree = voy_huff_tree(hist); 253 | 254 | // Decompress the image 255 | int length, line=0; 256 | do 257 | { 258 | length = voy_read_var(s, ibuf); // read one record 259 | if (length <= 0) 260 | break; 261 | 262 | voy_dcmprs(ibuf, &data[line*800], &length, &out_bytes, s->tree); // decompress one record into one line of pixels 263 | line++; 264 | } 265 | while (line < 800); 266 | 267 | free_alloc_list(&voy_alloc_list); // free the tree 268 | 269 | return data; 270 | } 271 | --------------------------------------------------------------------------------