I will tidy this up in the next few commitstags/v1.8.0
@@ -43,8 +43,9 @@ | |||||
# Program specifics | # Program specifics | ||||
*.png | *.png | ||||
!textlogo.png | !textlogo.png | ||||
!palettes/* | |||||
*.wav | *.wav | ||||
aptdec | aptdec | ||||
# VSCode | # VSCode | ||||
.vscode | |||||
.vscode |
@@ -1,7 +1,7 @@ | |||||
CC = gcc | CC = gcc | ||||
BIN = /usr/bin | BIN = /usr/bin | ||||
INCLUDES = -I. | 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 | OBJS = main.o image.o dsp.o filter.o reg.o pngio.o median.o color.o | ||||
aptdec: $(OBJS) | aptdec: $(OBJS) | ||||
@@ -15,7 +15,7 @@ For each audio file up to 6 images can be generated: | |||||
2. Calibrated channel A image | 2. Calibrated channel A image | ||||
3. Calibrated channel B image | 3. Calibrated channel B image | ||||
4. Temperature compensated IR image | 4. Temperature compensated IR image | ||||
5. False color image | |||||
5. Palleted image | |||||
6. MCIR (Map Color InfraRed) 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. | 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 | ## Options | ||||
``` | ``` | ||||
-i [r|a|b|c|t|m] | |||||
-i [r|a|b|t|m|p] | |||||
Output image type | 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" | Default: "ab" | ||||
-d <dir> | -d <dir> | ||||
@@ -88,18 +88,18 @@ Image names are `audiofile-x.png`, where `x` is: | |||||
- `r` for raw images | - `r` for raw images | ||||
- Sensor ID (`1`, `2`, `3A`, `3B`, `4`, `5`) for channel A|B 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 | - `t` for temperature calibrated images | ||||
- `m` for MCIR images | - `m` for MCIR images | ||||
Currently there are 6 available effects: | Currently there are 6 available effects: | ||||
- `t` for crop telemetry, off by default, only has effects on raw images | - `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 | - `d` for a median denoise filter | ||||
- `p` for a precipitation overlay | - `p` for a precipitation overlay | ||||
- `f` to flip the image (for southbound passes) | - `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 | ## 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. | 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 | ## Realtime decoding | ||||
As of recently a realtime output was added allowing realtime decoding of images. | 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 | 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 | ## Further reading | ||||
@@ -34,6 +34,7 @@ typedef struct { | |||||
int nrow; // Number of rows | int nrow; // Number of rows | ||||
int chA, chB; // ID of each channel | int chA, chB; // ID of each channel | ||||
char name[256]; // Stripped filename | char name[256]; // Stripped filename | ||||
char *palette; // Filename of palette | |||||
} image_t; | } image_t; | ||||
typedef struct { | typedef struct { | ||||
char *type; // Output image type | char *type; // Output image type | ||||
@@ -43,4 +44,5 @@ typedef struct { | |||||
char *path; // Output directory | char *path; // Output directory | ||||
int realtime; // Realtime decoding | int realtime; // Realtime decoding | ||||
char *filename; // Output filename | char *filename; // Output filename | ||||
char *palette; // Filename of palette | |||||
} options_t; | } options_t; |
@@ -248,7 +248,7 @@ int getpixelrow(float *pixelv, int nrow, int *zenith, int reset) { | |||||
// The point in which the pixel offset is recalculated | // The point in which the pixel offset is recalculated | ||||
if (corr < 0.75 * max) { | if (corr < 0.75 * max) { | ||||
//synced = 0; | |||||
synced = 0; | |||||
FreqLine = 1.0; | FreqLine = 1.0; | ||||
} | } | ||||
max = corr; | max = corr; | ||||
@@ -80,7 +80,7 @@ int main(int argc, char **argv) { | |||||
// Parse arguments | // Parse arguments | ||||
int opt; | 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) { | switch (opt) { | ||||
case 'd': | case 'd': | ||||
opts.path = optarg; | opts.path = optarg; | ||||
@@ -107,6 +107,9 @@ int main(int argc, char **argv) { | |||||
case 'o': | case 'o': | ||||
opts.filename = optarg; | opts.filename = optarg; | ||||
break; | break; | ||||
case 'p': | |||||
opts.palette = optarg; | |||||
break; | |||||
default: | default: | ||||
usage(); | usage(); | ||||
} | } | ||||
@@ -235,21 +238,19 @@ static int processAudio(char *filename, options_t *opts){ | |||||
histogramEqualise(img.prow, img.nrow, CHB_OFFSET, CH_WIDTH); | 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 | // Raw image | ||||
if (CONTAINS(opts->type, 'r')) { | 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]); | 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); | 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 | // Channel A | ||||
if (CONTAINS(opts->type, 'a')) { | if (CONTAINS(opts->type, 'a')) { | ||||
sprintf(desc, "%s (%s)", ch.id[img.chA], ch.name[img.chA]); | 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" | "Aptdec [options] audio files ...\n" | ||||
"Options:\n" | "Options:\n" | ||||
" -e [t|h|d|p|f|l] Effects\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 <dir> Image destination directory.\n" | " -d <dir> Image destination directory.\n" | ||||
" -o <name> Output filename\n" | " -o <name> Output filename\n" | ||||
" -s [15-19] Satellite number\n" | " -s [15-19] Satellite number\n" | ||||
" -m <file> Map file\n" | " -m <file> Map file\n" | ||||
" -r Realtime decode\n" | " -r Realtime decode\n" | ||||
" -p Path to palette\n" | |||||
"\nRefer to the README for more infomation\n"); | "\nRefer to the README for more infomation\n"); | ||||
exit(EINVAL); | exit(EINVAL); | ||||
@@ -193,6 +193,67 @@ int readRawImage(char *filename, float **prow, int *nrow) { | |||||
return 1; | 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 meta[] = { | ||||
{PNG_TEXT_COMPRESSION_NONE, "Software", VERSION}, | {PNG_TEXT_COMPRESSION_NONE, "Software", VERSION}, | ||||
{PNG_TEXT_COMPRESSION_NONE, "Channel", "Unknown", 7}, | {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 fc = (chid[0] == 'c'); | ||||
int greyscale = 0; | int greyscale = 0; | ||||
int skiptele = 0; | int skiptele = 0; | ||||
int imgpalette = (chid[0] == 'p'); | |||||
if(opts->effects != NULL && CONTAINS(opts->effects, 't')){ | if(opts->effects != NULL && CONTAINS(opts->effects, 't')){ | ||||
width -= TOTAL_TELE; | width -= TOTAL_TELE; | ||||
skiptele = 1; | skiptele = 1; | ||||
@@ -235,7 +297,7 @@ int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, c | |||||
return 0; | 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 = 1; | ||||
// Greyscale image | // 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); | printf("Writing %s", outName); | ||||
rgb_t *pal_row[256]; | |||||
if(imgpalette){ | |||||
if(!readPalette(img->palette, pal_row)){ | |||||
return 0; | |||||
} | |||||
} | |||||
// Build image | // Build image | ||||
for (int y = 0; y < img->nrow; y++) { | for (int y = 0; y < img->nrow; y++) { | ||||
png_color pix[width]; // Color | 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 + CHA_OFFSET], 0, 255), | ||||
CLIP(img->prow[y][x + CHB_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{ | }else{ | ||||
pix[x] = (png_color){ | pix[x] = (png_color){ | ||||
crow[y][x + skip + offset].r, | crow[y][x + skip + offset].r, | ||||
@@ -414,4 +491,4 @@ void closeWriter(){ | |||||
png_write_end(rt_png_ptr, rt_info_ptr); | png_write_end(rt_png_ptr, rt_info_ptr); | ||||
fclose(rt_pngfile); | fclose(rt_pngfile); | ||||
png_destroy_write_struct(&rt_png_ptr, &rt_info_ptr); | png_destroy_write_struct(&rt_png_ptr, &rt_info_ptr); | ||||
} | |||||
} |