You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

306 lines
9.0 KiB

  1. /*
  2. * This file is part of Aptdec.
  3. * Copyright (c) 2004-2009 Thierry Leconte (F4DWV), Xerbo (xerbo@protonmail.com) 2019-2020
  4. *
  5. * Aptdec is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. *
  18. */
  19. #include <stdlib.h>
  20. #include <stdio.h>
  21. #include <string.h>
  22. #include <libgen.h>
  23. #include <math.h>
  24. #include <sndfile.h>
  25. #include <errno.h>
  26. #include <time.h>
  27. #include "libs/argparse.h"
  28. #include "offsets.h"
  29. // DSP
  30. extern int init_dsp(double F);
  31. extern int getpixelrow(float *pixelv, int nrow, int *zenith, int reset);
  32. #include "pngio.h"
  33. #include "image.h"
  34. #include "color.h"
  35. // Audio file
  36. static SNDFILE *audioFile;
  37. // Number of channels in audio file
  38. int channels = 1;
  39. // Function declarations
  40. static int initsnd(char *filename);
  41. int getsample(float *sample, int nb);
  42. static int processAudio(char *filename, options_t *opts);
  43. int main(int argc, const char **argv) {
  44. //fprintf(stderr, VERSION"\n");
  45. // Check if there are actually any input files
  46. /*if(argc == optind || argc == 1){
  47. fprintf(stderr, "No input files provided.\n");
  48. usage();
  49. }*/
  50. options_t opts = { "r", "", 19, "", ".", 0, "", "", 1.0, 0 };
  51. static const char *const usages[] = {
  52. "aptdec [options] [[--] sources]",
  53. "aptdec [sources]",
  54. NULL,
  55. };
  56. struct argparse_option options[] = {
  57. OPT_HELP(),
  58. OPT_GROUP("Image options"),
  59. OPT_STRING('i', "image", &opts.type, "set output image type (see the README for a list)", NULL, 0, 0),
  60. OPT_STRING('e', "effect", &opts.effects, "add an effect (see the README for a list)", NULL, 0, 0),
  61. OPT_FLOAT('g', "gamma", &opts.gamma, "gamma adjustment (1.0 = off)", NULL, 0, 0),
  62. OPT_GROUP("Satellite options"),
  63. OPT_INTEGER('s', "satellite", &opts.satnum, "satellite ID, must be between 15 and 19", NULL, 0, 0),
  64. OPT_GROUP("Paths"),
  65. OPT_STRING('p', "palette", &opts.palette, "path to a palette", NULL, 0, 0),
  66. OPT_STRING('m', "map", &opts.map, "path to a WXtoImg map", NULL, 0, 0),
  67. OPT_STRING('o', "filename", &opts.filename, "filename of the output image", NULL, 0, 0),
  68. OPT_STRING('d', "output", &opts.path, "output directory (must exist first)", NULL, 0, 0),
  69. OPT_GROUP("Misc"),
  70. OPT_BOOLEAN('r', "realtime", &opts.realtime, "decode in realtime", NULL, 0, 0),
  71. OPT_INTEGER('k', "map-offset", &opts.mapOffset, "Map offset (in px, default 0)", NULL, 0, 0),
  72. OPT_END(),
  73. };
  74. struct argparse argparse;
  75. argparse_init(&argparse, options, usages, 0);
  76. argparse_describe(&argparse, "\nA lightweight FOSS NOAA APT satellite imagery decoder.", "\nSee `README.md` for a full description of command line arguments and `LICENSE` for licensing conditions.");
  77. argc = argparse_parse(&argparse, argc, argv);
  78. if(argc == 0){
  79. argparse_usage(&argparse);
  80. }
  81. // Actually decode the files
  82. for (int i = 0; i < argc; i++) {
  83. // Convert from a `const char *` to a normal `char *`
  84. char *filename = NULL;
  85. memcpy(filename, argv[i], strlen(argv[i]));
  86. processAudio(filename, &opts);
  87. }
  88. return 0;
  89. }
  90. static int processAudio(char *filename, options_t *opts){
  91. // Image info struct
  92. image_t img;
  93. // Mapping between wedge value and channel ID
  94. static struct {
  95. char *id[7];
  96. char *name[7];
  97. } ch = {
  98. { "?", "1", "2", "3A", "4", "5", "3B" },
  99. { "unknown", "visble", "near-infrared", "mid-infrared", "thermal-infrared", "thermal-infrared", "mid-infrared" }
  100. };
  101. // Buffer for image channel
  102. char desc[60];
  103. // Parse file path
  104. char path[256], extension[32];
  105. strcpy(path, filename);
  106. strcpy(path, dirname(path));
  107. sscanf(basename(filename), "%255[^.].%31s", img.name, extension);
  108. if(opts->realtime){
  109. // Set output filename to current time when in realtime mode
  110. time_t t;
  111. time(&t);
  112. strncpy(img.name, ctime(&t), 24);
  113. // Init a row writer
  114. initWriter(opts, &img, IMG_WIDTH, MAX_HEIGHT, "Unprocessed realtime image", "r");
  115. }
  116. if(strcmp(extension, "png") == 0){
  117. // Read PNG into image buffer
  118. printf("Reading %s\n", filename);
  119. if(readRawImage(filename, img.prow, &img.nrow) == 0){
  120. exit(EPERM);
  121. }
  122. }else{
  123. // Attempt to open the audio file
  124. if (initsnd(filename) == 0)
  125. exit(EPERM);
  126. // Build image
  127. // TODO: multithreading, would require some sort of input buffer
  128. for (img.nrow = 0; img.nrow < MAX_HEIGHT; img.nrow++) {
  129. // Allocate memory for this row
  130. img.prow[img.nrow] = (float *) malloc(sizeof(float) * 2150);
  131. // Write into memory and break the loop when there are no more samples to read
  132. if (getpixelrow(img.prow[img.nrow], img.nrow, &img.zenith, (img.nrow == 0)) == 0)
  133. break;
  134. if(opts->realtime) pushRow(img.prow[img.nrow], IMG_WIDTH);
  135. fprintf(stderr, "Row: %d\r", img.nrow);
  136. fflush(stderr);
  137. }
  138. // Close stream
  139. sf_close(audioFile);
  140. }
  141. if(opts->realtime) closeWriter();
  142. printf("Total rows: %d\n", img.nrow);
  143. // Fallback for detecting the zenith
  144. // TODO: encode metadata in raw images
  145. if(opts->map != NULL && opts->map[0] != '\0' && img.zenith == 0){
  146. fprintf(stderr, "Guessing zenith in image, map will most likely be misaligned.\n");
  147. img.zenith = img.nrow / 2;
  148. }
  149. // Calibrate
  150. img.chA = calibrate(img.prow, img.nrow, CHA_OFFSET, CH_WIDTH);
  151. img.chB = calibrate(img.prow, img.nrow, CHB_OFFSET, CH_WIDTH);
  152. printf("Channel A: %s (%s)\n", ch.id[img.chA], ch.name[img.chA]);
  153. printf("Channel B: %s (%s)\n", ch.id[img.chB], ch.name[img.chB]);
  154. // Crop noise from start and end of image
  155. if(CONTAINS(opts->effects, Crop_Noise)){
  156. cropNoise(&img);
  157. }
  158. // Denoise
  159. if(CONTAINS(opts->effects, Denoise)){
  160. denoise(img.prow, img.nrow, CHA_OFFSET, CH_WIDTH);
  161. denoise(img.prow, img.nrow, CHB_OFFSET, CH_WIDTH);
  162. }
  163. // Flip, for northbound passes
  164. if(CONTAINS(opts->effects, Flip_Image)){
  165. flipImage(&img, CH_WIDTH, CHA_OFFSET);
  166. flipImage(&img, CH_WIDTH, CHB_OFFSET);
  167. }
  168. // Temperature
  169. if (CONTAINS(opts->type, Temperature) && img.chB >= 4) {
  170. // Create another buffer as to not modify the orignal
  171. image_t tmpimg = img;
  172. for(int i = 0; i < img.nrow; i++){
  173. tmpimg.prow[i] = (float *) malloc(sizeof(float) * 2150);
  174. memcpy(tmpimg.prow[i], img.prow[i], sizeof(float) * 2150);
  175. }
  176. // Perform temperature calibration
  177. temperature(opts, &tmpimg, CHB_OFFSET, CH_WIDTH);
  178. ImageOut(opts, &tmpimg, CHB_OFFSET, CH_WIDTH, "Temperature", Temperature, (char *)TempPalette);
  179. }
  180. // MCIR
  181. if (CONTAINS(opts->type, MCIR))
  182. ImageOut(opts, &img, CHA_OFFSET, CH_WIDTH, "MCIR", MCIR, NULL);
  183. // Linear equalise
  184. if(CONTAINS(opts->effects, Linear_Equalise)){
  185. linearEnhance(img.prow, img.nrow, CHA_OFFSET, CH_WIDTH);
  186. linearEnhance(img.prow, img.nrow, CHB_OFFSET, CH_WIDTH);
  187. }
  188. // Histogram equalise
  189. if(CONTAINS(opts->effects, Histogram_Equalise)){
  190. histogramEqualise(img.prow, img.nrow, CHA_OFFSET, CH_WIDTH);
  191. histogramEqualise(img.prow, img.nrow, CHB_OFFSET, CH_WIDTH);
  192. }
  193. // Raw image
  194. if (CONTAINS(opts->type, Raw_Image)) {
  195. sprintf(desc, "%s (%s) & %s (%s)", ch.id[img.chA], ch.name[img.chA], ch.id[img.chB], ch.name[img.chB]);
  196. ImageOut(opts, &img, 0, IMG_WIDTH, desc, Raw_Image, NULL);
  197. }
  198. // Palette image
  199. if (CONTAINS(opts->type, Palleted)) {
  200. img.palette = opts->palette;
  201. strcpy(desc, "Palette composite");
  202. ImageOut(opts, &img, CHA_OFFSET, 909, desc, Palleted, NULL);
  203. }
  204. // Channel A
  205. if (CONTAINS(opts->type, Channel_A)) {
  206. sprintf(desc, "%s (%s)", ch.id[img.chA], ch.name[img.chA]);
  207. ImageOut(opts, &img, CHA_OFFSET, CH_WIDTH, desc, Channel_A, NULL);
  208. }
  209. // Channel B
  210. if (CONTAINS(opts->type, Channel_B)) {
  211. sprintf(desc, "%s (%s)", ch.id[img.chB], ch.name[img.chB]);
  212. ImageOut(opts, &img, CHB_OFFSET, CH_WIDTH, desc, Channel_B, NULL);
  213. }
  214. return 1;
  215. }
  216. static int initsnd(char *filename) {
  217. SF_INFO infwav;
  218. int res;
  219. // Open audio file
  220. infwav.format = 0;
  221. audioFile = sf_open(filename, SFM_READ, &infwav);
  222. if (audioFile == NULL) {
  223. fprintf(stderr, "Could not open %s\n", filename);
  224. return 0;
  225. }
  226. res = init_dsp(infwav.samplerate);
  227. printf("Input file: %s\n", filename);
  228. if(res < 0) {
  229. fprintf(stderr, "Input sample rate too low: %d\n", infwav.samplerate);
  230. return 0;
  231. }else if(res > 0) {
  232. fprintf(stderr, "Input sample rate too high: %d\n", infwav.samplerate);
  233. return 0;
  234. }
  235. printf("Input sample rate: %d\n", infwav.samplerate);
  236. channels = infwav.channels;
  237. return 1;
  238. }
  239. // Read samples from the audio file
  240. int getsample(float *sample, int nb) {
  241. if(channels == 1){
  242. return sf_read_float(audioFile, sample, nb);
  243. }else{
  244. /* Multi channel audio is encoded such as:
  245. * Ch1,Ch2,Ch1,Ch2,Ch1,Ch2
  246. */
  247. float buf[nb * channels]; // Something like BLKIN*2 could also be used
  248. int samples = sf_read_float(audioFile, buf, nb * channels);
  249. for(int i = 0; i < nb; i++) sample[i] = buf[i * channels];
  250. return samples / channels;
  251. }
  252. }