diff --git a/.gitignore b/.gitignore index 4ccc436..a7c4171 100644 --- a/.gitignore +++ b/.gitignore @@ -43,8 +43,9 @@ # Program specifics *.png !textlogo.png +!palettes/* *.wav aptdec # VSCode -.vscode \ No newline at end of file +.vscode diff --git a/Makefile b/Makefile index b63097a..fa7f7d9 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CC = gcc BIN = /usr/bin INCLUDES = -I. -CFLAGS = -O3 -DNDEBUG -Wall -Wextra $(INCLUDES) +CFLAGS = -O3 -g -Wall -Wextra $(INCLUDES) OBJS = main.o image.o dsp.o filter.o reg.o pngio.o median.o color.o aptdec: $(OBJS) diff --git a/README.md b/README.md index 211092f..8d81412 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ For each audio file up to 6 images can be generated: 2. Calibrated channel A image 3. Calibrated channel B image 4. Temperature compensated IR image -5. False color image +5. Palleted image 6. MCIR (Map Color InfraRed) image The input audio file must be mono with a sample rate in the range of 4160-62400 Hz, lower samples rates will process faster. @@ -51,9 +51,9 @@ To uninstall ## Options ``` --i [r|a|b|c|t|m] +-i [r|a|b|t|m|p] Output image type -Raw (r), Channel A (a), Channel B (b), False Color (c), Temperature (t) or MCIR (m) +Raw (r), Channel A (a), Channel B (b), Palleted (p), Temperature (t) or MCIR (m) Default: "ab" -d @@ -88,18 +88,18 @@ Image names are `audiofile-x.png`, where `x` is: - `r` for raw images - Sensor ID (`1`, `2`, `3A`, `3B`, `4`, `5`) for channel A|B images - - `c` for false color + - `p` for a paletted image - `t` for temperature calibrated images - `m` for MCIR images Currently there are 6 available effects: - `t` for crop telemetry, off by default, only has effects on raw images - - `h` for histogram equalise, stretch the colors in the image to black and white + - `h` for histogram equalise - `d` for a median denoise filter - `p` for a precipitation overlay - `f` to flip the image (for southbound passes) - - `l` to linearly equalise the image (recommended for falsecolor images) + - `l` to linearly equalise the image, stretch the colors in the image to black and white ## Examples @@ -111,6 +111,10 @@ This will process all `.wav` files in the current directory, generate calibrated Decode `audio.wav` with denoise and histogram equalization and save it into the current directory. +`aptdec -e d -p palettes/N19-June-High-Vegetation.png -i p audio.wav` + +Create a false color image from the `N19-June-High-Vegetation.pn` palette. + ## Realtime decoding As of recently a realtime output was added allowing realtime decoding of images. @@ -121,7 +125,7 @@ aptdec /tmp/aptaudio sox -t pulseaudio alsa_output.pci-0000_00_1b.0.analog-stereo.monitor -c 1 -t wav /tmp/aptaudio ``` -Perform a realtime decode with the audio being played out of `alsa_output.pci-0000_00_1b.0.analog`. +Perform a realtime decode with the audio being played out of `alsa_output.pci-0000_00_1b.0.analog`. To stop the decode kill the `sox` process ## Further reading diff --git a/common.h b/common.h index 9e79c44..2a4a50f 100644 --- a/common.h +++ b/common.h @@ -34,6 +34,7 @@ typedef struct { int nrow; // Number of rows int chA, chB; // ID of each channel char name[256]; // Stripped filename + char *palette; // Filename of palette } image_t; typedef struct { char *type; // Output image type @@ -43,4 +44,5 @@ typedef struct { char *path; // Output directory int realtime; // Realtime decoding char *filename; // Output filename + char *palette; // Filename of palette } options_t; \ No newline at end of file diff --git a/dsp.c b/dsp.c index 06f1eae..503e160 100755 --- a/dsp.c +++ b/dsp.c @@ -248,7 +248,7 @@ int getpixelrow(float *pixelv, int nrow, int *zenith, int reset) { // The point in which the pixel offset is recalculated if (corr < 0.75 * max) { - //synced = 0; + synced = 0; FreqLine = 1.0; } max = corr; diff --git a/main.c b/main.c index dc2a7a1..4acfca4 100644 --- a/main.c +++ b/main.c @@ -80,7 +80,7 @@ int main(int argc, char **argv) { // Parse arguments int opt; - while ((opt = getopt(argc, argv, "o:m:d:i:s:e:r")) != EOF) { + while ((opt = getopt(argc, argv, "o:m:d:i:s:e:rp:")) != EOF) { switch (opt) { case 'd': opts.path = optarg; @@ -107,6 +107,9 @@ int main(int argc, char **argv) { case 'o': opts.filename = optarg; break; + case 'p': + opts.palette = optarg; + break; default: usage(); } @@ -235,21 +238,19 @@ static int processAudio(char *filename, options_t *opts){ histogramEqualise(img.prow, img.nrow, CHB_OFFSET, CH_WIDTH); } - // False color - if(CONTAINS(opts->type, 'c')){ - if(img.chA == 2 && img.chB >= 4){ - ImageOut(opts, &img, 0, CH_WIDTH, "False Color", "c", NULL); - }else{ - fprintf(stderr, "Lacking channels required for false color computation\n"); - } - } - // Raw image if (CONTAINS(opts->type, 'r')) { sprintf(desc, "%s (%s) & %s (%s)", ch.id[img.chA], ch.name[img.chA], ch.id[img.chB], ch.name[img.chB]); ImageOut(opts, &img, 0, IMG_WIDTH, desc, "r", NULL); } + // Palette image + if (CONTAINS(opts->type, 'p')) { + img.palette = opts->palette; + strcpy(desc, "Palette composite"); + ImageOut(opts, &img, 0, 909, desc, "p", NULL); + } + // Channel A if (CONTAINS(opts->type, 'a')) { sprintf(desc, "%s (%s)", ch.id[img.chA], ch.name[img.chA]); @@ -317,24 +318,25 @@ static void usage(void) { "Aptdec [options] audio files ...\n" "Options:\n" " -e [t|h|d|p|f|l] Effects\n" - " t: Crop telemetry\n" - " h: Histogram equalise\n" - " d: Denoise\n" - " p: Precipitation\n" - " f: Flip image\n" - " l: Linear equalise\n" - " -i [r|a|b|c|t|m] Output image\n" - " r: Raw\n" - " a: Channel A\n" - " b: Channel B\n" - " c: False color\n" - " t: Temperature\n" - " m: MCIR\n" + " t: Crop telemetry\n" + " h: Histogram equalise\n" + " d: Denoise\n" + " p: Precipitation\n" + " f: Flip image\n" + " l: Linear equalise\n" + " -i [r|a|b|c|t|m|p] Output image\n" + " r: Raw\n" + " a: Channel A\n" + " b: Channel B\n" + " t: Temperature\n" + " m: MCIR\n" + " p: Paletted image\n" " -d Image destination directory.\n" " -o Output filename\n" " -s [15-19] Satellite number\n" " -m Map file\n" " -r Realtime decode\n" + " -p Path to palette\n" "\nRefer to the README for more infomation\n"); exit(EINVAL); diff --git a/palettes/N19-June-High-Vegetation.png b/palettes/N19-June-High-Vegetation.png new file mode 100644 index 0000000..4f24cb4 Binary files /dev/null and b/palettes/N19-June-High-Vegetation.png differ diff --git a/pngio.c b/pngio.c index e1364e0..3f4b5c7 100644 --- a/pngio.c +++ b/pngio.c @@ -193,6 +193,67 @@ int readRawImage(char *filename, float **prow, int *nrow) { return 1; } +int readPalette(char *filename, rgb_t **crow) { + FILE *fp = fopen(filename, "r"); + if(!fp) { + fprintf(stderr, "Cannot open %s\n", filename); + return 0; + } + + // Create reader + png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if(!png) return 0; + png_infop info = png_create_info_struct(png); + if(!info) return 0; + png_init_io(png, fp); + + // Read info from header + png_read_info(png, info); + int width = png_get_image_width(png, info); + int height = png_get_image_height(png, info); + png_byte color_type = png_get_color_type(png, info); + png_byte bit_depth = png_get_bit_depth(png, info); + + // Check the image + if(width != 256 && height != 256){ + fprintf(stderr, "Palette must be 256x256.\n"); + return 0; + }else if(bit_depth != 8){ + fprintf(stderr, "Palette must be 8 bit color.\n"); + return 0; + }else if(color_type != PNG_COLOR_TYPE_RGBA){ + fprintf(stderr, "Palette must be RGB.\n"); + return 0; + } + + // Create row buffers + png_bytep *PNGrows = NULL; + PNGrows = (png_bytep *) malloc(sizeof(png_bytep) * height); + for(int y = 0; y < height; y++) + PNGrows[y] = (png_byte *) malloc(png_get_rowbytes(png, info)); + + // Read image + png_read_image(png, PNGrows); + + // Tidy up + fclose(fp); + png_destroy_read_struct(&png, &info, NULL); + + // Put into crow + for(int y = 0; y < height; y++) { + crow[y] = (rgb_t *) malloc(sizeof(rgb_t) * width); + + for(int x = 0; x < width; x++) + crow[y][x] = (rgb_t){ + PNGrows[y][x*4], + PNGrows[y][x*4 + 1], + PNGrows[y][x*4 + 2] + }; + } + + return 1; +} + png_text meta[] = { {PNG_TEXT_COMPRESSION_NONE, "Software", VERSION}, {PNG_TEXT_COMPRESSION_NONE, "Channel", "Unknown", 7}, @@ -216,6 +277,7 @@ int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, c int fc = (chid[0] == 'c'); int greyscale = 0; int skiptele = 0; + int imgpalette = (chid[0] == 'p'); if(opts->effects != NULL && CONTAINS(opts->effects, 't')){ width -= TOTAL_TELE; skiptele = 1; @@ -235,7 +297,7 @@ int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, c return 0; } - if(palette == NULL && !CONTAINS(opts->effects, 'p') && !fc && opts->map[0] == '\0' && chid[0] != 'm'){ + if(palette == NULL && !CONTAINS(opts->effects, 'p') && !fc && opts->map[0] == '\0' && chid[0] != 'm' && !imgpalette){ greyscale = 1; // Greyscale image @@ -300,6 +362,13 @@ int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, c printf("Writing %s", outName); + rgb_t *pal_row[256]; + if(imgpalette){ + if(!readPalette(img->palette, pal_row)){ + return 0; + } + } + // Build image for (int y = 0; y < img->nrow; y++) { png_color pix[width]; // Color @@ -326,6 +395,14 @@ int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, c CLIP(img->prow[y][x + CHA_OFFSET], 0, 255), CLIP(img->prow[y][x + CHB_OFFSET], 0, 255) }; + }else if(imgpalette){ + int cha = img->prow[y][x + CHA_OFFSET]; + int cbb = img->prow[y][x + CHB_OFFSET]; + pix[x] = (png_color){ + pal_row[cbb][cha].r, + pal_row[cbb][cha].g, + pal_row[cbb][cha].b + }; }else{ pix[x] = (png_color){ crow[y][x + skip + offset].r, @@ -414,4 +491,4 @@ void closeWriter(){ png_write_end(rt_png_ptr, rt_info_ptr); fclose(rt_pngfile); png_destroy_write_struct(&rt_png_ptr, &rt_info_ptr); -} \ No newline at end of file +}