選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

main.c 16 KiB

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. }