Add a denoise filter Fix temperature calibration Tidy up some files Improve MCIRtags/v1.8.0
@@ -1,17 +1,19 @@ | |||||
CC = gcc | CC = gcc | ||||
BIN = /usr/bin | BIN = /usr/bin | ||||
INCLUDES = -I. | INCLUDES = -I. | ||||
CFLAGS = -O3 -Wall $(INCLUDES) | |||||
OBJS = main.o image.o dsp.o filter.o reg.o fcolor.o | |||||
CFLAGS = -O3 -DNDEBUG -Wall $(INCLUDES) | |||||
OBJS = main.o image.o dsp.o filter.o reg.o fcolor.o pngio.o median.o color.o | |||||
aptdec: $(OBJS) | aptdec: $(OBJS) | ||||
$(CC) -o $@ $(OBJS) -lm -lsndfile -lpng | $(CC) -o $@ $(OBJS) -lm -lsndfile -lpng | ||||
main.o: main.c palette.h offsets.h messages.h | |||||
dsp.o: dsp.c filtercoeff.h filter.h | |||||
color.o: color.c | |||||
main.o: main.c offsets.h messages.h | |||||
dsp.o: dsp.c filtercoeff.h filter.h | |||||
filter.o: filter.c filter.h | filter.o: filter.c filter.h | ||||
image.o: image.c satcal.h offsets.h messages.h | |||||
image.o: image.c offsets.h messages.h offsets.h | |||||
fcolor.o: fcolor.c offsets.h | fcolor.o: fcolor.c offsets.h | ||||
pngio.o: pngio.c offsets.h messages.h | |||||
clean: | clean: | ||||
rm -f *.o aptdec | rm -f *.o aptdec | ||||
@@ -7,7 +7,7 @@ Copyright (c) 2004-2009 Thierry Leconte (F4DWV), Xerbo (xerbo@protonmail.com) 20 | |||||
Aptdec is an FOSS program that decodes images transmitted by NOAA weather satellites. These satellites transmit continuously (among other things), medium resolution (1px/4km) images of the earth on 137 MHz. | Aptdec is an FOSS program that decodes images transmitted by NOAA weather satellites. These satellites transmit continuously (among other things), medium resolution (1px/4km) images of the earth on 137 MHz. | ||||
These transmissions could be easily received with an simple antenna and cheap SDR. Then the transmission can easily be decoded in narrow band FM mode. | These transmissions could be easily received with an simple antenna and cheap SDR. Then the transmission can easily be decoded in narrow band FM mode. | ||||
Aptdec can convert these audio files into .png images. | |||||
Aptdec can convert these audio files into `png` images. | |||||
For each audio file up to 6 images can be generated: | For each audio file up to 6 images can be generated: | ||||
@@ -53,11 +53,11 @@ To uninstall | |||||
``` | ``` | ||||
-i [r|a|b|c|t|l] | -i [r|a|b|c|t|l] | ||||
Output image type | Output image type | ||||
Raw (r), Channel A (a), Channel B (b), False Color (c), Temperature (t) | |||||
Raw (r), Channel A (a), Channel B (b), False Color (c), Temperature (t) or MCIR (m) | |||||
Default: "ab" | Default: "ab" | ||||
-d <dir> | -d <dir> | ||||
Images destination directory (optional). | |||||
Images destination directory (optional) | |||||
Default: Current directory | Default: Current directory | ||||
-s [15|16|17|18|19] | -s [15|16|17|18|19] | ||||
@@ -66,15 +66,15 @@ For temperature calibration | |||||
Default: "19" | Default: "19" | ||||
-e [t|h] | -e [t|h] | ||||
Enhancements | |||||
Histogram equalise (h), Crop Telemetry (t) | |||||
Effects | |||||
Histogram equalise (h), Crop Telemetry (t) or Denoise (d) | |||||
Defaults: off | Defaults: off | ||||
-m <file> | -m <file> | ||||
Map overlay generated by wxmap | |||||
Map file generated by wxmap | |||||
-c <file> | -c <file> | ||||
Use configuration file for false color generation. | |||||
Use configuration file for false color generation | |||||
Default: Internal defaults | Default: Internal defaults | ||||
``` | ``` | ||||
@@ -88,11 +88,13 @@ Image names are `audiofile-x.png`, where `x` is: | |||||
- 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. | - `c` for false color. | ||||
- `t` for temperature calibrated images | - `t` for temperature calibrated images | ||||
- `m` for MCIR images | |||||
Currently there are 3 available enhancements: | |||||
Currently there are 3 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, stretch the colors in the image to black and white | ||||
- `d` for a median denoise filter | |||||
## Example | ## Example | ||||
@@ -0,0 +1,159 @@ | |||||
/* | |||||
* This file is part of Aptdec. | |||||
* Copyright (c) 2004-2009 Thierry Leconte (F4DWV), Xerbo (xerbo@protonmail.com) 2019-2020 | |||||
* | |||||
* Aptdec is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 2 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. | |||||
* | |||||
*/ | |||||
#include "offsets.h" | |||||
typedef struct { | |||||
float r, g, b; | |||||
} rgb_t; | |||||
rgb_t applyPalette(char *palette, int val){ | |||||
return (rgb_t){ | |||||
palette[(int)CLIP(val, 0, 255)*3 + 0], | |||||
palette[(int)CLIP(val, 0, 255)*3 + 1], | |||||
palette[(int)CLIP(val, 0, 255)*3 + 2] | |||||
}; | |||||
} | |||||
rgb_t RGBcomposite(rgb_t top, float top_a, rgb_t bottom, float bottom_a){ | |||||
rgb_t composite; | |||||
composite.r = MCOMPOSITE(top.r, top_a, bottom.r, bottom_a); | |||||
composite.g = MCOMPOSITE(top.g, top_a, bottom.g, bottom_a); | |||||
composite.b = MCOMPOSITE(top.b, top_a, bottom.b, bottom_a); | |||||
return composite; | |||||
} | |||||
char GviPalette[256*3] = { | |||||
"\230t\17\233x\22\236{\27\241\200\33\244\203\37\247\210#\252\214'\255\220" | |||||
",\260\2240\264\2305\267\2358\272\240=\274\245A\300\251E\303\255I\306\262" | |||||
"M\311\266Q\314\272V\317\276Z\322\302^\325\306b\330\312g\334\317k\337\323" | |||||
"o\342\330s\344\333w\350\337{\352\344\200\356\350\203\361\354\207\364\360" | |||||
"\213\367\364\220\372\370\224\376\376\230\376\375\230\376\372\227\376\366" | |||||
"\224\376\362\222\376\357\220\376\352\216\376\347\213\376\343\211\376\340" | |||||
"\207\376\334\205\376\330\202\376\325\200\376\321\177\376\315|\376\312z\376" | |||||
"\306y\376\302v\376\277t\376\273q\376\267o\376\263m\376\260k\376\254h\376" | |||||
"\250f\376\245d\376\241b\376\235_\376\232^\376\226[\376\223Y\376\217W\376" | |||||
"\213U\376\207R\376\203Q\376\200N\376}L\376zJ\376vG\376rE\376nB\376k@\376" | |||||
"g>\376d<\376`:\376\\7\376X6\376T3\376Q0\376M/\376J-\376F*\376C(\376>%\376" | |||||
";$\3767!\3763\37\3760\35\376,\32\376(\31\376$\26\376!\24\376\35\22\376\32" | |||||
"\20\376\26\15\376\22\13\376\16\11\376\12\6\376\7\4\376\0\0\373\2\0\366\3" | |||||
"\0\362\5\0\355\7\0\350\11\0\343\13\0\336\15\0\332\17\0\325\21\0\320\22\0" | |||||
"\313\24\0\307\26\0\303\27\0\275\32\0\270\33\0\264\35\0\257\37\0\253!\0\246" | |||||
"#\0\241%\0\234&\0\227)\0\223+\0\216,\0\212.\0\2050\0\2002\0}4\0w6\0s7\0n" | |||||
"9\0i;\0e>\0`?\0[A\0VC\0QE\0MG\0HH\0CK\0?M\0:N\0""5P\0""0R\0,S\0'V\0\"X\0" | |||||
"\36Z\0\31\\\0\23^\0\17_\0\12a\0\3b\0\6e\2\12i\5\17n\11\23r\14\30v\17\34{" | |||||
"\23\40\200\26$\203\31)\207\35.\215\40""1\221$6\225':\232*?\236.C\2431G\250" | |||||
"4K\2548O\260;U\265>Y\272A\\\276Eb\303Ie\307Lj\313On\320Ss\324Vw\331Y{\336" | |||||
"]\200\342`\203\347d\207\353g\213\357j\217\364n\223\371q\230\376t\222\375" | |||||
"{\207\374\205}\373\216r\371\230g\371\241\\\370\254Q\367\265F\365\300;\364" | |||||
"\3110\363\323%\362\334\32\361\347\17\357\361\3\356\371\4\353\370\3\347\365" | |||||
"\4\342\361\4\335\357\4\331\354\4\323\351\3\317\346\3\313\343\4\306\340\3" | |||||
"\301\335\3\275\332\3\270\327\3\263\324\2\257\322\3\252\316\3\245\314\3\241" | |||||
"\311\2\234\306\2\230\303\2\223\300\3\216\275\3\211\273\2\205\267\2\200\265" | |||||
"\2|\262\2w\257\2s\254\2n\251\2j\246\2e\243\2`\240\2[\235\2W\232\1S\230\2" | |||||
"M\225\1I\221\2E\217\1@\214\1;\211\1""7\206\1""1\203\1-\200\0(~\1${\0\37y" | |||||
"\0\33u\0\25r\0\21p\0\14l\0\7j\0\3" | |||||
}; | |||||
char TempPalette[256*3] = { | |||||
"\376\376\376\376\376\376\375\375\376\374\375\376\374\375\375\374\373\375" | |||||
"\373\373\375\372\373\375\372\373\374\372\372\374\371\372\374\371\371\375" | |||||
"\370\371\374\367\370\375\367\370\374\367\367\374\366\367\373\366\366\373" | |||||
"\365\366\373\364\366\373\364\365\374\363\365\373\363\364\373\363\364\373" | |||||
"\362\363\372\361\363\372\361\362\372\361\361\372\360\361\372\357\361\372" | |||||
"\357\361\372\356\360\372\355\357\371\355\357\371\355\356\372\354\356\371" | |||||
"\354\355\371\354\355\371\353\355\371\353\354\370\352\353\370\351\353\370" | |||||
"\351\353\370\350\352\370\350\351\370\347\351\370\347\351\370\346\350\367" | |||||
"\346\350\367\346\350\367\345\346\367\344\346\367\344\346\367\344\345\366" | |||||
"\342\346\366\343\344\366\342\345\366\341\344\366\341\343\365\340\343\366" | |||||
"\337\342\365\337\341\366\336\341\365\337\341\365\336\340\365\335\340\365" | |||||
"\335\337\364\334\337\364\334\336\364\334\336\364\333\336\364\333\335\364" | |||||
"\332\334\364\331\334\363\331\334\364\330\333\363\330\333\363\327\332\363" | |||||
"\326\332\363\327\331\362\326\331\362\325\331\362\325\330\362\324\330\363" | |||||
"\323\327\362\323\327\362\322\326\362\322\326\362\322\325\361\321\325\361" | |||||
"\321\325\361\320\323\361\320\323\361\317\323\361\317\322\360\316\322\361" | |||||
"\316\322\360\315\321\360\314\321\360\314\320\360\313\320\360\313\317\360" | |||||
"\312\317\357\311\317\357\312\316\357\311\316\357\310\315\356\310\314\356" | |||||
"\307\314\356\307\313\357\306\313\357\306\313\356\305\312\356\305\312\355" | |||||
"\305\311\356\304\311\356\303\311\355\302\310\355\302\307\355\302\307\355" | |||||
"\301\306\355\301\306\355\300\305\354\300\305\354\277\305\354\277\304\354" | |||||
"\276\304\354\276\304\354\276\302\353\275\303\354\274\302\353\274\301\353" | |||||
"\274\301\353\273\301\353\272\300\353\272\277\353\271\277\352\271\276\352" | |||||
"\270\276\352\270\276\352\267\275\351\267\274\352\266\275\352\265\273\352" | |||||
"\265\273\351\264\273\351\265\272\351\263\272\351\263\272\351\263\271\350" | |||||
"\263\271\351\262\271\350\261\267\350\260\267\350\260\266\350\257\267\350" | |||||
"\256\270\350\255\272\350\254\273\351\252\275\351\251\275\351\247\300\351" | |||||
"\247\301\351\244\303\352\243\306\352\242\310\353\240\312\352\237\315\352" | |||||
"\236\317\352\234\322\353\233\324\353\232\330\353\230\332\354\227\336\354" | |||||
"\225\341\354\224\345\354\223\350\354\222\354\355\220\355\353\217\355\347" | |||||
"\216\355\344\214\355\337\212\356\334\211\356\330\210\357\324\207\356\317" | |||||
"\205\357\313\204\357\307\202\360\302\200\357\275\200\360\270\177\360\264" | |||||
"~\360\256|\361\251z\360\244y\361\237x\361\230v\362\223u\362\215s\362\207" | |||||
"r\362\200p\362|o\362un\362np\363ls\363kw\363i|\364g\200\363f\204\363d\210" | |||||
"\364b\215\364a\222\364`\227\365^\234\365]\241\365\\\246\365Z\254\366Y\261" | |||||
"\365W\267\366U\275\366T\303\366S\311\367Q\317\367O\325\367N\334\370L\343" | |||||
"\370J\352\370I\360\370G\367\371F\370\362E\371\354C\370\345A\371\336@\371" | |||||
"\326?\372\316=\372\307;\372\277:\372\2679\373\2577\373\2475\373\2373\373" | |||||
"\2261\373\2161\373\206.\373}-\374u,\375k*\374b(\375Y'\375O%\375E#\375;\"" | |||||
"\3762\40\376(\37\376\35" | |||||
}; | |||||
char PrecipPalette[256*3] = { | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" | |||||
"\x0\x0\x0\x1\x42\xc1\x3\x54\xd2\x8\x68\xe0\x10\x7c\xec\x1b\x91\xf5" | |||||
"\x29\xa4\xfc\x39\xb7\xff\x4a\xc9\xff\x5d\xd8\xfb\x71\xe6\xf5\x85\xf1\xeb" | |||||
"\x9a\xf9\xdf\xad\xfd\xd0\xbf\xff\xc0\xd0\xfd\xad\xdf\xf9\x9a\xeb\xf1\x86" | |||||
"\xf5\xe6\x71\xfb\xd9\x5d\xff\xc9\x4a\xff\xb7\x39\xfc\xa4\x29\xf5\x91\x1b" | |||||
"\xec\x7c\x10\xe0\x68\x8\xd2\x55\x3\xc1\x42\x1\xaf\x31\x2\x9c\x22\x7" | |||||
"\x87\x16\xe\x73\xc\x19\x5f\x5\x26\x4c\x2\x36\x3a\x1\x47\x2a\x4\x5a" | |||||
"\x1c\xa\x6e\x11\x13\x82\x9\x1f\x96\x3\x2d\xaa\x1\x3d\xbc\x2\x4f\xcd" | |||||
"\x6\x63\xdc\xe\x77\xe9\x18\x8b\xf3\x25\x9f\xfa\x34\xb2\xfe\x45\xc4\xff" | |||||
"\x58\xd4\xfc\x6c\xe2\xf7\x80\xee\xee\x94\xf7\xe2\xa8\xfc\xd4\xbb\xff\xc4" | |||||
"\xcc\xfe\xb2\xdb\xfa\x9f\xe8\xf3\x8b\xf2\xe9\x77\xfa\xdc\x63\xfe\xcd\x4f" | |||||
"\xff\xbc\x3d\xfd\xaa\x2d\xf7\x96\x1f\xef\x82\x13\xe4\x6e\xa\xd6\x5a\x4" | |||||
"\xc6\x47\x1\xb4\x36\x2\xa1\x26\x5\x8d\x19\xc" | |||||
}; |
@@ -51,7 +51,7 @@ static double FreqLine = 1.0; | |||||
static double FreqOsc; | static double FreqOsc; | ||||
static double K1, K2; | static double K1, K2; | ||||
// Check the input sample rate and calculate some constants | |||||
// Check the sample rate and calculate some constants | |||||
int init_dsp(double F) { | int init_dsp(double F) { | ||||
if(F > Fi) return(1); | if(F > Fi) return(1); | ||||
if(F < Fp) return(-1); | if(F < Fp) return(-1); | ||||
@@ -72,8 +72,7 @@ static inline double Phase(double I, double Q) { | |||||
double angle, r; | double angle, r; | ||||
int s; | int s; | ||||
if(I == 0.0 && Q == 0.0) | |||||
return(0.0); | |||||
if(I == 0.0 && Q == 0.0) return(0.0); | |||||
if (Q < 0) { | if (Q < 0) { | ||||
s = -1; | s = -1; | ||||
@@ -90,10 +89,11 @@ static inline double Phase(double I, double Q) { | |||||
angle = 0.75 - 0.25 * r; | angle = 0.75 - 0.25 * r; | ||||
} | } | ||||
if(s > 0) | |||||
if(s > 0){ | |||||
return(angle); | return(angle); | ||||
else | |||||
}else{ | |||||
return(-angle); | return(-angle); | ||||
} | |||||
} | } | ||||
/* Phase locked loop | /* Phase locked loop | ||||
@@ -179,7 +179,7 @@ int getpixelv(float *pvbuff, int count) { | |||||
double mult; | double mult; | ||||
// Sub-pixel offset | |||||
// Gaussian resampling factor | |||||
mult = (double) Fi / Fe * FreqLine; | mult = (double) Fi / Fe * FreqLine; | ||||
int m = RSFilterLen / mult + 1; | int m = RSFilterLen / mult + 1; | ||||
@@ -205,16 +205,18 @@ int getpixelv(float *pvbuff, int count) { | |||||
idxam += shift; | idxam += shift; | ||||
nam -= shift; | nam -= shift; | ||||
} | } | ||||
return(count); | return(count); | ||||
} | } | ||||
// Get an entire row of pixels, aligned with sync markers | // Get an entire row of pixels, aligned with sync markers | ||||
double minDoppler = 100; | |||||
// FIXME: occasionally skips noisy lines | |||||
int getpixelrow(float *pixelv, int nrow, int *zenith) { | int getpixelrow(float *pixelv, int nrow, int *zenith) { | ||||
static float pixels[PixelLine + SyncFilterLen]; | static float pixels[PixelLine + SyncFilterLen]; | ||||
static int npv; | static int npv; | ||||
static int synced = 0; | static int synced = 0; | ||||
static double max = 0.0; | static double max = 0.0; | ||||
static double minDoppler = 100; | |||||
double corr, ecorr, lcorr; | double corr, ecorr, lcorr; | ||||
int res; | int res; | ||||
@@ -233,12 +235,12 @@ int getpixelrow(float *pixelv, int nrow, int *zenith) { | |||||
// Calculate the frequency offset | // Calculate the frequency offset | ||||
ecorr = fir(pixelv, Sync, SyncFilterLen); | ecorr = fir(pixelv, Sync, SyncFilterLen); | ||||
corr = fir(&(pixelv[1]), Sync, SyncFilterLen - 1); | |||||
lcorr = fir(&(pixelv[2]), Sync, SyncFilterLen - 2); | |||||
corr = fir(&pixelv[1], Sync, SyncFilterLen - 1); | |||||
lcorr = fir(&pixelv[2], Sync, SyncFilterLen - 2); | |||||
FreqLine = 1.0+((ecorr-lcorr) / corr / PixelLine / 4.0); | FreqLine = 1.0+((ecorr-lcorr) / corr / PixelLine / 4.0); | ||||
// Find the point in which ecorr and lcorr intercept, make sure that it's not too perfect | |||||
if(fabs(lcorr - ecorr) < minDoppler && fabs(lcorr - ecorr) > 1){ | |||||
// Find the point in which ecorr and lcorr intercept | |||||
if(fabs(lcorr - ecorr) < minDoppler && fabs(lcorr - ecorr) > 2){ | |||||
minDoppler = fabs(lcorr - ecorr); | minDoppler = fabs(lcorr - ecorr); | ||||
*zenith = nrow; | *zenith = nrow; | ||||
} | } | ||||
@@ -2,7 +2,7 @@ | |||||
23 78 37 | 23 78 37 | ||||
240 250 255 | 240 250 255 | ||||
50 | 50 | ||||
0.5 | |||||
10 | |||||
24 | 24 | ||||
34 | 34 | ||||
14 | 14 | ||||
@@ -19,106 +19,87 @@ | |||||
#include <stdio.h> | #include <stdio.h> | ||||
#include <math.h> | #include <math.h> | ||||
#include "offsets.h" | #include "offsets.h" | ||||
typedef struct { | typedef struct { | ||||
float r, g, b, a; | |||||
} rgba; | |||||
float r, g, b; | |||||
} rgb_t; | |||||
extern rgb_t RGBcomposite(rgb_t top, float top_a, rgb_t bottom, float bottom_a); | |||||
static struct { | static struct { | ||||
rgba Sea; | |||||
rgba Land; | |||||
rgba Cloud; | |||||
int Seaintensity; | |||||
float Seaoffset; | |||||
int Landthreshold; | |||||
int Landintensity; | |||||
int Landoffset; | |||||
int Cloudthreshold; | |||||
int Cloudintensity; | |||||
rgb_t Sea, Land, Cloud; | |||||
int Seaintensity, Seaoffset; | |||||
int Landthreshold, Landintensity, Landoffset; | |||||
int Cloudthreshold, Cloudintensity; | |||||
} fcinfo = { | } fcinfo = { | ||||
{28, 44, 95, 1}, | |||||
{23, 78, 37, 1}, | |||||
{240, 250, 255, 1}, | |||||
50, | |||||
0.5, | |||||
24, | |||||
34, | |||||
14, | |||||
141, | |||||
114 | |||||
{28, 44, 95}, | |||||
{23, 78, 37}, | |||||
{240, 250, 255}, | |||||
50, 10, | |||||
24, 34, 14, | |||||
141, 114 | |||||
}; | }; | ||||
// Read the config file | // Read the config file | ||||
void readfcconf(char *file) { | |||||
int readfcconf(char *file) { | |||||
FILE *fin; | FILE *fin; | ||||
fin = fopen(file, "r"); | fin = fopen(file, "r"); | ||||
if (fin == NULL) | if (fin == NULL) | ||||
return; | |||||
return 0; | |||||
fscanf(fin, "%g %g %g\n", &fcinfo.Sea.r, &fcinfo.Sea.g, &fcinfo.Sea.b); | fscanf(fin, "%g %g %g\n", &fcinfo.Sea.r, &fcinfo.Sea.g, &fcinfo.Sea.b); | ||||
fscanf(fin, "%g %g %g\n", &fcinfo.Land.r, &fcinfo.Land.g, &fcinfo.Land.b); | fscanf(fin, "%g %g %g\n", &fcinfo.Land.r, &fcinfo.Land.g, &fcinfo.Land.b); | ||||
fscanf(fin, "%g %g %g\n", &fcinfo.Cloud.r, &fcinfo.Cloud.g, &fcinfo.Cloud.b); | fscanf(fin, "%g %g %g\n", &fcinfo.Cloud.r, &fcinfo.Cloud.g, &fcinfo.Cloud.b); | ||||
fscanf(fin, "%d\n", &fcinfo.Seaintensity); | fscanf(fin, "%d\n", &fcinfo.Seaintensity); | ||||
fscanf(fin, "%g\n", &fcinfo.Seaoffset); | |||||
fscanf(fin, "%d\n", &fcinfo.Seaoffset); | |||||
fscanf(fin, "%d\n", &fcinfo.Landthreshold); | fscanf(fin, "%d\n", &fcinfo.Landthreshold); | ||||
fscanf(fin, "%d\n", &fcinfo.Landintensity); | fscanf(fin, "%d\n", &fcinfo.Landintensity); | ||||
fscanf(fin, "%d\n", &fcinfo.Landoffset); | fscanf(fin, "%d\n", &fcinfo.Landoffset); | ||||
fscanf(fin, "%d\n", &fcinfo.Cloudthreshold); | fscanf(fin, "%d\n", &fcinfo.Cloudthreshold); | ||||
fscanf(fin, "%d", &fcinfo.Cloudintensity); | fscanf(fin, "%d", &fcinfo.Cloudintensity); | ||||
fclose(fin); | fclose(fin); | ||||
}; | |||||
// RGBA Composite | |||||
rgba composite(rgba top, rgba bottom){ | |||||
rgba composite; | |||||
composite.r = MCOMPOSITE(top.r, top.a, bottom.r, bottom.a); | |||||
composite.g = MCOMPOSITE(top.g, top.a, bottom.g, bottom.a); | |||||
composite.b = MCOMPOSITE(top.b, top.a, bottom.b, bottom.a); | |||||
composite.a = bottom.a == 1 || top.a == 1 ? 1 : (top.a+bottom.a)/2; | |||||
return composite; | |||||
} | |||||
return 1; | |||||
}; | |||||
void falsecolor(float vis, float temp, float *r, float *g, float *b){ | |||||
rgba buffer; | |||||
fcinfo.Land.a = 0.0f; fcinfo.Sea.a = 0.0f; | |||||
rgb_t falsecolor(float vis, float temp, float *r, float *g, float *b){ | |||||
rgb_t buffer; | |||||
float land = 0, sea, cloud; | |||||
// Calculate intensity of sea and land | |||||
fcinfo.Sea.a = CLIP(vis, 0, 20)/fcinfo.Seaintensity + fcinfo.Seaoffset; | |||||
if(vis > fcinfo.Landthreshold) fcinfo.Land.a = CLIP(vis-fcinfo.Landoffset, 0, fcinfo.Landintensity)/fcinfo.Landintensity; | |||||
// Calculate intensity of sea | |||||
sea = CLIP(vis+fcinfo.Seaoffset, 0, fcinfo.Seaintensity)/fcinfo.Seaintensity; | |||||
// Composite land on top of sea | |||||
buffer = composite(fcinfo.Land, fcinfo.Sea); | |||||
buffer.a = 1.0f; | |||||
// Land | |||||
if(vis > fcinfo.Landthreshold) | |||||
land = CLIP(vis+fcinfo.Landoffset, 0, fcinfo.Landintensity)/fcinfo.Landintensity; | |||||
// Composite land on sea | |||||
buffer = RGBcomposite(fcinfo.Land, land, fcinfo.Sea, sea); | |||||
// Composite clouds on top | // Composite clouds on top | ||||
fcinfo.Cloud.a = CLIP(temp-fcinfo.Cloudthreshold, 0, fcinfo.Cloudintensity)/fcinfo.Cloudintensity; | |||||
buffer = composite(fcinfo.Cloud, buffer); | |||||
cloud = CLIP(temp-fcinfo.Cloudthreshold, 0, fcinfo.Cloudintensity)/fcinfo.Cloudintensity; | |||||
buffer = RGBcomposite(fcinfo.Cloud, cloud, buffer, 1); | |||||
*r = buffer.r; | |||||
*g = buffer.g; | |||||
*b = buffer.b; | |||||
return buffer; | |||||
} | } | ||||
// GVI (global vegetation index) false color | // GVI (global vegetation index) false color | ||||
void Ngvi(float **prow, int nrow) { | void Ngvi(float **prow, int nrow) { | ||||
printf("Computing GVI false color\n"); | |||||
fflush(stdout); | |||||
printf("Computing GVI false color"); | |||||
for (int n = 0; n < nrow; n++) { | for (int n = 0; n < nrow; n++) { | ||||
float *pixelv = prow[n]; | float *pixelv = prow[n]; | ||||
for (int i = 0; i < CH_WIDTH; i++) { | |||||
float pv; | |||||
double gvi; | |||||
gvi = (pixelv[i + CHA_OFFSET] - pixelv[i + CHB_OFFSET]) / (pixelv[i + CHA_OFFSET] + pixelv[i + CHB_OFFSET]); | |||||
pv = (gvi + 0.1) * 340.0; | |||||
pv = CLIP(pv, 0, 255); | |||||
for (int i = 0; i < CH_WIDTH; i++) { | |||||
double gvi = (pixelv[i + CHA_OFFSET] - pixelv[i + CHB_OFFSET])/ | |||||
(pixelv[i + CHA_OFFSET] + pixelv[i + CHB_OFFSET]); | |||||
pixelv[i + CHB_OFFSET] = pv; | |||||
gvi = (gvi + 0.1) * 340.0; | |||||
pixelv[i + CHB_OFFSET] = CLIP(gvi, 0, 255); | |||||
} | } | ||||
} | } | ||||
printf("\nDone\n"); | |||||
}; | }; |
@@ -21,6 +21,7 @@ | |||||
#include <string.h> | #include <string.h> | ||||
#include <sndfile.h> | #include <sndfile.h> | ||||
#include <math.h> | #include <math.h> | ||||
#include <stdlib.h> | |||||
#include "offsets.h" | #include "offsets.h" | ||||
#include "messages.h" | #include "messages.h" | ||||
@@ -28,12 +29,27 @@ | |||||
#define REGORDER 3 | #define REGORDER 3 | ||||
typedef struct { | typedef struct { | ||||
double cf[REGORDER + 1]; | double cf[REGORDER + 1]; | ||||
} rgparam; | |||||
} rgparam_t; | |||||
typedef struct { | |||||
float *prow[3000]; // Row buffers | |||||
int nrow; // Number of rows | |||||
int chA, chB; // ID of each channel | |||||
char name[256]; // Stripped filename | |||||
} image_t; | |||||
typedef struct { | |||||
char *type; // Output image type | |||||
char *effects; | |||||
int satnum; // The satellite number | |||||
char *map; // Path to a map file | |||||
char *path; // Output directory | |||||
} options_t; | |||||
extern void polyreg(const int m, const int n, const double x[], const double y[], double c[]); | extern void polyreg(const int m, const int n, const double x[], const double y[], double c[]); | ||||
// Compute regression | // Compute regression | ||||
static void rgcomp(double x[16], rgparam * rgpr) { | |||||
static void rgcomp(double x[16], rgparam_t * rgpr) { | |||||
// { 0.106, 0.215, 0.324, 0.433, 0.542, 0.652, 0.78, 0.87, 0.0 } | // { 0.106, 0.215, 0.324, 0.433, 0.542, 0.652, 0.78, 0.87, 0.0 } | ||||
const double y[9] = { 31.07, 63.02, 94.96, 126.9, 158.86, 191.1, 228.62, 255.0, 0.0 }; | const double y[9] = { 31.07, 63.02, 94.96, 126.9, 158.86, 191.1, 228.62, 255.0, 0.0 }; | ||||
@@ -41,7 +57,7 @@ static void rgcomp(double x[16], rgparam * rgpr) { | |||||
} | } | ||||
// Convert a value to 0-255 based off the provided regression curve | // Convert a value to 0-255 based off the provided regression curve | ||||
static double rgcal(float x, rgparam *rgpr) { | |||||
static double rgcal(float x, rgparam_t *rgpr) { | |||||
double y, p; | double y, p; | ||||
int i; | int i; | ||||
@@ -66,13 +82,14 @@ void histogramEqualise(float **prow, int nrow, int offset, int width){ | |||||
// Find min/max points | // Find min/max points | ||||
int min = -1, max = -1; | int min = -1, max = -1; | ||||
for(int i = 5; i < 250; i++){ | for(int i = 5; i < 250; i++){ | ||||
if(histogram[i]/width/(nrow/255.0) > 1.0){ | |||||
if(min == -1) min = i; | |||||
if(histogram[i]/width/(nrow/255.0) > 0.2){ | |||||
if(min == -1) | |||||
min = i; | |||||
max = i; | max = i; | ||||
} | } | ||||
} | } | ||||
//printf("Min Value: %i, Max Value %i\n", min, max); | |||||
//printf("Column %i-%i: Min: %i, Max %i\n", offset, offset+width, min, max); | |||||
// Spread values to avoid overshoot | // Spread values to avoid overshoot | ||||
min -= 5; max += 5; | min -= 5; max += 5; | ||||
@@ -80,11 +97,11 @@ void histogramEqualise(float **prow, int nrow, int offset, int width){ | |||||
// Stretch the brightness into the new range | // Stretch the brightness into the new range | ||||
for(int y = 0; y < nrow; y++) | for(int y = 0; y < nrow; y++) | ||||
for(int x = 0; x < width; x++) | for(int x = 0; x < width; x++) | ||||
prow[y][x+offset] = (prow[y][x+offset]-min) / (max-min) * 255; | |||||
prow[y][x+offset] = CLIP((prow[y][x+offset]-min) / (max-min) * 255.0, 0, 255); | |||||
} | } | ||||
// Brightness calibrate, including telemetry | // Brightness calibrate, including telemetry | ||||
void calibrateBrightness(float **prow, int nrow, int offset, int width, int telestart, rgparam regr[30]){ | |||||
void calibrateBrightness(float **prow, int nrow, int offset, int width, int telestart, rgparam_t regr[30]){ | |||||
offset -= SYNC_WIDTH+SPC_WIDTH; | offset -= SYNC_WIDTH+SPC_WIDTH; | ||||
for (int n = 0; n < nrow; n++) { | for (int n = 0; n < nrow; n++) { | ||||
@@ -94,19 +111,19 @@ void calibrateBrightness(float **prow, int nrow, int offset, int width, int tele | |||||
float pv = pixelv[i + offset]; | float pv = pixelv[i + offset]; | ||||
// Blend between the calculated regression curves | // Blend between the calculated regression curves | ||||
/* TODO: this can actually make the image look *worse* | |||||
/* FIXME: this can actually make the image look *worse* | |||||
* if the signal has a constant input gain. | * if the signal has a constant input gain. | ||||
*/ | */ | ||||
int k, kof; | |||||
/*int k, kof; | |||||
k = (n - telestart) / FRAME_LEN; | k = (n - telestart) / FRAME_LEN; | ||||
if (k >= nbtele) | if (k >= nbtele) | ||||
k = nbtele - 1; | k = nbtele - 1; | ||||
kof = (n - telestart) % FRAME_LEN; | kof = (n - telestart) % FRAME_LEN; | ||||
if (kof < 64) { | if (kof < 64) { | ||||
if (k < 1) { | |||||
pv = rgcal(pv, &(regr[k])); | |||||
} else { | |||||
if (k < 1) {*/ | |||||
pv = rgcal(pv, &(regr[4])); | |||||
/*} else { | |||||
pv = rgcal(pv, &(regr[k])) * (64 + kof) / FRAME_LEN + | pv = rgcal(pv, &(regr[k])) * (64 + kof) / FRAME_LEN + | ||||
rgcal(pv, &(regr[k - 1])) * (64 - kof) / FRAME_LEN; | rgcal(pv, &(regr[k - 1])) * (64 - kof) / FRAME_LEN; | ||||
} | } | ||||
@@ -118,7 +135,7 @@ void calibrateBrightness(float **prow, int nrow, int offset, int width, int tele | |||||
rgcal(pv, &(regr[k + 1])) * (kof - 64) / FRAME_LEN; | rgcal(pv, &(regr[k + 1])) * (kof - 64) / FRAME_LEN; | ||||
} | } | ||||
} | } | ||||
*/ | |||||
pv = CLIP(pv, 0, 255); | pv = CLIP(pv, 0, 255); | ||||
pixelv[i + offset] = pv; | pixelv[i + offset] = pv; | ||||
} | } | ||||
@@ -129,7 +146,7 @@ void calibrateBrightness(float **prow, int nrow, int offset, int width, int tele | |||||
int calibrate(float **prow, int nrow, int offset, int width) { | int calibrate(float **prow, int nrow, int offset, int width) { | ||||
double teleline[3000] = { 0.0 }; | double teleline[3000] = { 0.0 }; | ||||
double wedge[16]; | double wedge[16]; | ||||
rgparam regr[30]; | |||||
rgparam_t regr[30]; | |||||
int n, k; | int n, k; | ||||
int mtelestart = 0, telestart; | int mtelestart = 0, telestart; | ||||
int channel = -1; | int channel = -1; | ||||
@@ -204,13 +221,13 @@ int calibrate(float **prow, int nrow, int offset, int width) { | |||||
* wedges, the wedge with the closest match will | * wedges, the wedge with the closest match will | ||||
* be the channel ID | * be the channel ID | ||||
*/ | */ | ||||
float min = 10000; | |||||
float min = -1; | |||||
for (j = 0; j < 6; j++) { | for (j = 0; j < 6; j++) { | ||||
float df; | float df; | ||||
df = tele[15] - tele[j]; | df = tele[15] - tele[j]; | ||||
df *= df; | df *= df; | ||||
if (df < min) { | |||||
if (df < min || min == -1) { | |||||
channel = j; | channel = j; | ||||
min = df; | min = df; | ||||
} | } | ||||
@@ -242,7 +259,6 @@ int calibrate(float **prow, int nrow, int offset, int width) { | |||||
} | } | ||||
// --- Temperature Calibration --- // | // --- Temperature Calibration --- // | ||||
extern int satnum; | |||||
#include "satcal.h" | #include "satcal.h" | ||||
typedef struct { | typedef struct { | ||||
@@ -250,10 +266,10 @@ typedef struct { | |||||
double Cs; | double Cs; | ||||
double Cb; | double Cb; | ||||
int ch; | int ch; | ||||
} tempparam; | |||||
} tempparam_t; | |||||
// IR channel temperature compensation | // IR channel temperature compensation | ||||
static void tempcomp(double t[16], int ch, tempparam *tpr) { | |||||
static void tempcomp(double t[16], int ch, int satnum, tempparam_t *tpr) { | |||||
double Tbb, T[4]; | double Tbb, T[4]; | ||||
double C; | double C; | ||||
@@ -285,7 +301,7 @@ static void tempcomp(double t[16], int ch, tempparam *tpr) { | |||||
} | } | ||||
// IR channel temperature calibration | // IR channel temperature calibration | ||||
static double tempcal(float Ce, tempparam * rgpr) { | |||||
static double tempcal(float Ce, int satnum, tempparam_t * rgpr) { | |||||
double Nl, Nc, Ns, Ne; | double Nl, Nc, Ns, Ne; | ||||
double T, vc; | double T, vc; | ||||
@@ -308,23 +324,82 @@ static double tempcal(float Ce, tempparam * rgpr) { | |||||
} | } | ||||
// Temperature calibration wrapper | // Temperature calibration wrapper | ||||
void temperature(float **prow, int nrow, int ch, int offset){ | |||||
tempparam temp; | |||||
void temperature(options_t *opts, image_t *img, int offset, int width){ | |||||
tempparam_t temp; | |||||
printf("Temperature... "); | printf("Temperature... "); | ||||
fflush(stdout); | fflush(stdout); | ||||
tempcomp(tele, ch, &temp); | |||||
for (int n = 0; n < nrow; n++) { | |||||
float *pixelv = prow[n]; | |||||
tempcomp(tele, img->chB, opts->satnum - 15, &temp); | |||||
for (int i = 0; i < CH_WIDTH; i++) { | |||||
float pv = tempcal(pixelv[i + offset], &temp); | |||||
for (int y = 0; y < img->nrow; y++) { | |||||
float *pixelv = img->prow[y]; | |||||
pv = CLIP(pv, 0, 255); | |||||
pixelv[i + offset] = pv; | |||||
for (int x = 0; x < width; x++) { | |||||
float pv = tempcal(pixelv[x + offset], opts->satnum - 15, &temp); | |||||
pixelv[x + offset] = CLIP(pv, 0, 255); | |||||
} | } | ||||
} | } | ||||
printf("Done\n"); | printf("Done\n"); | ||||
} | } | ||||
void distrib(options_t *opts, image_t *img, char *chid) { | |||||
int max = 0; | |||||
// Options | |||||
options_t options; | |||||
options.path = opts->path; | |||||
// Image options | |||||
image_t distrib; | |||||
strcpy(distrib.name, img->name); | |||||
distrib.nrow = 256; | |||||
// Assign memory | |||||
for(int i = 0; i < 256; i++) | |||||
distrib.prow[i] = (float *) malloc(sizeof(float) * 256); | |||||
for(int n = 0; n < img->nrow; n++) { | |||||
float *pixelv = img->prow[n]; | |||||
for(int i = 0; i < CH_WIDTH; i++) { | |||||
int y = CLIP((int)pixelv[i + CHA_OFFSET], 0, 255); | |||||
int x = CLIP((int)pixelv[i + CHB_OFFSET], 0, 255); | |||||
distrib.prow[y][x]++; | |||||
if(distrib.prow[y][x] > max) | |||||
max = distrib.prow[y][x]; | |||||
} | |||||
} | |||||
// Scale to 0-255 | |||||
for(int x = 0; x < 256; x++) | |||||
for(int y = 0; y < 256; y++) | |||||
distrib.prow[y][x] = distrib.prow[y][x] / max * 255.0; | |||||
extern int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, char *chid, char *palette); | |||||
ImageOut(&options, &distrib, 0, 256, "Distribution", chid, NULL); | |||||
} | |||||
extern float quick_select(float arr[], int n); | |||||
// Recursive biased median denoise | |||||
#define TRIG_LEVEL 40 | |||||
void denoise(float **prow, int nrow, int offset, int width){ | |||||
for(int y = 2; y < nrow-2; y++){ | |||||
for(int x = offset+1; x < offset+width-1; x++){ | |||||
if(prow[y][x+1] - prow[y][x] > TRIG_LEVEL || | |||||
prow[y][x-1] - prow[y][x] > TRIG_LEVEL || | |||||
prow[y+1][x] - prow[y][x] > TRIG_LEVEL || | |||||
prow[y-1][x] - prow[y][x] > TRIG_LEVEL){ | |||||
prow[y][x] = quick_select((float[]){ | |||||
prow[y+2][x-1], prow[y+2][x], prow[y+2][x+1], | |||||
prow[y+1][x-1], prow[y+1][x], prow[y+1][x+1], | |||||
prow[y-1][x-1], prow[y-1][x], prow[y-1][x+1], | |||||
prow[y-2][x-1], prow[y-2][x], prow[y-2][x+1] | |||||
}, 12); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
#undef TRIG_LEVEL |
@@ -24,321 +24,286 @@ | |||||
#include <libgen.h> | #include <libgen.h> | ||||
#include <math.h> | #include <math.h> | ||||
#include <sndfile.h> | #include <sndfile.h> | ||||
#include <png.h> | |||||
#include <errno.h> | |||||
#include "messages.h" | #include "messages.h" | ||||
#include "offsets.h" | #include "offsets.h" | ||||
#include "palette.h" | |||||
#define FAILURE 0 | |||||
#define SUCCESS 1 | |||||
typedef struct { | |||||
char *type; // Output image type | |||||
char *effects; | |||||
int satnum; // The satellite number | |||||
char *map; // Path to a map file | |||||
char *path; // Output directory | |||||
} options_t; | |||||
typedef struct { | |||||
float *prow[3000]; // Row buffers | |||||
int nrow; // Number of rows | |||||
int chA, chB; // ID of each channel | |||||
char name[256]; // Stripped filename | |||||
} image_t; | |||||
// DSP | |||||
extern int getpixelrow(float *pixelv, int nrow, int *zenith); | extern int getpixelrow(float *pixelv, int nrow, int *zenith); | ||||
extern int init_dsp(double F); | extern int init_dsp(double F); | ||||
static SNDFILE *inwav; | |||||
// I/O | |||||
extern int readfcconf(char *file); | |||||
extern int readRawImage(char *filename, float **prow, int *nrow); | |||||
extern int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, char *chid, char *palette); | |||||
static int initsnd(char *filename) { | |||||
SF_INFO infwav; | |||||
int res; | |||||
// Image functions | |||||
extern int calibrate(float **prow, int nrow, int offset, int width); | |||||
extern void histogramEqualise(float **prow, int nrow, int offset, int width); | |||||
extern void temperature(options_t *opts, image_t *img, int offset, int width); | |||||
extern int Ngvi(float **prow, int nrow); | |||||
extern void denoise(float **prow, int nrow, int offset, int width); | |||||
extern void distrib(options_t *opts, image_t *img, char *chid); | |||||
// Open audio file | |||||
infwav.format = 0; | |||||
inwav = sf_open(filename, SFM_READ, &infwav); | |||||
if (inwav == NULL) { | |||||
fprintf(stderr, ERR_FILE_READ, filename); | |||||
return(0); | |||||
} | |||||
// Palettes | |||||
extern char GviPalette[256*3]; | |||||
extern char TempPalette[256*3]; | |||||
res = init_dsp(infwav.samplerate); | |||||
printf("Input file: %s\n", filename); | |||||
if(res < 0) { | |||||
fprintf(stderr, "Input sample rate too low: %d\n", infwav.samplerate); | |||||
return(0); | |||||
}else if(res > 0) { | |||||
fprintf(stderr, "Input sample rate too high: %d\n", infwav.samplerate); | |||||
return(0); | |||||
} | |||||
printf("Input sample rate: %d\n", infwav.samplerate); | |||||
// Row where the satellite is closest to the observer | |||||
int zenith = 0; | |||||
// Audio file | |||||
static SNDFILE *audioFile; | |||||
if (infwav.channels != 1) { | |||||
fprintf(stderr, "Too many channels in input file: %d\n", infwav.channels); | |||||
return(0); | |||||
} | |||||
static int initsnd(char *filename); | |||||
int getsample(float *sample, int nb); | |||||
static int processAudio(char *filename, options_t *opts); | |||||
static void usage(void); | |||||
return(1); | |||||
} | |||||
int main(int argc, char **argv) { | |||||
fprintf(stderr, VERSION"\n"); | |||||
// Get samples from the wave file | |||||
int getsample(float *sample, int nb) { | |||||
return sf_read_float(inwav, sample, nb); | |||||
} | |||||
if(argc == 1) | |||||
usage(); | |||||
static png_text text_ptr[] = { | |||||
{PNG_TEXT_COMPRESSION_NONE, "Software", VERSION}, | |||||
{PNG_TEXT_COMPRESSION_NONE, "Channel", NULL, 0}, | |||||
{PNG_TEXT_COMPRESSION_NONE, "Description", "NOAA satellite image", 20} | |||||
}; | |||||
int mapOverlay(char *filename, float **crow, int nrow, int zenith, int MCIR) { | |||||
FILE *fp = fopen(filename, "rb"); | |||||
// 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 != 1040){ | |||||
fprintf(stderr, "Map must be 1040px wide.\n"); | |||||
return 0; | |||||
}else if(bit_depth != 16){ | |||||
fprintf(stderr, "Map must be 16 bit color.\n"); | |||||
return 0; | |||||
}else if(color_type != PNG_COLOR_TYPE_RGB){ | |||||
fprintf(stderr, "Map must be RGB.\n"); | |||||
return 0; | |||||
}else if(zenith > height/2 || nrow-zenith > height/2){ | |||||
fprintf(stderr, "WARNING: Map is too short to cover entire image\n"); | |||||
// Check if there are actually any input files | |||||
if(optind == argc){ | |||||
fprintf(stderr, "No input files provided.\n"); | |||||
usage(); | |||||
} | } | ||||
// Create row buffers | |||||
png_bytep *mapRows = NULL; | |||||
mapRows = (png_bytep *) malloc(sizeof(png_bytep) * height); | |||||
for(int y = 0; y < height; y++) mapRows[y] = (png_byte *) malloc(png_get_rowbytes(png, info)); | |||||
// Read image | |||||
png_read_image(png, mapRows); | |||||
// Tidy up | |||||
fclose(fp); | |||||
png_destroy_read_struct(&png, &info, NULL); | |||||
int mapOffset = (height/2)-zenith; | |||||
for(int y = 0; y < nrow; y++) { | |||||
for(int x = 49; x < width - 82; x++){ | |||||
// Maps are 16 bit / channel | |||||
png_bytep px = &mapRows[CLIP(y + mapOffset, 0, height)][x * 6]; | |||||
uint16_t r = (uint16_t)(px[0] << 8) | px[1]; | |||||
uint16_t g = (uint16_t)(px[2] << 8) | px[3]; | |||||
uint16_t b = (uint16_t)(px[4] << 8) | px[5]; | |||||
// Pointers to the current pixel in each channel | |||||
float *cha = &crow[y][(x+36) * 3]; | |||||
float *chb = &crow[y][(x+CHB_OFFSET-49) * 3]; | |||||
// Fill in map | |||||
if(MCIR){ | |||||
// Sea | |||||
cha[0] = 42; cha[1] = 53; cha[2] = 105; | |||||
// Land | |||||
if(g > 128){ | |||||
float vegetation = (r-160) / 96.0; | |||||
cha[0] = 120; cha[1] = vegetation*60.0 + 100; cha[2] = 95; | |||||
options_t opts = { "r", "", 19, "", "." }; | |||||
// Parse arguments | |||||
int opt; | |||||
while ((opt = getopt(argc, argv, "c:m:d:i:s:e:")) != EOF) { | |||||
switch (opt) { | |||||
case 'd': | |||||
opts.path = optarg; | |||||
break; | |||||
case 'c': | |||||
readfcconf(optarg); | |||||
break; | |||||
case 'm': | |||||
opts.map = optarg; | |||||
break; | |||||
case 'i': | |||||
opts.type = optarg; | |||||
break; | |||||
case 's': | |||||
opts.satnum = atoi(optarg); | |||||
if(opts.satnum < 15 || opts.satnum > 19){ | |||||
fprintf(stderr, "Invalid satellite number, it must be the range 15-19\n"); | |||||
exit(EPERM); | |||||
} | } | ||||
} | |||||
// Color -> alpha: composite | |||||
int composite = r + g + b; | |||||
// Color -> alpha: flattern color depth | |||||
float factor = (255 * 255 * 2.0) / composite; | |||||
r *= factor; g *= factor; b *= factor; | |||||
// Color -> alpha: convert black to alpha | |||||
float alpha = CLIP(abs(0 - composite) / 65535.0, 0, 1); | |||||
// Map overlay on channel A | |||||
cha[0] = MCOMPOSITE(r/257, alpha, cha[0], 1); | |||||
cha[1] = MCOMPOSITE(g/257, alpha, cha[1], 1); | |||||
cha[2] = MCOMPOSITE(b/257, alpha, cha[2], 1); | |||||
// Map overlay on channel B | |||||
if(!MCIR){ | |||||
chb[0] = MCOMPOSITE(r/257, alpha, chb[0], 1); | |||||
chb[1] = MCOMPOSITE(g/257, alpha, chb[1], 1); | |||||
chb[2] = MCOMPOSITE(b/257, alpha, chb[2], 1); | |||||
} | |||||
// Cloud overlay on channel A | |||||
if(MCIR){ | |||||
float cloud = (chb[0] - 110) / 120; | |||||
cha[0] = MCOMPOSITE(240, cloud, cha[0], 1); | |||||
cha[1] = MCOMPOSITE(250, cloud, cha[1], 1); | |||||
cha[2] = MCOMPOSITE(255, cloud, cha[2], 1); | |||||
} | |||||
break; | |||||
case 'e': | |||||
opts.effects = optarg; | |||||
break; | |||||
default: | |||||
usage(); | |||||
} | } | ||||
} | |||||
// Process the files | |||||
for (; optind < argc; optind++) { | |||||
processAudio(argv[optind], &opts); | |||||
} | } | ||||
return 1; | |||||
exit(0); | |||||
} | } | ||||
// Row where to satellite reaches peak elevation | |||||
int zenith = 0; | |||||
static int processAudio(char *filename, options_t *opts){ | |||||
// Image info struct | |||||
image_t img; | |||||
int applyPalette(float **crow, int nrow, int width, char *palette){ | |||||
if(palette == NULL) return 0; | |||||
// Mapping between wedge value and channel ID | |||||
static struct { | |||||
char *id[7]; | |||||
char *name[7]; | |||||
} ch = { | |||||
{ "?", "1", "2", "3A", "4", "5", "3B" }, | |||||
{ "unknown", "visble", "near-infrared", "mid-infrared", "thermal-infrared", "thermal-infrared", "mid-infrared" } | |||||
}; | |||||
for(int y = 0; y < nrow; y++){ | |||||
for(int x = 0; x < width; x++){ | |||||
float *px = &crow[y][x * 3]; | |||||
px[0] = palette[(int)CLIP(px[0], 0, 255)*3 + 0]; | |||||
px[1] = palette[(int)CLIP(px[1], 0, 255)*3 + 1]; | |||||
px[2] = palette[(int)CLIP(px[2], 0, 255)*3 + 2]; | |||||
// Buffer for image channel | |||||
char desc[60]; | |||||
// Parse file path | |||||
char path[256], extension[32]; | |||||
strcpy(path, filename); | |||||
strcpy(path, dirname(path)); | |||||
sscanf(basename(filename), "%[^.].%s", img.name, extension); | |||||
if(strcmp(extension, "png") == 0){ | |||||
// Read PNG into image buffer | |||||
printf("Reading %s", filename); | |||||
if(readRawImage(filename, img.prow, &img.nrow) == 0){ | |||||
fprintf(stderr, "Skipping %s; see above.\n", img.name); | |||||
return FAILURE; | |||||
} | } | ||||
} | |||||
return 1; | |||||
} | |||||
}else{ | |||||
// Attempt to open the audio file | |||||
if (initsnd(filename) == 0) | |||||
exit(EPERM); | |||||
// Build image | |||||
for (img.nrow = 0; img.nrow < 3000; img.nrow++) { | |||||
// Allocate memory for this row | |||||
img.prow[img.nrow] = (float *) malloc(sizeof(float) * 2150); | |||||
// Write into memory and break the loop when there are no more samples to read | |||||
if (getpixelrow(img.prow[img.nrow], img.nrow, &zenith) == 0) | |||||
break; | |||||
static int ImageOut(char *filename, char *chid, float **prow, int nrow, int width, int offset, char *palette, char *effects, char *mapFile) { | |||||
FILE *pngfile; | |||||
fprintf(stderr, "Row: %d\r", img.nrow); | |||||
fflush(stderr); | |||||
} | |||||
// Reduce the width of the image to componsate for the missing telemetry | |||||
if(CONTAINS(effects, 't')) width -= TOTAL_TELE; | |||||
// Close stream | |||||
sf_close(audioFile); | |||||
} | |||||
// Create writer | |||||
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); | |||||
if (!png_ptr) { | |||||
png_destroy_write_struct(&png_ptr, (png_infopp) NULL); | |||||
fprintf(stderr, ERR_PNG_WRITE); | |||||
return(0); | |||||
} | |||||
png_infop info_ptr = png_create_info_struct(png_ptr); | |||||
if (!info_ptr) { | |||||
png_destroy_write_struct(&png_ptr, (png_infopp) NULL); | |||||
fprintf(stderr, ERR_PNG_INFO); | |||||
return(0); | |||||
} | |||||
printf("\nTotal rows: %d\n", img.nrow); | |||||
// 8 bit RGB image | |||||
png_set_IHDR(png_ptr, info_ptr, width, nrow, | |||||
8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, | |||||
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); | |||||
text_ptr[1].text = chid; | |||||
text_ptr[1].text_length = strlen(chid); | |||||
png_set_text(png_ptr, info_ptr, text_ptr, 3); | |||||
png_set_pHYs(png_ptr, info_ptr, 4160, 4160, PNG_RESOLUTION_METER); | |||||
// Init I/O | |||||
pngfile = fopen(filename, "wb"); | |||||
if (!pngfile) { | |||||
fprintf(stderr, ERR_FILE_WRITE, filename); | |||||
return(1); | |||||
} | |||||
png_init_io(png_ptr, pngfile); | |||||
png_write_info(png_ptr, info_ptr); | |||||
// Fallback for detecting the zenith | |||||
// TODO: encode zenith in raw images | |||||
if(opts->map != NULL && opts->map[0] != '\0' && zenith == 0){ | |||||
fprintf(stderr, "Guessing zenith in image, map will most likely be misaligned.\n"); | |||||
zenith = img.nrow / 2; | |||||
} | |||||
// Move prow into crow, crow ~ color rows | |||||
float *crow[3000]; | |||||
for(int i = 0; i < nrow; i++){ | |||||
crow[i] = (float *) malloc(sizeof(float) * 2080 * 3); | |||||
// Calibrate | |||||
img.chA = calibrate(img.prow, img.nrow, CHA_OFFSET, CH_WIDTH); | |||||
img.chB = calibrate(img.prow, img.nrow, CHB_OFFSET, CH_WIDTH); | |||||
printf("Channel A: %s (%s)\n", ch.id[img.chA], ch.name[img.chA]); | |||||
printf("Channel B: %s (%s)\n", ch.id[img.chB], ch.name[img.chB]); | |||||
for(int x = 0; x < 2080; x++) | |||||
crow[i][x*3] = crow[i][x*3 + 1] = crow[i][x*3 + 2] = prow[i][x]; | |||||
// Denoise | |||||
if(CONTAINS(opts->effects, 'd')){ | |||||
denoise(img.prow, img.nrow, CHA_OFFSET, CH_WIDTH); | |||||
denoise(img.prow, img.nrow, CHB_OFFSET, CH_WIDTH); | |||||
} | } | ||||
applyPalette(crow, nrow, 2080, palette); | |||||
// Temperature | |||||
if (CONTAINS(opts->type, 't') && img.chB >= 4) { | |||||
temperature(opts, &img, CHB_OFFSET, CH_WIDTH); | |||||
ImageOut(opts, &img, CHB_OFFSET, CH_WIDTH, "Temperature", "t", (char *)TempPalette); | |||||
} | |||||
if(mapFile != NULL && mapFile[0] != '\0'){ | |||||
if(mapOverlay(mapFile, crow, nrow, zenith, strcmp(chid, "MCIR") == 0) == 0){ | |||||
fprintf(stderr, "Skipping MCIR generation; see above.\n"); | |||||
return 0; | |||||
// False color image | |||||
if(CONTAINS(opts->type, 'c')){ | |||||
if (img.chA == 2 && img.chB >= 4) { // Normal false color | |||||
// TODO: use real MSA | |||||
// TODO: provide more than just "natural" color images | |||||
ImageOut(opts, &img, 0, CH_WIDTH, "False Color", "c", NULL); | |||||
} else if (img.chB == 2) { // GVI (global vegetation index) false color | |||||
Ngvi(img.prow, img.nrow); | |||||
ImageOut(opts, &img, CHB_OFFSET, CH_WIDTH, "GVI False Color", "c", (char *)GviPalette); | |||||
} else { | |||||
fprintf(stderr, "Skipping False Color generation; lacking required channels.\n"); | |||||
} | } | ||||
}else if(strcmp(chid, "MCIR") == 0){ | |||||
fprintf(stderr, "Skipping MCIR generation; no map provided.\n"); | |||||
return 0; | |||||
} | } | ||||
extern void falsecolor(float vis, float temp, float *r, float *g, float *b); | |||||
printf("Writing %s", filename); | |||||
int fcimage = strcmp(chid, "False Color") == 0; | |||||
// Build RGB image | |||||
for (int n = 0; n < nrow; n++) { | |||||
png_color pix[width]; | |||||
for (int i = 0; i < width; i++) { | |||||
float *px = &crow[n][offset*3 + i*3]; | |||||
if(fcimage){ | |||||
float r = 0, g = 0, b = 0; | |||||
falsecolor(prow[n][i + CHA_OFFSET], prow[n][i + CHB_OFFSET], &r, &g, &b); | |||||
pix[i].red = r; | |||||
pix[i].green = g; | |||||
pix[i].blue = b; | |||||
}else{ | |||||
pix[i].red = px[0]; | |||||
pix[i].green = px[1]; | |||||
pix[i].blue = px[2]; | |||||
} | |||||
} | |||||
png_write_row(png_ptr, (png_bytep) pix); | |||||
} | |||||
// MCIR | |||||
if (CONTAINS(opts->type, 'm')) | |||||
ImageOut(opts, &img, 0, IMG_WIDTH, "MCIR", "m", NULL); | |||||
// Tidy up | |||||
png_write_end(png_ptr, info_ptr); | |||||
fclose(pngfile); | |||||
printf("\nDone\n"); | |||||
png_destroy_write_struct(&png_ptr, &info_ptr); | |||||
// Histogram equalise | |||||
if(CONTAINS(opts->effects, 'h')){ | |||||
histogramEqualise(img.prow, img.nrow, CHA_OFFSET, CH_WIDTH); | |||||
histogramEqualise(img.prow, img.nrow, CHB_OFFSET, CH_WIDTH); | |||||
} | |||||
return(1); | |||||
} | |||||
// Raw image | |||||
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]); | |||||
ImageOut(opts, &img, 0, IMG_WIDTH, desc, "r", NULL); | |||||
} | |||||
// Outputs a image with the value distribution between channel A and B | |||||
static void distrib(char *filename, float **prow, int nrow) { | |||||
float *distrib[256]; | |||||
int max = 0; | |||||
// Assign memory | |||||
for(int i = 0; i < 256; i++) | |||||
distrib[i] = (float *) malloc(sizeof(float) * 256); | |||||
for(int n = 0; n < nrow; n++) { | |||||
float *pixelv = prow[n]; | |||||
for(int i = 0; i < CH_WIDTH; i++) { | |||||
int y = (int)(pixelv[i + CHA_OFFSET]); | |||||
int x = (int)(pixelv[i + CHB_OFFSET]); | |||||
distrib[y][x] += 1; | |||||
if(distrib[y][x] > max) max = distrib[y][x]; | |||||
} | |||||
// Channel A | |||||
if (CONTAINS(opts->type, 'a')) { | |||||
sprintf(desc, "%s (%s)", ch.id[img.chA], ch.name[img.chA]); | |||||
ImageOut(opts, &img, CHA_OFFSET, CH_WIDTH, desc, ch.id[img.chA], NULL); | |||||
} | } | ||||
// Scale to 0-255 | |||||
for(int x = 0; x < 256; x++) | |||||
for(int y = 0; y < 256; y++) | |||||
distrib[y][x] = distrib[y][x] / max * 255; | |||||
// Channel B | |||||
if (CONTAINS(opts->type, 'b')) { | |||||
sprintf(desc, "%s (%s)", ch.id[img.chB], ch.name[img.chB]); | |||||
ImageOut(opts, &img, CHB_OFFSET, CH_WIDTH, desc, ch.id[img.chA], NULL); | |||||
} | |||||
ImageOut(filename, "Brightness distribution", distrib, 256, 256, 0, NULL, 0, NULL); | |||||
// Distribution image | |||||
if (CONTAINS(opts->type, 'd')) | |||||
distrib(opts, &img, "d"); | |||||
return SUCCESS; | |||||
} | } | ||||
extern int calibrate(float **prow, int nrow, int offset, int width); | |||||
extern void histogramEqualise(float **prow, int nrow, int offset, int width); | |||||
extern void temperature(float **prow, int nrow, int ch, int offset); | |||||
extern int Ngvi(float **prow, int nrow); | |||||
extern void readfcconf(char *file); | |||||
extern int optind; | |||||
extern char *optarg; | |||||
static int initsnd(char *filename) { | |||||
SF_INFO infwav; | |||||
int res; | |||||
// Default to NOAA 19 | |||||
int satnum = 4; | |||||
// Open audio file | |||||
infwav.format = 0; | |||||
audioFile = sf_open(filename, SFM_READ, &infwav); | |||||
if (audioFile == NULL) { | |||||
fprintf(stderr, ERR_FILE_READ, filename); | |||||
return FAILURE; | |||||
} | |||||
res = init_dsp(infwav.samplerate); | |||||
printf("Input file: %s\n", filename); | |||||
if(res < 0) { | |||||
fprintf(stderr, "Input sample rate too low: %d\n", infwav.samplerate); | |||||
return FAILURE; | |||||
}else if(res > 0) { | |||||
fprintf(stderr, "Input sample rate too high: %d\n", infwav.samplerate); | |||||
return FAILURE; | |||||
} | |||||
printf("Input sample rate: %d\n", infwav.samplerate); | |||||
// TODO: accept stereo audio | |||||
if (infwav.channels != 1) { | |||||
fprintf(stderr, "Too many channels in input file: %d\n", infwav.channels); | |||||
return FAILURE; | |||||
} | |||||
return SUCCESS; | |||||
} | |||||
// Read samples from the wave file | |||||
int getsample(float *sample, int nb) { | |||||
return sf_read_float(audioFile, sample, nb); | |||||
} | |||||
static void usage(void) { | static void usage(void) { | ||||
printf("Aptdec [options] audio files ...\n" | |||||
fprintf(stderr, | |||||
"Aptdec [options] audio files ...\n" | |||||
"Options:\n" | "Options:\n" | ||||
" -e [t|h] Enhancements\n" | |||||
" -e [t|h] Effects\n" | |||||
" t: Crop telemetry\n" | " t: Crop telemetry\n" | ||||
" h: Histogram equalise\n" | " h: Histogram equalise\n" | ||||
" -i [r|a|b|c|t] Output image type\n" | |||||
" d: Denoise\n" | |||||
" -i [r|a|b|c|t] Output image\n" | |||||
" r: Raw\n" | " r: Raw\n" | ||||
" a: Channel A\n" | " a: Channel A\n" | ||||
" b: Channel B\n" | " b: Channel B\n" | ||||
@@ -349,257 +314,6 @@ static void usage(void) { | |||||
" -s [15-19] Satellite number\n" | " -s [15-19] Satellite number\n" | ||||
" -c <file> False color config file\n" | " -c <file> False color config file\n" | ||||
" -m <file> Map file\n"); | " -m <file> Map file\n"); | ||||
exit(1); | |||||
} | |||||
int readRawImage(char *filename, float **prow, int *nrow) { | |||||
FILE *fp = fopen(filename, "r"); | |||||
// 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 != 2080){ | |||||
fprintf(stderr, "Raw image must be 2080px wide.\n"); | |||||
return 0; | |||||
}else if(bit_depth != 8){ | |||||
fprintf(stderr, "Raw image must have 8 bit color.\n"); | |||||
return 0; | |||||
}else if(color_type != PNG_COLOR_TYPE_GRAY){ | |||||
fprintf(stderr, "Raw image must be grayscale.\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 prow | |||||
*nrow = height; | |||||
for(int y = 0; y < height; y++) { | |||||
prow[y] = (float *) malloc(sizeof(float) * width); | |||||
for(int x = 0; x < width; x++) | |||||
prow[y][x] = (float)PNGrows[y][x]; | |||||
} | |||||
return 1; | |||||
} | |||||
int main(int argc, char **argv) { | |||||
char pngfilename[1024]; | |||||
char name[128]; | |||||
char pngdirname[128] = ""; | |||||
char mapFile[256]; | |||||
char *extension; | |||||
// Default to a raw image, with no enhancements | |||||
char imgopt[20] = "r"; | |||||
char enhancements[20] = ""; | |||||
// Image buffer | |||||
float *prow[3000]; | |||||
int nrow; | |||||
// Mapping between telemetry wedge value and channel | |||||
static struct { | |||||
char *id[7]; | |||||
char *name[7]; | |||||
} ch = { | |||||
{ "?", "1", "2", "3A", "4", "5", "3B" }, | |||||
{ "unknown", "visble", "near-infrared", "mid-infrared", "thermal-infrared", "thermal-infrared", "mid-infrared" } | |||||
}; | |||||
// The active sensor in each channel | |||||
int chA, chB; | |||||
// Print version | |||||
printf(VERSION"\n"); | |||||
// Print usage if there are no arguments | |||||
if(argc == 1) | |||||
usage(); | |||||
int c; | |||||
while ((c = getopt(argc, argv, "c:m:d:i:s:e:")) != EOF) { | |||||
switch (c) { | |||||
// Output directory name | |||||
case 'd': | |||||
strcpy(pngdirname, optarg); | |||||
break; | |||||
// False color config file | |||||
case 'c': | |||||
readfcconf(optarg); | |||||
break; | |||||
// Map file | |||||
case 'm': | |||||
strcpy(mapFile, optarg); | |||||
break; | |||||
// Output image type | |||||
case 'i': | |||||
strncpy(imgopt, optarg, 20); | |||||
break; | |||||
// Satellite number (for calibration) | |||||
case 's': | |||||
satnum = atoi(optarg)-15; | |||||
// Check if it's within the valid range | |||||
if (satnum < 0 || satnum > 4) { | |||||
fprintf(stderr, "Invalid satellite number, it must be the range [15-19]\n"); | |||||
exit(1); | |||||
} | |||||
break; | |||||
// Enchancements | |||||
case 'e': | |||||
strncpy(enhancements, optarg, 20); | |||||
break; | |||||
default: | |||||
usage(); | |||||
} | |||||
} | |||||
if(optind == argc){ | |||||
printf("No input files provided.\n"); | |||||
usage(); | |||||
} | |||||
// Process the provided files | |||||
for (; optind < argc; optind++) { | |||||
chA = chB = 0; | |||||
// Generate output name | |||||
strcpy(pngfilename, argv[optind]); | |||||
strcpy(name, basename(pngfilename)); | |||||
strtok(name, "."); | |||||
extension = strtok(NULL, "."); | |||||
if (pngdirname[0] == '\0') | |||||
strcpy(pngdirname, dirname(pngfilename)); | |||||
if(strcmp(extension, "png") == 0){ | |||||
printf("Reading %s", argv[optind]); | |||||
if(readRawImage(argv[optind], prow, &nrow) == 0){ | |||||
fprintf(stderr, "Skipping %s; see above.\n", name); | |||||
continue; | |||||
} | |||||
}else{ | |||||
// Open sound file, exit if that fails | |||||
if (initsnd(argv[optind]) == 0) exit(1); | |||||
// Main image building loop | |||||
for (nrow = 0; nrow < 3000; nrow++) { | |||||
// Allocate 2150 floats worth of memory for every line of the image | |||||
prow[nrow] = (float *) malloc(sizeof(float) * 2150); | |||||
// Read into prow and break the loop once we reach the end of the image | |||||
if (getpixelrow(prow[nrow], nrow, &zenith) == 0) break; | |||||
printf("Row: %d\r", nrow); | |||||
fflush(stdout); | |||||
} | |||||
// Close sound file | |||||
sf_close(inwav); | |||||
} | |||||
if(zenith == 0 & mapFile[0] != '\0'){ | |||||
fprintf(stderr, "WARNING: Guessing peak elevation in image, map will most likely not be aligned.\n"); | |||||
zenith = nrow / 2; | |||||
} | |||||
printf("\nTotal rows: %d\n", nrow); | |||||
// Calibrate | |||||
chA = calibrate(prow, nrow, CHA_OFFSET, CH_WIDTH); | |||||
chB = calibrate(prow, nrow, CHB_OFFSET, CH_WIDTH); | |||||
printf("Channel A: %s (%s)\n", ch.id[chA], ch.name[chA]); | |||||
printf("Channel B: %s (%s)\n", ch.id[chB], ch.name[chB]); | |||||
// Temperature | |||||
if (CONTAINS(imgopt, 't') && chB >= 4) { | |||||
// TODO: Doesn't work with channel 4 | |||||
temperature(prow, nrow, chB, CHB_OFFSET); | |||||
sprintf(pngfilename, "%s/%s-t.png", pngdirname, name); | |||||
ImageOut(pngfilename, "Temperature", prow, nrow, 909, CHB_OFFSET, (char *)TempPalette, enhancements, mapFile); | |||||
} | |||||
// False color image | |||||
if(CONTAINS(imgopt, 'c')){ | |||||
if (chA == 2 && chB >= 4) { // Normal false color | |||||
sprintf(pngfilename, "%s/%s-c.png", pngdirname, name); | |||||
//ImageRGBOut(pngfilename, prow, nrow); | |||||
ImageOut(pngfilename, "False Color", prow, nrow, CH_WIDTH, 0, NULL, enhancements, mapFile); | |||||
} else if (chB == 2) { // GVI (global vegetation index) false color | |||||
Ngvi(prow, nrow); | |||||
sprintf(pngfilename, "%s/%s-c.png", pngdirname, name); | |||||
ImageOut(pngfilename, "GVI False Color", prow, nrow, CH_WIDTH, CHB_OFFSET, (char *)GviPalette, enhancements, mapFile); | |||||
} else { | |||||
fprintf(stderr, "Skipping False Color generation; lacking required channels.\n"); | |||||
} | |||||
} | |||||
// MCIR | |||||
if (CONTAINS(imgopt, 'm')) { | |||||
sprintf(pngfilename, "%s/%s-m.png", pngdirname, name); | |||||
ImageOut(pngfilename, "MCIR", prow, nrow, 909, CHA_OFFSET, NULL, enhancements, mapFile); | |||||
} | |||||
// Histogram equalise | |||||
if(CONTAINS(enhancements, 'h')){ | |||||
histogramEqualise(prow, nrow, CHA_OFFSET, CH_WIDTH); | |||||
histogramEqualise(prow, nrow, CHB_OFFSET, CH_WIDTH); | |||||
} | |||||
// Raw image | |||||
if (CONTAINS(imgopt, 'r')) { | |||||
char channelstr[45]; | |||||
sprintf(channelstr, "%s (%s) & %s (%s)", ch.id[chA], ch.name[chA], ch.id[chB], ch.name[chB]); | |||||
sprintf(pngfilename, "%s/%s-r.png", pngdirname, name); | |||||
ImageOut(pngfilename, channelstr, prow, nrow, IMG_WIDTH, 0, NULL, enhancements, mapFile); | |||||
} | |||||
// Channel A | |||||
if (CONTAINS(imgopt, 'a')) { | |||||
char channelstr[21]; | |||||
sprintf(channelstr, "%s (%s)", ch.id[chA], ch.name[chA]); | |||||
sprintf(pngfilename, "%s/%s-%s.png", pngdirname, name, ch.id[chA]); | |||||
ImageOut(pngfilename, channelstr, prow, nrow, CH_WIDTH, CHA_OFFSET, NULL, enhancements, mapFile); | |||||
} | |||||
// Channel B | |||||
if (CONTAINS(imgopt, 'b')) { | |||||
char channelstr[21]; | |||||
sprintf(channelstr, "%s (%s)", ch.id[chB], ch.name[chB]); | |||||
sprintf(pngfilename, "%s/%s-%s.png", pngdirname, name, ch.id[chB]); | |||||
ImageOut(pngfilename, channelstr, prow, nrow, CH_WIDTH , CHB_OFFSET, NULL, enhancements, mapFile); | |||||
} | |||||
// Distribution image | |||||
if (CONTAINS(imgopt, 'd')) { | |||||
sprintf(pngfilename, "%s/%s-d.png", pngdirname, name); | |||||
distrib(pngfilename, prow, nrow); | |||||
} | |||||
} | |||||
exit(0); | |||||
} | |||||
exit(EINVAL); | |||||
} |
@@ -0,0 +1,56 @@ | |||||
/* | |||||
* This Quickselect routine is based on the algorithm described in | |||||
* "Numerical recipes in C", Second Edition, | |||||
* Cambridge University Press, 1992, Section 8.5, ISBN 0-521-43108-5 | |||||
* This code by Nicolas Devillard - 1998. Public domain. | |||||
*/ | |||||
#define ELEM_SWAP(a, b) { register float t = (a); (a) = (b); (b) = t; } | |||||
float quick_select(float arr[], int n) { | |||||
int low, median, high; | |||||
int middle, ll, hh; | |||||
low = 0; high = n-1; median = (low + high) / 2; | |||||
for (;;) { | |||||
if (high <= low) /* One element only */ | |||||
return arr[median] ; | |||||
if (high == low + 1) { /* Two elements only */ | |||||
if (arr[low] > arr[high]) | |||||
ELEM_SWAP(arr[low], arr[high]); | |||||
return arr[median]; | |||||
} | |||||
/* Find median of low, middle and high items; swap into position low */ | |||||
middle = (low + high) / 2; | |||||
if (arr[middle] > arr[high]) ELEM_SWAP(arr[middle], arr[high]); | |||||
if (arr[low] > arr[high]) ELEM_SWAP(arr[low], arr[high]); | |||||
if (arr[middle] > arr[low]) ELEM_SWAP(arr[middle], arr[low]); | |||||
/* Swap low item (now in position middle) into position (low+1) */ | |||||
ELEM_SWAP(arr[middle], arr[low+1]); | |||||
/* Nibble from each end towards middle, swapping items when stuck */ | |||||
ll = low + 1; | |||||
hh = high; | |||||
for (;;) { | |||||
do ll++; while (arr[low] > arr[ll]); | |||||
do hh--; while (arr[hh] > arr[low]); | |||||
if (hh < ll) | |||||
break; | |||||
ELEM_SWAP(arr[ll], arr[hh]); | |||||
} | |||||
/* Swap middle item (in position low) back into correct position */ | |||||
ELEM_SWAP(arr[low], arr[hh]); | |||||
/* Re-set active partition */ | |||||
if (hh <= median) | |||||
low = ll; | |||||
if (hh >= median) | |||||
high = hh - 1; | |||||
} | |||||
} | |||||
#undef ELEM_SWAP |
@@ -1,92 +0,0 @@ | |||||
/* | |||||
* This file is part of Aptdec. | |||||
* Copyright (c) 2004-2009 Thierry Leconte (F4DWV), Xerbo (xerbo@protonmail.com) 2019-2020 | |||||
* | |||||
* Aptdec is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 2 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. | |||||
* | |||||
*/ | |||||
char GviPalette[256*3] = { | |||||
"\230t\17\233x\22\236{\27\241\200\33\244\203\37\247\210#\252\214'\255\220" | |||||
",\260\2240\264\2305\267\2358\272\240=\274\245A\300\251E\303\255I\306\262" | |||||
"M\311\266Q\314\272V\317\276Z\322\302^\325\306b\330\312g\334\317k\337\323" | |||||
"o\342\330s\344\333w\350\337{\352\344\200\356\350\203\361\354\207\364\360" | |||||
"\213\367\364\220\372\370\224\376\376\230\376\375\230\376\372\227\376\366" | |||||
"\224\376\362\222\376\357\220\376\352\216\376\347\213\376\343\211\376\340" | |||||
"\207\376\334\205\376\330\202\376\325\200\376\321\177\376\315|\376\312z\376" | |||||
"\306y\376\302v\376\277t\376\273q\376\267o\376\263m\376\260k\376\254h\376" | |||||
"\250f\376\245d\376\241b\376\235_\376\232^\376\226[\376\223Y\376\217W\376" | |||||
"\213U\376\207R\376\203Q\376\200N\376}L\376zJ\376vG\376rE\376nB\376k@\376" | |||||
"g>\376d<\376`:\376\\7\376X6\376T3\376Q0\376M/\376J-\376F*\376C(\376>%\376" | |||||
";$\3767!\3763\37\3760\35\376,\32\376(\31\376$\26\376!\24\376\35\22\376\32" | |||||
"\20\376\26\15\376\22\13\376\16\11\376\12\6\376\7\4\376\0\0\373\2\0\366\3" | |||||
"\0\362\5\0\355\7\0\350\11\0\343\13\0\336\15\0\332\17\0\325\21\0\320\22\0" | |||||
"\313\24\0\307\26\0\303\27\0\275\32\0\270\33\0\264\35\0\257\37\0\253!\0\246" | |||||
"#\0\241%\0\234&\0\227)\0\223+\0\216,\0\212.\0\2050\0\2002\0}4\0w6\0s7\0n" | |||||
"9\0i;\0e>\0`?\0[A\0VC\0QE\0MG\0HH\0CK\0?M\0:N\0""5P\0""0R\0,S\0'V\0\"X\0" | |||||
"\36Z\0\31\\\0\23^\0\17_\0\12a\0\3b\0\6e\2\12i\5\17n\11\23r\14\30v\17\34{" | |||||
"\23\40\200\26$\203\31)\207\35.\215\40""1\221$6\225':\232*?\236.C\2431G\250" | |||||
"4K\2548O\260;U\265>Y\272A\\\276Eb\303Ie\307Lj\313On\320Ss\324Vw\331Y{\336" | |||||
"]\200\342`\203\347d\207\353g\213\357j\217\364n\223\371q\230\376t\222\375" | |||||
"{\207\374\205}\373\216r\371\230g\371\241\\\370\254Q\367\265F\365\300;\364" | |||||
"\3110\363\323%\362\334\32\361\347\17\357\361\3\356\371\4\353\370\3\347\365" | |||||
"\4\342\361\4\335\357\4\331\354\4\323\351\3\317\346\3\313\343\4\306\340\3" | |||||
"\301\335\3\275\332\3\270\327\3\263\324\2\257\322\3\252\316\3\245\314\3\241" | |||||
"\311\2\234\306\2\230\303\2\223\300\3\216\275\3\211\273\2\205\267\2\200\265" | |||||
"\2|\262\2w\257\2s\254\2n\251\2j\246\2e\243\2`\240\2[\235\2W\232\1S\230\2" | |||||
"M\225\1I\221\2E\217\1@\214\1;\211\1""7\206\1""1\203\1-\200\0(~\1${\0\37y" | |||||
"\0\33u\0\25r\0\21p\0\14l\0\7j\0\3" | |||||
}; | |||||
char TempPalette[256*3] = { | |||||
"\376\376\376\376\376\376\375\375\376\374\375\376\374\375\375\374\373\375" | |||||
"\373\373\375\372\373\375\372\373\374\372\372\374\371\372\374\371\371\375" | |||||
"\370\371\374\367\370\375\367\370\374\367\367\374\366\367\373\366\366\373" | |||||
"\365\366\373\364\366\373\364\365\374\363\365\373\363\364\373\363\364\373" | |||||
"\362\363\372\361\363\372\361\362\372\361\361\372\360\361\372\357\361\372" | |||||
"\357\361\372\356\360\372\355\357\371\355\357\371\355\356\372\354\356\371" | |||||
"\354\355\371\354\355\371\353\355\371\353\354\370\352\353\370\351\353\370" | |||||
"\351\353\370\350\352\370\350\351\370\347\351\370\347\351\370\346\350\367" | |||||
"\346\350\367\346\350\367\345\346\367\344\346\367\344\346\367\344\345\366" | |||||
"\342\346\366\343\344\366\342\345\366\341\344\366\341\343\365\340\343\366" | |||||
"\337\342\365\337\341\366\336\341\365\337\341\365\336\340\365\335\340\365" | |||||
"\335\337\364\334\337\364\334\336\364\334\336\364\333\336\364\333\335\364" | |||||
"\332\334\364\331\334\363\331\334\364\330\333\363\330\333\363\327\332\363" | |||||
"\326\332\363\327\331\362\326\331\362\325\331\362\325\330\362\324\330\363" | |||||
"\323\327\362\323\327\362\322\326\362\322\326\362\322\325\361\321\325\361" | |||||
"\321\325\361\320\323\361\320\323\361\317\323\361\317\322\360\316\322\361" | |||||
"\316\322\360\315\321\360\314\321\360\314\320\360\313\320\360\313\317\360" | |||||
"\312\317\357\311\317\357\312\316\357\311\316\357\310\315\356\310\314\356" | |||||
"\307\314\356\307\313\357\306\313\357\306\313\356\305\312\356\305\312\355" | |||||
"\305\311\356\304\311\356\303\311\355\302\310\355\302\307\355\302\307\355" | |||||
"\301\306\355\301\306\355\300\305\354\300\305\354\277\305\354\277\304\354" | |||||
"\276\304\354\276\304\354\276\302\353\275\303\354\274\302\353\274\301\353" | |||||
"\274\301\353\273\301\353\272\300\353\272\277\353\271\277\352\271\276\352" | |||||
"\270\276\352\270\276\352\267\275\351\267\274\352\266\275\352\265\273\352" | |||||
"\265\273\351\264\273\351\265\272\351\263\272\351\263\272\351\263\271\350" | |||||
"\263\271\351\262\271\350\261\267\350\260\267\350\260\266\350\257\267\350" | |||||
"\256\270\350\255\272\350\254\273\351\252\275\351\251\275\351\247\300\351" | |||||
"\247\301\351\244\303\352\243\306\352\242\310\353\240\312\352\237\315\352" | |||||
"\236\317\352\234\322\353\233\324\353\232\330\353\230\332\354\227\336\354" | |||||
"\225\341\354\224\345\354\223\350\354\222\354\355\220\355\353\217\355\347" | |||||
"\216\355\344\214\355\337\212\356\334\211\356\330\210\357\324\207\356\317" | |||||
"\205\357\313\204\357\307\202\360\302\200\357\275\200\360\270\177\360\264" | |||||
"~\360\256|\361\251z\360\244y\361\237x\361\230v\362\223u\362\215s\362\207" | |||||
"r\362\200p\362|o\362un\362np\363ls\363kw\363i|\364g\200\363f\204\363d\210" | |||||
"\364b\215\364a\222\364`\227\365^\234\365]\241\365\\\246\365Z\254\366Y\261" | |||||
"\365W\267\366U\275\366T\303\366S\311\367Q\317\367O\325\367N\334\370L\343" | |||||
"\370J\352\370I\360\370G\367\371F\370\362E\371\354C\370\345A\371\336@\371" | |||||
"\326?\372\316=\372\307;\372\277:\372\2679\373\2577\373\2475\373\2373\373" | |||||
"\2261\373\2161\373\206.\373}-\374u,\375k*\374b(\375Y'\375O%\375E#\375;\"" | |||||
"\3762\40\376(\37\376\35" | |||||
}; |
@@ -0,0 +1,313 @@ | |||||
/* | |||||
* This file is part of Aptdec. | |||||
* Copyright (c) 2004-2009 Thierry Leconte (F4DWV), Xerbo (xerbo@protonmail.com) 2019-2020 | |||||
* | |||||
* Aptdec is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 2 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. | |||||
* | |||||
*/ | |||||
#include <png.h> | |||||
#include <stdlib.h> | |||||
#include <stdio.h> | |||||
#include <string.h> | |||||
#include <stdint.h> | |||||
#include "offsets.h" | |||||
#include "messages.h" | |||||
typedef struct { | |||||
float r, g, b; | |||||
} rgb_t; | |||||
extern int zenith; | |||||
extern char PrecipPalette[256*3]; | |||||
extern rgb_t applyPalette(char *palette, int val); | |||||
extern rgb_t RGBcomposite(rgb_t top, float top_a, rgb_t bottom, float bottom_a); | |||||
int mapOverlay(char *filename, rgb_t **crow, int nrow, int zenith, int MCIR) { | |||||
FILE *fp = fopen(filename, "rb"); | |||||
// 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 != 1040){ | |||||
fprintf(stderr, "Map must be 1040px wide.\n"); | |||||
return 0; | |||||
}else if(bit_depth != 16){ | |||||
fprintf(stderr, "Map must be 16 bit color.\n"); | |||||
return 0; | |||||
}else if(color_type != PNG_COLOR_TYPE_RGB){ | |||||
fprintf(stderr, "Map must be RGB.\n"); | |||||
return 0; | |||||
}else if(zenith > height/2 || nrow-zenith > height/2){ | |||||
fprintf(stderr, "WARNING: Map is too short to cover entire image\n"); | |||||
} | |||||
// Create row buffers | |||||
png_bytep *mapRows = NULL; | |||||
mapRows = (png_bytep *) malloc(sizeof(png_bytep) * height); | |||||
for(int y = 0; y < height; y++) | |||||
mapRows[y] = (png_byte *) malloc(png_get_rowbytes(png, info)); | |||||
// Read image | |||||
png_read_image(png, mapRows); | |||||
// Tidy up | |||||
fclose(fp); | |||||
png_destroy_read_struct(&png, &info, NULL); | |||||
// Map overlay / MCIR / Precipitation | |||||
int mapOffset = (height/2)-zenith; | |||||
for(int y = 0; y < nrow; y++) { | |||||
for(int x = 49; x < width - 82; x++){ | |||||
// Maps are 16 bit / channel | |||||
png_bytep px = &mapRows[CLIP(y + mapOffset, 0, height)][x * 6]; | |||||
rgb_t map = { | |||||
(px[0] << 8) | px[1], | |||||
(px[2] << 8) | px[3], | |||||
(px[4] << 8) | px[5] | |||||
}; | |||||
// Pixel offsets | |||||
int chb = x + CHB_OFFSET - 49; | |||||
int cha = x + 36; | |||||
// Fill in map | |||||
if(MCIR){ | |||||
if(map.b < 128 && map.g > 128){ | |||||
// Land | |||||
float green = CLIP((map.g-256)/32.0, 0, 1); | |||||
float blue = 1-CLIP((map.b-32)/64.0, 0, 1); | |||||
crow[y][cha] = (rgb_t){50 + blue*50, 80 + green*70, 64}; | |||||
}else{ | |||||
// Sea | |||||
crow[y][cha] = (rgb_t){12, 30, 85}; | |||||
} | |||||
} | |||||
// Color -> alpha: composite | |||||
int composite = map.r + map.g + map.b; | |||||
// Color -> alpha: flattern and convert to 8 bits / channel | |||||
float factor = (255 * 255 * 2.0) / composite; | |||||
map.r *= factor/257.0; map.g *= factor/257.0; map.b *= factor/257.0; | |||||
// Color -> alpha: convert black to alpha | |||||
float alpha = CLIP(composite / 65535.0, 0, 1); | |||||
// Map overlay on channel A | |||||
crow[y][cha] = RGBcomposite(map, alpha, crow[y][cha], 1); | |||||
// Map overlay on channel B | |||||
if(!MCIR) | |||||
crow[y][chb] = RGBcomposite(map, alpha, crow[y][chb], 1); | |||||
// Cloud overlay on channel A | |||||
if(MCIR){ | |||||
float cloud = CLIP((crow[y][chb].r - 115) / 107, 0, 1); | |||||
crow[y][cha] = RGBcomposite((rgb_t){240, 250, 255}, cloud, crow[y][cha], 1); | |||||
} | |||||
// Precipitation | |||||
if(crow[y][chb].r > 191){ | |||||
float ramp = CLIP((crow[y][chb].r - 191) / 5.0, 0, 1); | |||||
crow[y][cha] = RGBcomposite(applyPalette(PrecipPalette, crow[y][chb].r), ramp, crow[y][cha], 1); | |||||
} | |||||
} | |||||
} | |||||
return 1; | |||||
} | |||||
int readRawImage(char *filename, float **prow, int *nrow) { | |||||
FILE *fp = fopen(filename, "r"); | |||||
// 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 != IMG_WIDTH){ | |||||
fprintf(stderr, "Raw image must be %ipx wide.\n", IMG_WIDTH); | |||||
return 0; | |||||
}else if(bit_depth != 8){ | |||||
fprintf(stderr, "Raw image must have 8 bit color.\n"); | |||||
return 0; | |||||
}else if(color_type != PNG_COLOR_TYPE_GRAY){ | |||||
fprintf(stderr, "Raw image must be grayscale.\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 prow | |||||
*nrow = height; | |||||
for(int y = 0; y < height; y++) { | |||||
prow[y] = (float *) malloc(sizeof(float) * width); | |||||
for(int x = 0; x < width; x++) | |||||
prow[y][x] = (float)PNGrows[y][x]; | |||||
} | |||||
return 1; | |||||
} | |||||
typedef struct { | |||||
float *prow[3000]; // Row buffers | |||||
int nrow; // Number of rows | |||||
int chA, chB; // ID of each channel | |||||
char name[256]; // Stripped filename | |||||
} image_t; | |||||
typedef struct { | |||||
char *type; // Output image type | |||||
char *effects; | |||||
int satnum; // The satellite number | |||||
char *map; // Path to a map file | |||||
char *path; // Output directory | |||||
} options_t; | |||||
//int ImageOut(char *filename, char *chid, float **prow, int nrow, int width, int offset, char *palette, char *effects, char *mapFile) { | |||||
int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, char *chid, char *palette){ | |||||
char outName[384]; | |||||
sprintf(outName, "%s/%s-%s.png", opts->path, img->name, chid); | |||||
FILE *pngfile; | |||||
png_text text_ptr[] = { | |||||
{PNG_TEXT_COMPRESSION_NONE, "Software", VERSION}, | |||||
{PNG_TEXT_COMPRESSION_NONE, "Channel", desc, sizeof(desc)}, | |||||
{PNG_TEXT_COMPRESSION_NONE, "Description", "NOAA satellite image", 20} | |||||
}; | |||||
// Reduce the width of the image to componsate for the missing telemetry | |||||
if(opts->effects != NULL && CONTAINS(opts->effects, 't')) | |||||
width -= TOTAL_TELE; | |||||
// Create writer | |||||
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); | |||||
if (!png_ptr) { | |||||
png_destroy_write_struct(&png_ptr, (png_infopp) NULL); | |||||
fprintf(stderr, ERR_PNG_WRITE); | |||||
return(0); | |||||
} | |||||
png_infop info_ptr = png_create_info_struct(png_ptr); | |||||
if (!info_ptr) { | |||||
png_destroy_write_struct(&png_ptr, (png_infopp) NULL); | |||||
fprintf(stderr, ERR_PNG_INFO); | |||||
return(0); | |||||
} | |||||
// 8 bit RGB image | |||||
png_set_IHDR(png_ptr, info_ptr, width, img->nrow, | |||||
8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, | |||||
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); | |||||
png_set_text(png_ptr, info_ptr, text_ptr, 3); | |||||
// Channel = 25cm wide | |||||
png_set_pHYs(png_ptr, info_ptr, 3636, 3636, PNG_RESOLUTION_METER); | |||||
// Init I/O | |||||
pngfile = fopen(outName, "wb"); | |||||
if (!pngfile) { | |||||
fprintf(stderr, ERR_FILE_WRITE, outName); | |||||
return(1); | |||||
} | |||||
png_init_io(png_ptr, pngfile); | |||||
png_write_info(png_ptr, info_ptr); | |||||
// Move prow into crow, crow ~ color rows | |||||
rgb_t *crow[img->nrow]; | |||||
for(int y = 0; y < img->nrow; y++){ | |||||
crow[y] = (rgb_t *) malloc(sizeof(rgb_t) * IMG_WIDTH); | |||||
for(int x = 0; x < IMG_WIDTH; x++){ | |||||
if(palette == NULL){ | |||||
crow[y][x].r = crow[y][x].g = crow[y][x].b = CLIP(img->prow[y][x], 0, 255); | |||||
}else{ | |||||
crow[y][x] = applyPalette(palette, img->prow[y][x]); | |||||
} | |||||
} | |||||
} | |||||
if(opts->map != NULL && opts->map[0] != '\0'){ | |||||
if(mapOverlay(opts->map, crow, img->nrow, zenith, strcmp(chid, "MCIR") == 0) == 0){ | |||||
fprintf(stderr, "Skipping MCIR generation; see above.\n"); | |||||
return 0; | |||||
} | |||||
}else if(strcmp(chid, "MCIR") == 0){ | |||||
fprintf(stderr, "Skipping MCIR generation; no map provided.\n"); | |||||
return 0; | |||||
} | |||||
//extern void falsecolor(float vis, float temp, float *r, float *g, float *b); | |||||
printf("Writing %s", outName); | |||||
int fcimage = strcmp(chid, "False Color") == 0; | |||||
// Build RGB image | |||||
for (int y = 0; y < img->nrow; y++) { | |||||
png_color pix[width]; | |||||
for (int x = 0; x < width; x++) { | |||||
pix[x].red = (int)crow[y][x + offset].r; | |||||
pix[x].green = (int)crow[y][x + offset].g; | |||||
pix[x].blue = (int)crow[y][x + offset].b; | |||||
} | |||||
png_write_row(png_ptr, (png_bytep) pix); | |||||
} | |||||
// Tidy up | |||||
png_write_end(png_ptr, info_ptr); | |||||
fclose(pngfile); | |||||
printf("\nDone\n"); | |||||
png_destroy_write_struct(&png_ptr, &info_ptr); | |||||
return(1); | |||||
} | |||||