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.

21 jaren geleden
21 jaren geleden
21 jaren geleden
21 jaren geleden
19 jaren geleden
21 jaren geleden
19 jaren geleden
21 jaren geleden
21 jaren geleden
19 jaren geleden
21 jaren geleden
21 jaren geleden
21 jaren geleden
21 jaren geleden
21 jaren geleden
21 jaren geleden
19 jaren geleden
21 jaren geleden
21 jaren geleden
21 jaren geleden
21 jaren geleden
21 jaren geleden
21 jaren geleden
21 jaren geleden
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. /*
  2. * This file is part of Aptdec.
  3. * Copyright (c) 2004-2009 Thierry Leconte (F4DWV), Xerbo (xerbo@protonmail.com) 2019
  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 <getopt.h>
  23. #include <libgen.h>
  24. #include <math.h>
  25. #include <sndfile.h>
  26. #include <png.h>
  27. #include "messages.h"
  28. #include "offsets.h"
  29. #include "palette.h"
  30. extern int getpixelrow(float *pixelv, int nrow, int *zenith);
  31. extern int init_dsp(double F);
  32. static SNDFILE *inwav;
  33. static int initsnd(char *filename) {
  34. SF_INFO infwav;
  35. int res;
  36. // Open audio file
  37. infwav.format = 0;
  38. inwav = sf_open(filename, SFM_READ, &infwav);
  39. if (inwav == NULL) {
  40. fprintf(stderr, ERR_FILE_READ, filename);
  41. return(0);
  42. }
  43. res = init_dsp(infwav.samplerate);
  44. printf("Input file: %s\n", filename);
  45. if(res < 0) {
  46. fprintf(stderr, "Input sample rate too low: %d\n", infwav.samplerate);
  47. return(0);
  48. }else if(res > 0) {
  49. fprintf(stderr, "Input sample rate too high: %d\n", infwav.samplerate);
  50. return(0);
  51. }
  52. printf("Input sample rate: %d\n", infwav.samplerate);
  53. if (infwav.channels != 1) {
  54. fprintf(stderr, "Too many channels in input file: %d\n", infwav.channels);
  55. return(0);
  56. }
  57. return(1);
  58. }
  59. // Get samples from the wave file
  60. int getsample(float *sample, int nb) {
  61. return sf_read_float(inwav, sample, nb);
  62. }
  63. static png_text text_ptr[] = {
  64. {PNG_TEXT_COMPRESSION_NONE, "Software", VERSION},
  65. {PNG_TEXT_COMPRESSION_NONE, "Channel", NULL, 0},
  66. {PNG_TEXT_COMPRESSION_NONE, "Description", "NOAA satellite image", 20}
  67. };
  68. int mapOverlay(char *filename, float **crow, int nrow, int zenith, int MCIR) {
  69. FILE *fp = fopen(filename, "rb");
  70. // Create reader
  71. png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  72. if(!png) return 0;
  73. png_infop info = png_create_info_struct(png);
  74. if(!info) return 0;
  75. png_init_io(png, fp);
  76. // Read info from header
  77. png_read_info(png, info);
  78. int width = png_get_image_width(png, info);
  79. int height = png_get_image_height(png, info);
  80. png_byte color_type = png_get_color_type(png, info);
  81. png_byte bit_depth = png_get_bit_depth(png, info);
  82. // Check the image
  83. if(width != 1040){
  84. fprintf(stderr, "Map must be 1040px wide.\n");
  85. return 0;
  86. }else if(bit_depth != 16){
  87. fprintf(stderr, "Map must be 16 bit color.\n");
  88. return 0;
  89. }else if(color_type != PNG_COLOR_TYPE_RGB){
  90. fprintf(stderr, "Map must be RGB.\n");
  91. return 0;
  92. }else if(zenith > height/2 || nrow-zenith > height/2){
  93. fprintf(stderr, "WARNING: Map is too short to cover entire image\n");
  94. }
  95. // Create row buffers
  96. png_bytep *mapRows = NULL;
  97. mapRows = (png_bytep *) malloc(sizeof(png_bytep) * height);
  98. for(int y = 0; y < height; y++) mapRows[y] = (png_byte *) malloc(png_get_rowbytes(png, info));
  99. // Read image
  100. png_read_image(png, mapRows);
  101. // Tidy up
  102. fclose(fp);
  103. png_destroy_read_struct(&png, &info, NULL);
  104. int mapOffset = (height/2)-zenith;
  105. for(int y = 0; y < nrow; y++) {
  106. for(int x = 49; x < width - 82; x++){
  107. // Maps are 16 bit / channel
  108. png_bytep px = &mapRows[CLIP(y + mapOffset, 0, height)][x * 6];
  109. uint16_t r = (uint16_t)(px[0] << 8) | px[1];
  110. uint16_t g = (uint16_t)(px[2] << 8) | px[3];
  111. uint16_t b = (uint16_t)(px[4] << 8) | px[5];
  112. // Pointers to the current pixel in each channel
  113. float *cha = &crow[y][(x+36) * 3];
  114. float *chb = &crow[y][(x+CHB_OFFSET-49) * 3];
  115. // Fill in map
  116. if(MCIR){
  117. // Sea
  118. cha[0] = 42; cha[1] = 53; cha[2] = 105;
  119. // Land
  120. if(g > 128){
  121. float vegetation = (r-160) / 96.0;
  122. cha[0] = 120; cha[1] = vegetation*60.0 + 100; cha[2] = 95;
  123. }
  124. }
  125. // Color -> alpha: composite
  126. int composite = r + g + b;
  127. // Color -> alpha: flattern color depth
  128. float factor = (255 * 255 * 2.0) / composite;
  129. r *= factor; g *= factor; b *= factor;
  130. // Color -> alpha: convert black to alpha
  131. float alpha = CLIP(abs(0 - composite) / 65535.0, 0, 1);
  132. // Map overlay on channel A
  133. cha[0] = MCOMPOSITE(r/257, alpha, cha[0], 1);
  134. cha[1] = MCOMPOSITE(g/257, alpha, cha[1], 1);
  135. cha[2] = MCOMPOSITE(b/257, alpha, cha[2], 1);
  136. // Map overlay on channel B
  137. if(!MCIR){
  138. chb[0] = MCOMPOSITE(r/257, alpha, chb[0], 1);
  139. chb[1] = MCOMPOSITE(g/257, alpha, chb[1], 1);
  140. chb[2] = MCOMPOSITE(b/257, alpha, chb[2], 1);
  141. }
  142. // Cloud overlay on channel A
  143. if(MCIR){
  144. float cloud = (chb[0] - 110) / 120;
  145. cha[0] = MCOMPOSITE(240, cloud, cha[0], 1);
  146. cha[1] = MCOMPOSITE(250, cloud, cha[1], 1);
  147. cha[2] = MCOMPOSITE(255, cloud, cha[2], 1);
  148. }
  149. }
  150. }
  151. return 1;
  152. }
  153. // Row where to satellite reaches peak elevation
  154. int zenith = 0;
  155. static int ImageOut(char *filename, char *chid, float **prow, int nrow, int width, int offset, char *palette, char *effects, char *mapFile) {
  156. FILE *pngfile;
  157. // Reduce the width of the image to componsate for the missing telemetry
  158. if(CONTAINS(effects, 't')) width -= TOTAL_TELE;
  159. // Create writer
  160. png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  161. if (!png_ptr) {
  162. png_destroy_write_struct(&png_ptr, (png_infopp) NULL);
  163. fprintf(stderr, ERR_PNG_WRITE);
  164. return(0);
  165. }
  166. png_infop info_ptr = png_create_info_struct(png_ptr);
  167. if (!info_ptr) {
  168. png_destroy_write_struct(&png_ptr, (png_infopp) NULL);
  169. fprintf(stderr, ERR_PNG_INFO);
  170. return(0);
  171. }
  172. // 8 bit RGB image
  173. png_set_IHDR(png_ptr, info_ptr, width, nrow,
  174. 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
  175. PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
  176. text_ptr[1].text = chid;
  177. text_ptr[1].text_length = strlen(chid);
  178. png_set_text(png_ptr, info_ptr, text_ptr, 3);
  179. png_set_pHYs(png_ptr, info_ptr, 4160, 4160, PNG_RESOLUTION_METER);
  180. // Init I/O
  181. pngfile = fopen(filename, "wb");
  182. if (!pngfile) {
  183. fprintf(stderr, ERR_FILE_WRITE, filename);
  184. return(1);
  185. }
  186. png_init_io(png_ptr, pngfile);
  187. png_write_info(png_ptr, info_ptr);
  188. // Move prow into crow, crow ~ color rows
  189. float *crow[3000];
  190. for(int i = 0; i < nrow; i++){
  191. crow[i] = (float *) malloc(sizeof(float) * 2080 * 3);
  192. for(int x = 0; x < 2080; x++)
  193. crow[i][x*3 + 0] = crow[i][x*3 + 1] = crow[i][x*3 + 2] = prow[i][x];
  194. }
  195. if(mapFile != NULL && mapFile[0] != '\0'){
  196. if(mapOverlay(mapFile, crow, nrow, zenith, strcmp(chid, "MCIR") == 0) == 0){
  197. fprintf(stderr, "Skipping MCIR generation; see above.\n");
  198. return 0;
  199. }
  200. }else if(strcmp(chid, "MCIR")){
  201. fprintf(stderr, "Skipping MCIR generation; no map provided.\n");
  202. return 0;
  203. }
  204. printf("Writing %s", filename);
  205. // Build RGB image
  206. for (int n = 0; n < nrow; n++) {
  207. png_color pix[width];
  208. for (int i = 0; i < width; i++) {
  209. float *px = &crow[n][offset*3 + i*3];
  210. pix[i].red = CLIP(px[0], 0, 255);
  211. pix[i].green = CLIP(px[1], 0, 255);
  212. pix[i].blue = CLIP(px[2], 0, 255);
  213. }
  214. png_write_row(png_ptr, (png_bytep) pix);
  215. }
  216. // Tidy up
  217. png_write_end(png_ptr, info_ptr);
  218. fclose(pngfile);
  219. printf("\nDone\n");
  220. png_destroy_write_struct(&png_ptr, &info_ptr);
  221. return(1);
  222. }
  223. // Outputs a image with the value distribution between channel A and B
  224. static void distrib(char *filename, float **prow, int nrow) {
  225. float *distrib[256];
  226. int max = 0;
  227. // Assign memory
  228. for(int i = 0; i < 256; i++)
  229. distrib[i] = (float *) malloc(sizeof(float) * 256);
  230. for(int n = 0; n < nrow; n++) {
  231. float *pixelv = prow[n];
  232. for(int i = 0; i < CH_WIDTH; i++) {
  233. int y = (int)(pixelv[i + CHA_OFFSET]);
  234. int x = (int)(pixelv[i + CHB_OFFSET]);
  235. distrib[y][x] += 1;
  236. if(distrib[y][x] > max) max = distrib[y][x];
  237. }
  238. }
  239. // Scale to 0-255
  240. for(int x = 0; x < 256; x++)
  241. for(int y = 0; y < 256; y++)
  242. distrib[y][x] = distrib[y][x] / max * 255;
  243. ImageOut(filename, "Brightness distribution", distrib, 256, 256, 0, NULL, 0, NULL);
  244. }
  245. extern int calibrate(float **prow, int nrow, int offset, int width);
  246. extern void histogramEqualise(float **prow, int nrow, int offset, int width);
  247. extern void temperature(float **prow, int nrow, int ch, int offset);
  248. extern int Ngvi(float **prow, int nrow);
  249. extern void readfcconf(char *file);
  250. extern int optind;
  251. extern char *optarg;
  252. // Default to NOAA 19
  253. int satnum = 4;
  254. static void usage(void) {
  255. printf("Aptdec [options] audio files ...\n"
  256. "Options:\n"
  257. " -e [t|h] Enhancements\n"
  258. " t: Crop telemetry\n"
  259. " h: Histogram equalise\n"
  260. " -i [r|a|b|c|t] Output image type\n"
  261. " r: Raw\n"
  262. " a: Channel A\n"
  263. " b: Channel B\n"
  264. " c: False color\n"
  265. " t: Temperature\n"
  266. " m: MCIR\n"
  267. " -d <dir> Image destination directory.\n"
  268. " -s [15-19] Satellite number\n"
  269. " -c <file> False color config file\n"
  270. " -m <file> Map file\n");
  271. exit(1);
  272. }
  273. int readRawImage(char *filename, float **prow, int *nrow) {
  274. FILE *fp = fopen(filename, "r");
  275. // Create reader
  276. png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  277. if(!png) return 0;
  278. png_infop info = png_create_info_struct(png);
  279. if(!info) return 0;
  280. png_init_io(png, fp);
  281. // Read info from header
  282. png_read_info(png, info);
  283. int width = png_get_image_width(png, info);
  284. int height = png_get_image_height(png, info);
  285. png_byte color_type = png_get_color_type(png, info);
  286. png_byte bit_depth = png_get_bit_depth(png, info);
  287. // Check the image
  288. if(width != 2080){
  289. fprintf(stderr, "Raw image must be 2080px wide.\n");
  290. return 0;
  291. }else if(bit_depth != 8){
  292. fprintf(stderr, "Raw image must have 8 bit color.\n");
  293. return 0;
  294. }else if(color_type != PNG_COLOR_TYPE_GRAY){
  295. fprintf(stderr, "Raw image must be grayscale.\n");
  296. return 0;
  297. }
  298. // Create row buffers
  299. png_bytep *PNGrows = NULL;
  300. PNGrows = (png_bytep *) malloc(sizeof(png_bytep) * height);
  301. for(int y = 0; y < height; y++) PNGrows[y] = (png_byte *) malloc(png_get_rowbytes(png, info));
  302. // Read image
  303. png_read_image(png, PNGrows);
  304. // Tidy up
  305. fclose(fp);
  306. png_destroy_read_struct(&png, &info, NULL);
  307. // Put into prow
  308. *nrow = height;
  309. for(int y = 0; y < height; y++) {
  310. prow[y] = (float *) malloc(sizeof(float) * width);
  311. for(int x = 0; x < width; x++)
  312. prow[y][x] = (float)PNGrows[y][x];
  313. }
  314. return 1;
  315. }
  316. int main(int argc, char **argv) {
  317. char pngfilename[1024];
  318. char name[128];
  319. char pngdirname[128] = "";
  320. char mapFile[256];
  321. char *extension;
  322. // Default to a raw image, with no enhancements
  323. char imgopt[20] = "r";
  324. char enhancements[20] = "";
  325. // Image buffer
  326. float *prow[3000];
  327. int nrow;
  328. // Mapping between telemetry wedge value and channel
  329. static struct {
  330. char *id[7];
  331. char *name[7];
  332. } ch = {
  333. { "?", "1", "2", "3A", "4", "5", "3B" },
  334. { "unknown", "visble", "near-infrared", "mid-infrared", "thermal-infrared", "thermal-infrared", "mid-infrared" }
  335. };
  336. // The active sensor in each channel
  337. int chA, chB;
  338. // Print version
  339. printf(VERSION"\n");
  340. // Print usage if there are no arguments
  341. if(argc == 1)
  342. usage();
  343. int c;
  344. while ((c = getopt(argc, argv, "c:m:d:i:s:e:")) != EOF) {
  345. switch (c) {
  346. // Output directory name
  347. case 'd':
  348. strcpy(pngdirname, optarg);
  349. break;
  350. // False color config file
  351. case 'c':
  352. readfcconf(optarg);
  353. break;
  354. // Map file
  355. case 'm':
  356. strcpy(mapFile, optarg);
  357. break;
  358. // Output image type
  359. case 'i':
  360. strncpy(imgopt, optarg, 20);
  361. break;
  362. // Satellite number (for calibration)
  363. case 's':
  364. satnum = atoi(optarg)-15;
  365. // Check if it's within the valid range
  366. if (satnum < 0 || satnum > 4) {
  367. fprintf(stderr, "Invalid satellite number, it must be the range [15-19]\n");
  368. exit(1);
  369. }
  370. break;
  371. // Enchancements
  372. case 'e':
  373. strncpy(enhancements, optarg, 20);
  374. break;
  375. default:
  376. usage();
  377. }
  378. }
  379. if(optind == argc){
  380. printf("No input files provided.\n");
  381. usage();
  382. }
  383. // Process the provided files
  384. for (; optind < argc; optind++) {
  385. chA = chB = 0;
  386. // Generate output name
  387. strcpy(pngfilename, argv[optind]);
  388. strcpy(name, basename(pngfilename));
  389. strtok(name, ".");
  390. extension = strtok(NULL, ".");
  391. if (pngdirname[0] == '\0')
  392. strcpy(pngdirname, dirname(pngfilename));
  393. if(strcmp(extension, "png") == 0){
  394. printf("Reading %s", argv[optind]);
  395. if(readRawImage(argv[optind], prow, &nrow) == 0){
  396. fprintf(stderr, "Skipping %s; see above.\n", name);
  397. continue;
  398. }
  399. }else{
  400. // Open sound file, exit if that fails
  401. if (initsnd(argv[optind]) == 0) exit(1);
  402. // Main image building loop
  403. for (nrow = 0; nrow < 3000; nrow++) {
  404. // Allocate 2150 floats worth of memory for every line of the image
  405. prow[nrow] = (float *) malloc(sizeof(float) * 2150);
  406. // Read into prow and break the loop once we reach the end of the image
  407. if (getpixelrow(prow[nrow], nrow, &zenith) == 0) break;
  408. printf("Row: %d\r", nrow);
  409. fflush(stdout);
  410. }
  411. // Close sound file
  412. sf_close(inwav);
  413. }
  414. if(zenith == 0 & mapFile[0] != '\0'){
  415. fprintf(stderr, "WARNING: Guessing peak elevation in image, map will most likely not be aligned.\n");
  416. zenith = nrow / 2;
  417. }
  418. printf("\nTotal rows: %d\n", nrow);
  419. // Calibrate
  420. chA = calibrate(prow, nrow, CHA_OFFSET, CH_WIDTH);
  421. chB = calibrate(prow, nrow, CHB_OFFSET, CH_WIDTH);
  422. printf("Channel A: %s (%s)\n", ch.id[chA], ch.name[chA]);
  423. printf("Channel B: %s (%s)\n", ch.id[chB], ch.name[chB]);
  424. // Temperature
  425. if (CONTAINS(imgopt, 't') && chB >= 4) {
  426. // TODO: Doesn't work with channel 4
  427. temperature(prow, nrow, chB, CHB_OFFSET);
  428. sprintf(pngfilename, "%s/%s-t.png", pngdirname, name);
  429. ImageOut(pngfilename, "Temperature", prow, nrow, 2080, 0, TempPalette, enhancements, mapFile);
  430. }
  431. // False color image
  432. if(CONTAINS(imgopt, 'c')){
  433. if (chA == 2 && chB >= 4) { // Normal false color
  434. sprintf(pngfilename, "%s/%s-c.png", pngdirname, name);
  435. //ImageRGBOut(pngfilename, prow, nrow);
  436. ImageOut(pngfilename, "False Color", prow, nrow, CH_WIDTH, 0, NULL, enhancements, mapFile);
  437. } else if (chB == 2) { // GVI (global vegetation index) false color
  438. Ngvi(prow, nrow);
  439. sprintf(pngfilename, "%s/%s-c.png", pngdirname, name);
  440. ImageOut(pngfilename, "GVI False Color", prow, nrow, CH_WIDTH, CHB_OFFSET, GviPalette, enhancements, mapFile);
  441. } else {
  442. fprintf(stderr, "Skipping False Color generation; lacking required channels.\n");
  443. }
  444. }
  445. // MCIR
  446. if (CONTAINS(imgopt, 'm')) {
  447. sprintf(pngfilename, "%s/%s-m.png", pngdirname, name);
  448. ImageOut(pngfilename, "MCIR", prow, nrow, 909, CHA_OFFSET, NULL, enhancements, mapFile);
  449. }
  450. // Histogram equalise
  451. if(CONTAINS(enhancements, 'h')){
  452. histogramEqualise(prow, nrow, CHA_OFFSET, CH_WIDTH);
  453. histogramEqualise(prow, nrow, CHB_OFFSET, CH_WIDTH);
  454. }
  455. // Raw image
  456. if (CONTAINS(imgopt, 'r')) {
  457. char channelstr[45];
  458. sprintf(channelstr, "%s (%s) & %s (%s)", ch.id[chA], ch.name[chA], ch.id[chB], ch.name[chB]);
  459. sprintf(pngfilename, "%s/%s-r.png", pngdirname, name);
  460. ImageOut(pngfilename, channelstr, prow, nrow, IMG_WIDTH, 0, NULL, enhancements, mapFile);
  461. }
  462. // Channel A
  463. if (CONTAINS(imgopt, 'a')) {
  464. char channelstr[21];
  465. sprintf(channelstr, "%s (%s)", ch.id[chA], ch.name[chA]);
  466. sprintf(pngfilename, "%s/%s-%s.png", pngdirname, name, ch.id[chA]);
  467. ImageOut(pngfilename, channelstr, prow, nrow, CH_WIDTH, CHA_OFFSET, NULL, enhancements, mapFile);
  468. }
  469. // Channel B
  470. if (CONTAINS(imgopt, 'b')) {
  471. char channelstr[21];
  472. sprintf(channelstr, "%s (%s)", ch.id[chB], ch.name[chB]);
  473. sprintf(pngfilename, "%s/%s-%s.png", pngdirname, name, ch.id[chB]);
  474. ImageOut(pngfilename, channelstr, prow, nrow, CH_WIDTH , CHB_OFFSET, NULL, enhancements, mapFile);
  475. }
  476. // Distribution image
  477. if (CONTAINS(imgopt, 'd')) {
  478. sprintf(pngfilename, "%s/%s-d.png", pngdirname, name);
  479. distrib(pngfilename, prow, nrow);
  480. }
  481. }
  482. exit(0);
  483. }