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.
 
 
 
 
 

317 lines
10 KiB

  1. /*
  2. * This file is part of Aptdec.
  3. * Copyright (c) 2004-2009 Thierry Leconte (F4DWV), Xerbo (xerbo@protonmail.com) 2019-2022
  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 <stdio.h>
  20. #include <stdlib.h>
  21. #include <string.h>
  22. #ifndef _MSC_VER
  23. #include <libgen.h>
  24. #else
  25. #include <windows.h>
  26. #endif
  27. #include <errno.h>
  28. #include <math.h>
  29. #include <sndfile.h>
  30. #include <time.h>
  31. #include "apt.h"
  32. #include "argparse/argparse.h"
  33. #include "color.h"
  34. #include "common.h"
  35. #include "image.h"
  36. #include "pngio.h"
  37. #include "util.h"
  38. // Audio file
  39. static SNDFILE *audioFile;
  40. // Number of channels in audio file
  41. int channels = 1;
  42. // Function declarations
  43. static int initsnd(char *filename);
  44. int getsamples(void *context, float *samples, int nb);
  45. static int processAudio(char *filename, options_t *opts);
  46. #ifdef _MSC_VER
  47. // Functions not supported by MSVC
  48. static char *dirname(char *path) {
  49. static char dir[MAX_PATH];
  50. _splitpath(path, NULL, dir, NULL, NULL);
  51. return dir;
  52. }
  53. static char *basename(char *path) {
  54. static char base[MAX_PATH];
  55. _splitpath(path, NULL, NULL, base, NULL);
  56. return base;
  57. }
  58. #endif
  59. int main(int argc, const char **argv) {
  60. options_t opts = {
  61. .type = "r", .effects = "", .satnum = 19, .path = ".", .realtime = 0, .filename = "", .palette = "", .gamma = 1.0};
  62. static const char *const usages[] = {
  63. "aptdec [options] [[--] sources]",
  64. "aptdec [sources]",
  65. NULL,
  66. };
  67. struct argparse_option options[] = {
  68. OPT_HELP(),
  69. OPT_GROUP("Image options"),
  70. OPT_STRING('i', "image", &opts.type, "set output image type (see the README for a list)", NULL, 0, 0),
  71. OPT_STRING('e', "effect", &opts.effects, "add an effect (see the README for a list)", NULL, 0, 0),
  72. OPT_FLOAT('g', "gamma", &opts.gamma, "gamma adjustment (1.0 = off)", NULL, 0, 0),
  73. OPT_GROUP("Satellite options"),
  74. OPT_INTEGER('s', "satellite", &opts.satnum, "satellite ID, must be between 15 and 19", NULL, 0, 0),
  75. OPT_GROUP("Paths"),
  76. OPT_STRING('p', "palette", &opts.palette, "path to a palette", NULL, 0, 0),
  77. OPT_STRING('o', "filename", &opts.filename, "filename of the output image", NULL, 0, 0),
  78. OPT_STRING('d', "output", &opts.path, "output directory (must exist first)", NULL, 0, 0),
  79. OPT_GROUP("Misc"),
  80. OPT_BOOLEAN('r', "realtime", &opts.realtime, "decode in realtime", NULL, 0, 0),
  81. OPT_END(),
  82. };
  83. struct argparse argparse;
  84. argparse_init(&argparse, options, usages, 0);
  85. argparse_describe(
  86. &argparse, "\nA lightweight FOSS NOAA APT satellite imagery decoder.",
  87. "\nSee `README.md` for a full description of command line arguments and `LICENSE` for licensing conditions.");
  88. argc = argparse_parse(&argparse, argc, argv);
  89. if (argc == 0) {
  90. argparse_usage(&argparse);
  91. }
  92. // Actually decode the files
  93. for (int i = 0; i < argc; i++) {
  94. char *filename = strdup(argv[i]);
  95. processAudio(filename, &opts);
  96. }
  97. return 0;
  98. }
  99. static int processAudio(char *filename, options_t *opts) {
  100. // Image info struct
  101. apt_image_t img;
  102. // Mapping between wedge value and channel ID
  103. static struct {
  104. char *id[7];
  105. char *name[7];
  106. } ch = {{"?", "1", "2", "3A", "4", "5", "3B"},
  107. {"unknown", "visble", "near-infrared", "near-infrared", "thermal-infrared", "thermal-infrared", "mid-infrared"}};
  108. // Buffer for image channel
  109. char desc[60];
  110. // Parse file path
  111. char path[256], extension[32];
  112. strcpy(path, filename);
  113. strcpy(path, dirname(path));
  114. sscanf(basename(filename), "%255[^.].%31s", img.name, extension);
  115. if (opts->realtime) {
  116. // Set output filename to current time when in realtime mode
  117. time_t t;
  118. time(&t);
  119. strncpy(img.name, ctime(&t), 24);
  120. // Init a row writer
  121. initWriter(opts, &img, APT_IMG_WIDTH, APT_MAX_HEIGHT, "Unprocessed realtime image", "r");
  122. }
  123. if (strcmp(extension, "png") == 0) {
  124. // Read PNG into image buffer
  125. printf("Reading %s\n", filename);
  126. if (readRawImage(filename, img.prow, &img.nrow) == 0) {
  127. exit(EPERM);
  128. }
  129. } else {
  130. // Attempt to open the audio file
  131. if (initsnd(filename) == 0) exit(EPERM);
  132. // Build image
  133. // TODO: multithreading, would require some sort of input buffer
  134. for (img.nrow = 0; img.nrow < APT_MAX_HEIGHT; img.nrow++) {
  135. // Allocate memory for this row
  136. img.prow[img.nrow] = (float *)malloc(sizeof(float) * APT_PROW_WIDTH);
  137. // Write into memory and break the loop when there are no more samples to read
  138. if (apt_getpixelrow(img.prow[img.nrow], img.nrow, &img.zenith, (img.nrow == 0), getsamples, NULL) == 0) break;
  139. if (opts->realtime) pushRow(img.prow[img.nrow], APT_IMG_WIDTH);
  140. fprintf(stderr, "Row: %d\r", img.nrow);
  141. fflush(stderr);
  142. }
  143. // Close stream
  144. sf_close(audioFile);
  145. }
  146. if (opts->realtime) closeWriter();
  147. printf("Total rows: %d\n", img.nrow);
  148. // Calibrate
  149. img.chA = apt_calibrate(img.prow, img.nrow, APT_CHA_OFFSET, APT_CH_WIDTH);
  150. img.chB = apt_calibrate(img.prow, img.nrow, APT_CHB_OFFSET, APT_CH_WIDTH);
  151. printf("Channel A: %s (%s)\n", ch.id[img.chA], ch.name[img.chA]);
  152. printf("Channel B: %s (%s)\n", ch.id[img.chB], ch.name[img.chB]);
  153. // Crop noise from start and end of image
  154. if (CONTAINS(opts->effects, Crop_Noise)) {
  155. img.zenith -= apt_cropNoise(&img);
  156. }
  157. // Denoise
  158. if (CONTAINS(opts->effects, Denoise)) {
  159. apt_denoise(img.prow, img.nrow, APT_CHA_OFFSET, APT_CH_WIDTH);
  160. apt_denoise(img.prow, img.nrow, APT_CHB_OFFSET, APT_CH_WIDTH);
  161. }
  162. // Flip, for northbound passes
  163. if (CONTAINS(opts->effects, Flip_Image)) {
  164. apt_flipImage(&img, APT_CH_WIDTH, APT_CHA_OFFSET);
  165. apt_flipImage(&img, APT_CH_WIDTH, APT_CHB_OFFSET);
  166. }
  167. // Temperature
  168. if (CONTAINS(opts->type, Temperature) && img.chB >= 4) {
  169. // Create another buffer as to not modify the orignal
  170. apt_image_t tmpimg = img;
  171. for (int i = 0; i < img.nrow; i++) {
  172. tmpimg.prow[i] = (float *)malloc(sizeof(float) * APT_PROW_WIDTH);
  173. memcpy(tmpimg.prow[i], img.prow[i], sizeof(float) * APT_PROW_WIDTH);
  174. }
  175. // Perform temperature calibration
  176. apt_calibrate_thermal(opts->satnum, &tmpimg, APT_CHB_OFFSET, APT_CH_WIDTH);
  177. ImageOut(opts, &tmpimg, APT_CHB_OFFSET, APT_CH_WIDTH, "Temperature", Temperature, (char *)apt_TempPalette);
  178. }
  179. // Visible
  180. if (CONTAINS(opts->type, Visible) && img.chA <= 2) {
  181. // Create another buffer as to not modify the orignal
  182. apt_image_t tmpimg = img;
  183. for (int i = 0; i < img.nrow; i++) {
  184. tmpimg.prow[i] = (float *)malloc(sizeof(float) * APT_PROW_WIDTH);
  185. memcpy(tmpimg.prow[i], img.prow[i], sizeof(float) * APT_PROW_WIDTH);
  186. }
  187. // Perform visible calibration
  188. apt_calibrate_visible(opts->satnum, &tmpimg, APT_CHA_OFFSET, APT_CH_WIDTH);
  189. ImageOut(opts, &tmpimg, APT_CHA_OFFSET, APT_CH_WIDTH, "Visible", Visible, NULL);
  190. }
  191. // Linear equalise
  192. if (CONTAINS(opts->effects, Linear_Equalise)) {
  193. apt_linearEnhance(img.prow, img.nrow, APT_CHA_OFFSET, APT_CH_WIDTH);
  194. apt_linearEnhance(img.prow, img.nrow, APT_CHB_OFFSET, APT_CH_WIDTH);
  195. }
  196. // Histogram equalise
  197. if (CONTAINS(opts->effects, Histogram_Equalise)) {
  198. apt_histogramEqualise(img.prow, img.nrow, APT_CHA_OFFSET, APT_CH_WIDTH);
  199. apt_histogramEqualise(img.prow, img.nrow, APT_CHB_OFFSET, APT_CH_WIDTH);
  200. }
  201. // Raw image
  202. if (CONTAINS(opts->type, Raw_Image)) {
  203. sprintf(desc, "%s (%s) & %s (%s)", ch.id[img.chA], ch.name[img.chA], ch.id[img.chB], ch.name[img.chB]);
  204. ImageOut(opts, &img, 0, APT_IMG_WIDTH, desc, Raw_Image, NULL);
  205. }
  206. // Palette image
  207. if (CONTAINS(opts->type, Palleted)) {
  208. img.palette = opts->palette;
  209. strcpy(desc, "Palette composite");
  210. ImageOut(opts, &img, APT_CHA_OFFSET, APT_CH_WIDTH, desc, Palleted, NULL);
  211. }
  212. // Channel A
  213. if (CONTAINS(opts->type, Channel_A)) {
  214. sprintf(desc, "%s (%s)", ch.id[img.chA], ch.name[img.chA]);
  215. ImageOut(opts, &img, APT_CHA_OFFSET, APT_CH_WIDTH, desc, Channel_A, NULL);
  216. }
  217. // Channel B
  218. if (CONTAINS(opts->type, Channel_B)) {
  219. sprintf(desc, "%s (%s)", ch.id[img.chB], ch.name[img.chB]);
  220. ImageOut(opts, &img, APT_CHB_OFFSET, APT_CH_WIDTH, desc, Channel_B, NULL);
  221. }
  222. return 1;
  223. }
  224. float *samplebuf;
  225. static int initsnd(char *filename) {
  226. SF_INFO infwav;
  227. int res;
  228. // Open audio file
  229. infwav.format = 0;
  230. audioFile = sf_open(filename, SFM_READ, &infwav);
  231. if (audioFile == NULL) {
  232. error_noexit("Could not file");
  233. return 0;
  234. }
  235. res = apt_init(infwav.samplerate);
  236. printf("Input file: %s\n", filename);
  237. if (res < 0) {
  238. error_noexit("Input sample rate too low");
  239. return 0;
  240. } else if (res > 0) {
  241. error_noexit("Input sample rate too high");
  242. return 0;
  243. }
  244. printf("Input sample rate: %d\n", infwav.samplerate);
  245. channels = infwav.channels;
  246. samplebuf = (float *)malloc(sizeof(float) * 32768 * channels);
  247. return 1;
  248. }
  249. // Read samples from the audio file
  250. int getsamples(void *context, float *samples, int nb) {
  251. (void)context;
  252. if (channels == 1) {
  253. return (int)sf_read_float(audioFile, samples, nb);
  254. } else if (channels == 2) {
  255. // Stereo channels are interleaved
  256. int samplesRead = (int)sf_read_float(audioFile, samplebuf, nb * channels);
  257. for (int i = 0; i < nb; i++) {
  258. samples[i] = samplebuf[i * channels];
  259. }
  260. return samplesRead / channels;
  261. } else {
  262. printf("Only mono and stereo input files are supported\n");
  263. exit(1);
  264. }
  265. }