Add linear equalise Add image flip for southbound passes Completly remove old falseclor in favour of a much simplier methodtags/v1.8.0
@@ -2,18 +2,19 @@ CC = gcc | |||||
BIN = /usr/bin | BIN = /usr/bin | ||||
INCLUDES = -I. | INCLUDES = -I. | ||||
CFLAGS = -O3 -DNDEBUG -Wall -Wextra $(INCLUDES) | CFLAGS = -O3 -DNDEBUG -Wall -Wextra $(INCLUDES) | ||||
OBJS = main.o image.o dsp.o filter.o reg.o fcolor.o pngio.o median.o color.o | |||||
OBJS = main.o image.o dsp.o filter.o reg.o pngio.o median.o color.o | |||||
aptdec: $(OBJS) | aptdec: $(OBJS) | ||||
$(CC) -o $@ $(OBJS) -lm -lsndfile -lpng | $(CC) -o $@ $(OBJS) -lm -lsndfile -lpng | ||||
reg.o: reg.c | |||||
color.o: color.c | 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 | |||||
image.o: image.c offsets.h messages.h offsets.h | |||||
fcolor.o: fcolor.c offsets.h | |||||
pngio.o: pngio.c offsets.h messages.h | |||||
main.o: main.c | |||||
media.o: median.c | |||||
dsp.o: dsp.c | |||||
filter.o: filter.c | |||||
image.o: image.c | |||||
pngio.o: pngio.c | |||||
clean: | clean: | ||||
rm -f *.o aptdec | rm -f *.o aptdec | ||||
@@ -24,7 +24,7 @@ Aptdec uses `libsndfile` to read the input audio, so any format supported by `li | |||||
## Compilation | ## Compilation | ||||
Aptdec is portable since it is written in standard C. | Aptdec is portable since it is written in standard C. | ||||
It has successfully compiled and ran on Debian with both `gcc` and `clang` and will most likely work on any Unix platform. | |||||
It has successfully compiled and ran on Debian with both `gcc`, `clang` and `tcc` and will most likely work on any Unix platform. | |||||
Just edit the Makefile and run `make` (no configure script as of right now). | Just edit the Makefile and run `make` (no configure script as of right now). | ||||
Aptdec uses `libsndfile`, `libpng` and `libm`. | Aptdec uses `libsndfile`, `libpng` and `libm`. | ||||
@@ -65,18 +65,14 @@ Satellite number | |||||
For temperature calibration | For temperature calibration | ||||
Default: "19" | Default: "19" | ||||
-e [t|h|d|p] | |||||
-e [r|a|b|c|t|m] | |||||
Effects | Effects | ||||
Histogram equalise (h), Crop Telemetry (t), Denoise (d) or Precipitation (p) | |||||
Histogram equalise (h), Crop Telemetry (t), Denoise (d), Precipitation (p) or Linear equalise (l) | |||||
Defaults: off | Defaults: off | ||||
-m <file> | -m <file> | ||||
Map file generated by wxmap | Map file generated by wxmap | ||||
-c <file> | |||||
Use configuration file for false color generation | |||||
Default: Internal defaults | |||||
-r | -r | ||||
Realtime decode. When decoding in realtime it is highly recommended to choose a plain raw image. | Realtime decode. When decoding in realtime it is highly recommended to choose a plain raw image. | ||||
``` | ``` | ||||
@@ -88,17 +84,19 @@ Generated images are outputted in PNG and are 24 bit RGB for all image types apa | |||||
Image names are `audiofile-x.png`, where `x` is: | Image names are `audiofile-x.png`, where `x` is: | ||||
- `r` for raw images | - `r` for raw images | ||||
- Sensor ID (1, 2, 3A, 3B, 4, 5) for channel A|B images | |||||
- `c` for false color. | |||||
- Sensor ID (`1`, `2`, `3A`, `3B`, `4`, `5`) for channel A|B images | |||||
- `c` for false color | |||||
- `t` for temperature calibrated images | - `t` for temperature calibrated images | ||||
- `m` for MCIR images | - `m` for MCIR images | ||||
Currently there are 4 available effects: | |||||
Currently there are 6 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 | - `d` for a median denoise filter | ||||
- `p` for a precipitation overlay | - `p` for a precipitation overlay | ||||
- `f` to flip the image (for southbound passes) | |||||
- `l` to linearly equalise the image (recommended for falsecolor images) | |||||
## Examples | ## Examples | ||||
@@ -108,7 +106,7 @@ This will process all `.wav` files in the current directory, generate calibrated | |||||
`aptdec -e dh -i b audio.wav` | `aptdec -e dh -i b audio.wav` | ||||
Decode `audio.wav` with denoise and histogram equalisation and save it into the current directory. | |||||
Decode `audio.wav` with denoise and histogram equalization and save it into the current directory. | |||||
## Realtime decoding | ## Realtime decoding | ||||
@@ -120,7 +118,7 @@ aptdec /tmp/aptaudio | |||||
sox -t pulseaudio alsa_output.pci-0000_00_1b.0.analog-stereo.monitor -c 1 -t wav /tmp/aptaudio | sox -t pulseaudio alsa_output.pci-0000_00_1b.0.analog-stereo.monitor -c 1 -t wav /tmp/aptaudio | ||||
``` | ``` | ||||
Perform a realtime decode with the audio being played out of`alsa_output.pci-0000_00_1b.0.analog`. | |||||
Perform a realtime decode with the audio being played out of `alsa_output.pci-0000_00_1b.0.analog`. | |||||
## Further reading | ## Further reading | ||||
@@ -17,11 +17,9 @@ | |||||
* | * | ||||
*/ | */ | ||||
#include "offsets.h" | |||||
#include "common.h" | |||||
typedef struct { | |||||
float r, g, b; | |||||
} rgb_t; | |||||
#define MCOMPOSITE(m1, a1, m2, a2) (m1*a1 + m2*a2*(1-a1)) | |||||
rgb_t applyPalette(char *palette, int val){ | rgb_t applyPalette(char *palette, int val){ | ||||
return (rgb_t){ | return (rgb_t){ | ||||
@@ -32,86 +30,54 @@ rgb_t applyPalette(char *palette, int val){ | |||||
} | } | ||||
rgb_t RGBcomposite(rgb_t top, float top_a, rgb_t bottom, float bottom_a){ | 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; | |||||
return (rgb_t){ | |||||
MCOMPOSITE(top.r, top_a, bottom.r, bottom_a), | |||||
MCOMPOSITE(top.g, top_a, bottom.g, bottom_a), | |||||
MCOMPOSITE(top.b, top_a, bottom.b, bottom_a) | |||||
}; | |||||
} | } | ||||
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" | |||||
}; | |||||
// The "I totally didn't just steal this from WXtoImg" palette | |||||
char TempPalette[256*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" | |||||
"\x45\x0\x8f\x46\x0\x91\x47\x0\x92\x48\x0\x94\x49\x0\x96\x4a\x0\x98\x4b\x0\x9b\x4d\x0\x9d" | |||||
"\x4e\x0\xa0\x50\x0\xa2\x51\x0\xa5\x52\x0\xa7\x54\x0\xaa\x56\x0\xae\x57\x0\xb1" | |||||
"\x58\x0\xb4\x5a\x0\xb7\x5c\x0\xba\x5e\x0\xbd\x5f\x0\xc0\x61\x0\xc4\x64\x0\xc8" | |||||
"\x66\x0\xcb\x68\x0\xce\x69\x0\xd1\x68\x0\xd4\x65\x0\xd7\x63\x0\xda\x61\x0\xdd" | |||||
"\x5d\x0\xe1\x5b\x0\xe4\x59\x0\xe6\x56\x0\xe9\x53\x0\xeb\x50\x0\xee\x4d\x0\xf0" | |||||
"\x49\x0\xf3\x47\x0\xfc\x43\x0\xfa\x31\x0\xbf\x20\x0\x89\x20\x0\x92\x1e\x0\x95" | |||||
"\x1b\x0\x97\x19\x0\x9a\x17\x0\x9c\x15\x0\x9e\x12\x0\xa0\xf\x0\xa3\xf\x2\xa5" | |||||
"\xe\x6\xa8\xe\xa\xab\xe\xd\xad\xe\x11\xb1\xd\x15\xb4\xd\x18\xb7\xd\x1c\xba" | |||||
"\xb\x21\xbd\xa\x25\xc0\xa\x29\xc3\x9\x2d\xc6\x8\x33\xca\x7\x36\xcd\x7\x3b\xd0" | |||||
"\x7\x41\xd3\x5\x45\xd6\x4\x4b\xd9\x4\x50\xdc\x3\x55\xde\x2\x5d\xe2\x1\x61\xe5" | |||||
"\x0\x66\xe7\x0\x6c\xea\x0\x72\xec\x0\x78\xee\x0\x7d\xf0\x0\x82\xf3\x0\x8d\xfc" | |||||
"\x0\x90\xfa\x0\x71\xbf\x0\x54\x89\x0\x5c\x91\x0\x61\x94\x0\x64\x96\x0\x68\x97" | |||||
"\x0\x6d\x99\x0\x71\x9b\x0\x75\x9d\x0\x79\x9f\x0\x7e\xa0\x0\x82\xa2\x0\x87\xa4" | |||||
"\x0\x8c\xa6\x0\x92\xaa\x0\x96\xac\x0\x9c\xae\x0\xa2\xb1\x0\xa6\xb3\x0\xaa\xb5" | |||||
"\x0\xad\xb7\x0\xb1\xba\x0\xb6\xbe\x0\xba\xc0\x0\xbe\xc2\x0\xc2\xc5\x0\xc6\xc6" | |||||
"\x0\xca\xc9\x0\xcc\xca\x0\xcf\xcb\x0\xd2\xcc\x0\xd4\xcc\x0\xd6\xcc\x0\xd9\xcb" | |||||
"\x0\xdb\xcb\x0\xde\xcb\x0\xe0\xcb\x0\xe2\xcc\x0\xea\xd2\x0\xea\xcf\x0\xb9\xa4" | |||||
"\x0\x8e\x7a\x1\x94\x7c\x4\x97\x79\x7\x99\x75\x9\x9b\x71\xd\x9d\x6b\x10\x9f\x67" | |||||
"\x12\xa1\x63\x15\xa3\x5f\x17\xa5\x59\x1a\xa8\x55\x1d\xaa\x50\x20\xac\x4b\x24\xaf\x45" | |||||
"\x28\xb2\x41\x2b\xb5\x3b\x2e\xb8\x35\x31\xba\x30\x34\xbd\x2b\x39\xbf\x24\x3f\xc1\x17" | |||||
"\x49\xc5\x8\x4f\xc8\x1\x4f\xca\x0\x4e\xcd\x0\x4e\xcf\x0\x4f\xd2\x0\x54\xd5\x0" | |||||
"\x5d\xd8\x0\x68\xdb\x0\x6e\xdd\x0\x74\xdf\x0\x7a\xe2\x0\x7f\xe4\x0\x85\xe7\x0" | |||||
"\x8b\xe9\x0\x8f\xeb\x0\x9b\xf3\x0\x9e\xf2\x0\x7e\xbb\x0\x60\x8a\x0\x68\x92\x0" | |||||
"\x6d\x95\x0\x71\x96\x0\x75\x98\x0\x7b\x9a\x0\x7f\x9d\x0\x83\x9f\x0\x87\xa1\x0" | |||||
"\x8c\xa2\x0\x8f\xa5\x0\x92\xa7\x0\x96\xa9\x0\x9a\xad\x0\x9d\xb0\x0\xa1\xb2\x0" | |||||
"\xa5\xb5\x0\xa9\xb7\x0\xad\xba\x0\xb2\xbd\x0\xb6\xbf\x0\xbb\xc3\x0\xbf\xc6\x0" | |||||
"\xc3\xc8\x0\xc8\xcb\x0\xcc\xce\x0\xd0\xd1\x0\xd3\xd2\x0\xd5\xd4\x0\xd9\xd4\x0" | |||||
"\xdc\xd4\x0\xde\xd5\x0\xe1\xd5\x0\xe3\xd5\x0\xe6\xd4\x0\xe8\xd1\x0\xea\xce\x0" | |||||
"\xf2\xcf\x0\xf2\xca\x0\xbb\x99\x0\x8a\x6e\x0\x92\x72\x0\x95\x72\x0\x97\x71\x0" | |||||
"\x9a\x70\x0\x9c\x6e\x0\x9e\x6d\x0\xa0\x6b\x0\xa3\x6a\x0\xa5\x68\x0\xa8\x67\x0" | |||||
"\xab\x66\x0\xae\x65\x0\xb2\x63\x0\xb4\x61\x0\xb7\x5f\x0\xba\x5d\x0\xbd\x5c\x0" | |||||
"\xc0\x59\x0\xc3\x57\x0\xc6\x54\x0\xca\x50\x0\xcd\x4d\x0\xd0\x4a\x0\xd3\x47\x0" | |||||
"\xd6\x43\x0\xd9\x40\x0\xdc\x3d\x0\xde\x39\x0\xe2\x33\x0\xe5\x2f\x0\xe7\x2c\x0" | |||||
"\xea\x28\x0\xec\x23\x0\xef\x1f\x0\xf1\x1a\x0\xf3\x14\x0\xfb\xf\x0\xfa\xd\x0" | |||||
"\xc1\x5\x0\x8e\x0\x0\x97\x0\x0\x9b\x0\x0\x9e\x0\x0\xa1\x0\x0\xa5\x0\x0" | |||||
"\xa9\x0\x0\xad\x0\x0\xb1\x0\x0\xb6\x0\x0\xba\x0\x0\xbd\x0\x0\xc2\x0\x0" | |||||
"\xc8\x0\x0\xcc\x0\x0\xcc\x0\x0" | |||||
}; | }; | ||||
char PrecipPalette[256*3] = { | char PrecipPalette[256*3] = { | ||||
"\xe0\x98\x8\xec\x84\x10\xf5\x70\x1b\xfc\x5c\x29\xff\x49\x38\xff\x37\x4a" | "\xe0\x98\x8\xec\x84\x10\xf5\x70\x1b\xfc\x5c\x29\xff\x49\x38\xff\x37\x4a" | ||||
"\xfb\x28\x5d\xf5\x1a\x71\xeb\xf\x85\xdf\x8\x99\xd0\x3\xad\xc0\x1\xbf" | "\xfb\x28\x5d\xf5\x1a\x71\xeb\xf\x85\xdf\x8\x99\xd0\x3\xad\xc0\x1\xbf" | ||||
@@ -17,9 +17,29 @@ | |||||
* | * | ||||
*/ | */ | ||||
#define ERR_FILE_WRITE "Could not open %s for writing\n" | |||||
#define ERR_FILE_READ "Could not open %s for reading\n" | |||||
#define ERR_PNG_WRITE "Could not create a PNG write struct\n" | |||||
#define ERR_PNG_INFO "Could not create a PNG info struct\n" | |||||
#define ERR_TELE_ROW "Telemetry decoding error, not enough rows.\n" | |||||
#define VERSION "Aptdec; copyright (c) 2004-2009 Thierry Leconte F4DWV, Xerbo (xerbo@protonmail.com) 2019-2020" | |||||
// Constants | |||||
#define VERSION "Aptdec; (c) 2004-2009 Thierry Leconte F4DWV, Xerbo (xerbo@protonmail.com) 2019-2020" | |||||
#define MAX_HEIGHT 3000 | |||||
// Useful macros | |||||
#define CLIP(v, lo, hi) (v > hi ? hi : (v > lo ? v : lo)) | |||||
#define CONTAINS(str, char) (strchr(str, (int) char) != NULL) | |||||
// Typedefs | |||||
typedef struct { | |||||
float r, g, b; | |||||
} rgb_t; | |||||
typedef struct { | |||||
float *prow[MAX_HEIGHT]; // 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; // Effects on the image | |||||
int satnum; // The satellite number | |||||
char *map; // Path to a map file | |||||
char *path; // Output directory | |||||
int realtime; // Realtime decoding | |||||
} options_t; |
@@ -53,8 +53,8 @@ static double K1, K2; | |||||
// Check the 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 < Fp) return(-1); | |||||
if(F > Fi) return 1; | |||||
if(F < Fp) return -1; | |||||
Fe = F; | Fe = F; | ||||
K1 = DFc / Fe; | K1 = DFc / Fe; | ||||
@@ -62,7 +62,7 @@ int init_dsp(double F) { | |||||
// Number of samples per cycle | // Number of samples per cycle | ||||
FreqOsc = Fc / Fe; | FreqOsc = Fc / Fe; | ||||
return(0); | |||||
return 0; | |||||
} | } | ||||
/* Fast phase estimator | /* Fast phase estimator | ||||
@@ -72,7 +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,9 +90,9 @@ static inline double Phase(double I, double Q) { | |||||
} | } | ||||
if(s > 0){ | if(s > 0){ | ||||
return(angle); | |||||
return angle; | |||||
}else{ | }else{ | ||||
return(-angle); | |||||
return -angle; | |||||
} | } | ||||
} | } | ||||
@@ -129,7 +129,7 @@ static double pll(double I, double Q) { | |||||
if (FreqOsc < ((Fc - DFc) / Fe)) | if (FreqOsc < ((Fc - DFc) / Fe)) | ||||
FreqOsc = (Fc - DFc) / Fe; | FreqOsc = (Fc - DFc) / Fe; | ||||
return(Ip); | |||||
return Ip; | |||||
} | } | ||||
// Convert samples into pixels | // Convert samples into pixels | ||||
@@ -154,7 +154,7 @@ static int getamp(double *ampbuff, int count) { | |||||
// Make sure there is enough samples to continue | // Make sure there is enough samples to continue | ||||
if (nin < IQFilterLen * 2 + 2) | if (nin < IQFilterLen * 2 + 2) | ||||
return(n); | |||||
return n; | |||||
} | } | ||||
// Process read samples into a brightness value | // Process read samples into a brightness value | ||||
@@ -166,7 +166,7 @@ static int getamp(double *ampbuff, int count) { | |||||
nin--; | nin--; | ||||
} | } | ||||
return(count); | |||||
return count; | |||||
} | } | ||||
// Sub-pixel offsetting + FIR compensation | // Sub-pixel offsetting + FIR compensation | ||||
@@ -192,7 +192,7 @@ int getpixelv(float *pvbuff, int count) { | |||||
res = getamp(&(ampbuff[nam]), BLKAMP - nam); | res = getamp(&(ampbuff[nam]), BLKAMP - nam); | ||||
nam += res; | nam += res; | ||||
if (nam < m) | if (nam < m) | ||||
return(n); | |||||
return n; | |||||
} | } | ||||
// Gaussian FIR compensation filter | // Gaussian FIR compensation filter | ||||
@@ -205,11 +205,11 @@ int getpixelv(float *pvbuff, int count) { | |||||
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 | ||||
// FIXME: occasionally skips noisy lines | |||||
// FIXME: skips noisy lines with no findable sync marker | |||||
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; | ||||
@@ -228,8 +228,8 @@ int getpixelrow(float *pixelv, int nrow, int *zenith) { | |||||
if (npv < SyncFilterLen + 2) { | if (npv < SyncFilterLen + 2) { | ||||
res = getpixelv(&(pixelv[npv]), SyncFilterLen + 2 - npv); | res = getpixelv(&(pixelv[npv]), SyncFilterLen + 2 - npv); | ||||
npv += res; | npv += res; | ||||
// Exit if there are no pixels left | |||||
if (npv < SyncFilterLen + 2) return(0); | |||||
if (npv < SyncFilterLen + 2) | |||||
return 0; | |||||
} | } | ||||
// Calculate the frequency offset | // Calculate the frequency offset | ||||
@@ -258,7 +258,7 @@ int getpixelrow(float *pixelv, int nrow, int *zenith) { | |||||
res = getpixelv(&(pixelv[npv]), PixelLine + SyncFilterLen - npv); | res = getpixelv(&(pixelv[npv]), PixelLine + SyncFilterLen - npv); | ||||
npv += res; | npv += res; | ||||
if (npv < PixelLine + SyncFilterLen) | if (npv < PixelLine + SyncFilterLen) | ||||
return(0); | |||||
return 0; | |||||
} | } | ||||
// Test every possible position until we get the best result | // Test every possible position until we get the best result | ||||
@@ -289,7 +289,7 @@ int getpixelrow(float *pixelv, int nrow, int *zenith) { | |||||
res = getpixelv(&(pixelv[npv]), PixelLine - npv); | res = getpixelv(&(pixelv[npv]), PixelLine - npv); | ||||
npv += res; | npv += res; | ||||
if (npv < PixelLine) | if (npv < PixelLine) | ||||
return(0); | |||||
return 0; | |||||
} | } | ||||
// Move the sync lines into the output buffer with the calculated offset | // Move the sync lines into the output buffer with the calculated offset | ||||
@@ -300,5 +300,5 @@ int getpixelrow(float *pixelv, int nrow, int *zenith) { | |||||
npv -= PixelLine; | npv -= PixelLine; | ||||
} | } | ||||
return(1); | |||||
return 1; | |||||
} | } |
@@ -1,10 +0,0 @@ | |||||
28 44 95 | |||||
23 78 37 | |||||
240 250 255 | |||||
50 | |||||
10 | |||||
24 | |||||
34 | |||||
14 | |||||
141 | |||||
114 |
@@ -1,105 +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/>. | |||||
* | |||||
*/ | |||||
#include <stdio.h> | |||||
#include <math.h> | |||||
#include "offsets.h" | |||||
typedef struct { | |||||
float r, g, b; | |||||
} rgb_t; | |||||
extern rgb_t RGBcomposite(rgb_t top, float top_a, rgb_t bottom, float bottom_a); | |||||
static struct { | |||||
rgb_t Sea, Land, Cloud; | |||||
int Seaintensity, Seaoffset; | |||||
int Landthreshold, Landintensity, Landoffset; | |||||
int Cloudthreshold, Cloudintensity; | |||||
} fcinfo = { | |||||
{28, 44, 95}, | |||||
{23, 78, 37}, | |||||
{240, 250, 255}, | |||||
50, 10, | |||||
24, 34, 14, | |||||
141, 114 | |||||
}; | |||||
// Read the config file | |||||
int readfcconf(char *file) { | |||||
FILE *fin; | |||||
fin = fopen(file, "r"); | |||||
if (fin == NULL) | |||||
return 0; | |||||
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.Cloud.r, &fcinfo.Cloud.g, &fcinfo.Cloud.b); | |||||
fscanf(fin, "%d\n", &fcinfo.Seaintensity); | |||||
fscanf(fin, "%d\n", &fcinfo.Seaoffset); | |||||
fscanf(fin, "%d\n", &fcinfo.Landthreshold); | |||||
fscanf(fin, "%d\n", &fcinfo.Landintensity); | |||||
fscanf(fin, "%d\n", &fcinfo.Landoffset); | |||||
fscanf(fin, "%d\n", &fcinfo.Cloudthreshold); | |||||
fscanf(fin, "%d", &fcinfo.Cloudintensity); | |||||
fclose(fin); | |||||
return 1; | |||||
}; | |||||
rgb_t falsecolor(float vis, float temp){ | |||||
rgb_t buffer; | |||||
float land = 0.0, sea, cloud; | |||||
// Calculate intensity of sea | |||||
sea = CLIP(vis+fcinfo.Seaoffset, 0, fcinfo.Seaintensity)/fcinfo.Seaintensity; | |||||
// 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 | |||||
cloud = CLIP(temp-fcinfo.Cloudthreshold, 0, fcinfo.Cloudintensity)/fcinfo.Cloudintensity; | |||||
buffer = RGBcomposite(fcinfo.Cloud, cloud, buffer, 1); | |||||
return buffer; | |||||
} | |||||
// GVI (global vegetation index) false color | |||||
void Ngvi(float **prow, int nrow) { | |||||
printf("Computing GVI false color"); | |||||
for (int n = 0; n < nrow; n++) { | |||||
float *pixelv = prow[n]; | |||||
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]); | |||||
gvi = (gvi + 0.1) * 340.0; | |||||
pixelv[i + CHB_OFFSET] = CLIP(gvi, 0, 255); | |||||
} | |||||
} | |||||
printf("\nDone\n"); | |||||
}; |
@@ -29,7 +29,7 @@ float fir(float *buff, const float *coeff, const int len) { | |||||
for (int i = 0; i < len; i++) { | for (int i = 0; i < len; i++) { | ||||
r += buff[i] * coeff[i]; | r += buff[i] * coeff[i]; | ||||
} | } | ||||
return(r); | |||||
return r; | |||||
} | } | ||||
/* IQ finite impulse response | /* IQ finite impulse response | ||||
@@ -63,5 +63,5 @@ float rsfir(double *buff, const float *coeff, const int len, const double offset | |||||
alpha = n - k; | alpha = n - k; | ||||
out += buff[i] * (coeff[k] * (1.0 - alpha) + coeff[k + 1] * alpha); | out += buff[i] * (coeff[k] * (1.0 - alpha) + coeff[k + 1] * alpha); | ||||
} | } | ||||
return(out); | |||||
return out; | |||||
} | } |
@@ -19,5 +19,4 @@ | |||||
float fir(float *buff, const float *coeff, const int len); | float fir(float *buff, const float *coeff, const int len); | ||||
void iqfir(float *buff, const float *coeff, const int len, double *I, double *Q); | void iqfir(float *buff, const float *coeff, const int len, double *I, double *Q); | ||||
float rsfir(double *buff, const float *coeff, const int len, const double offset, const double delta); | |||||
float rsfir(double *buff, const float *coeff, const int len, const double offset, const double delta); |
@@ -23,30 +23,14 @@ | |||||
#include <math.h> | #include <math.h> | ||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include "common.h" | |||||
#include "offsets.h" | #include "offsets.h" | ||||
#include "messages.h" | |||||
#define REGORDER 3 | #define REGORDER 3 | ||||
typedef struct { | typedef struct { | ||||
double cf[REGORDER + 1]; | double cf[REGORDER + 1]; | ||||
} rgparam_t; | } rgparam_t; | ||||
typedef struct { | |||||
float *prow[MAX_HEIGHT]; // 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 | |||||
int realtime; | |||||
} 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 | ||||
@@ -54,7 +38,7 @@ 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 }; | ||||
polyreg(REGORDER, 9, x, y, rgpr -> cf); | |||||
polyreg(REGORDER, 9, x, y, rgpr->cf); | |||||
} | } | ||||
// Convert a value to 0-255 based off the provided regression curve | // Convert a value to 0-255 based off the provided regression curve | ||||
@@ -66,7 +50,7 @@ static double rgcal(float x, rgparam_t *rgpr) { | |||||
y += rgpr->cf[i] * p; | y += rgpr->cf[i] * p; | ||||
p = p * x; | p = p * x; | ||||
} | } | ||||
return(y); | |||||
return y; | |||||
} | } | ||||
static double tele[16]; | static double tele[16]; | ||||
@@ -77,7 +61,7 @@ void histogramEqualise(float **prow, int nrow, int offset, int width){ | |||||
int histogram[256] = { 0 }; | int histogram[256] = { 0 }; | ||||
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++) | ||||
histogram[(int)floor(prow[y][x+offset])]++; | |||||
histogram[(int)CLIP(prow[y][x+offset], 0, 255)]++; | |||||
// Calculate cumulative frequency | // Calculate cumulative frequency | ||||
long sum = 0, cf[256] = { 0 }; | long sum = 0, cf[256] = { 0 }; | ||||
@@ -96,59 +80,76 @@ void histogramEqualise(float **prow, int nrow, int offset, int width){ | |||||
} | } | ||||
} | } | ||||
void linearEnhance(float **prow, int nrow, int offset, int width){ | |||||
// Plot histogram | |||||
int histogram[256] = { 0 }; | |||||
for(int y = 0; y < nrow; y++) | |||||
for(int x = 0; x < width; x++) | |||||
histogram[(int)CLIP(prow[y][x+offset], 0, 255)]++; | |||||
// Find min/max points | |||||
int min = -1, max = -1; | |||||
for(int i = 5; i < 250; i++){ | |||||
if(histogram[i]/width/(nrow/255.0) > 0.25){ | |||||
if(min == -1) min = i; | |||||
max = i; | |||||
} | |||||
} | |||||
// Stretch the brightness into the new range | |||||
for(int y = 0; y < nrow; y++) | |||||
for(int x = 0; x < width; x++) | |||||
prow[y][x+offset] = (prow[y][x+offset]-min) / (max-min) * 255.0; | |||||
} | |||||
// Brightness calibrate, including telemetry | // Brightness calibrate, including telemetry | ||||
void calibrateImage(float **prow, int nrow, int offset, int width, rgparam_t regr){ | void calibrateImage(float **prow, int nrow, int offset, int width, rgparam_t regr){ | ||||
offset -= SYNC_WIDTH+SPC_WIDTH; | offset -= SYNC_WIDTH+SPC_WIDTH; | ||||
for (int n = 0; n < nrow; n++) { | |||||
float *pixelv = prow[n]; | |||||
for (int i = 0; i < width+SYNC_WIDTH+SPC_WIDTH+TELE_WIDTH; i++) { | |||||
float pv = rgcal(pixelv[i + offset], ®r); | |||||
pixelv[i + offset] = CLIP(pv, 0, 255); | |||||
for (int y = 0; y < nrow; y++) { | |||||
for (int x = 0; x < width+SYNC_WIDTH+SPC_WIDTH+TELE_WIDTH; x++) { | |||||
float pv = rgcal(prow[y][x + offset], ®r); | |||||
prow[y][x + offset] = CLIP(pv, 0, 255); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
double teleNoise(double wedges[16]){ | double teleNoise(double wedges[16]){ | ||||
int pattern[9] = { 31, 63, 95, 127, 159, 191, 223, 255, 0 }; | |||||
double pattern[9] = { 31.07, 63.02, 94.96, 126.9, 158.86, 191.1, 228.62, 255.0, 0.0 }; | |||||
double noise = 0; | double noise = 0; | ||||
for(int i = 0; i < 9; i++) | for(int i = 0; i < 9; i++) | ||||
noise += fabs(wedges[i] - (double)pattern[i]); | |||||
noise += fabs(wedges[i] - pattern[i]); | |||||
return noise; | return noise; | ||||
} | } | ||||
// Get telemetry data for thermal calibration/equalization | // Get telemetry data for thermal calibration/equalization | ||||
int calibrate(float **prow, int nrow, int offset, int width) { | int calibrate(float **prow, int nrow, int offset, int width) { | ||||
double teleline[MAX_HEIGHT] = { 0.0 }; | |||||
double teleline[MAX_HEIGHT] = { 0.0 }; | |||||
double wedge[16]; | double wedge[16]; | ||||
rgparam_t regr[30]; | |||||
rgparam_t regr[MAX_HEIGHT/FRAME_LEN + 1]; | |||||
int telestart, mtelestart = 0; | int telestart, mtelestart = 0; | ||||
int channel = -1; | int channel = -1; | ||||
// The minimum rows required to decode a full frame | // The minimum rows required to decode a full frame | ||||
if (nrow < 192) { | if (nrow < 192) { | ||||
fprintf(stderr, ERR_TELE_ROW); | |||||
fprintf(stderr, "Telemetry decoding error, not enough rows\n"); | |||||
return 0; | return 0; | ||||
} | } | ||||
// Calculate average of a row of telemetry | // Calculate average of a row of telemetry | ||||
for (int n = 0; n < nrow; n++) { | |||||
float *pixelv = prow[n]; | |||||
// Average the center 40px | |||||
for (int i = 3; i < 43; i++) | |||||
teleline[n] += pixelv[i + offset + width]; | |||||
teleline[n] /= 40.0; | |||||
for (int y = 0; y < nrow; y++) { | |||||
for (int x = 3; x < 43; x++) | |||||
teleline[y] += prow[y][x + offset + width]; | |||||
teleline[y] /= 40.0; | |||||
} | } | ||||
/* Wedge 7 is white and 8 is black, this will have the largest | /* Wedge 7 is white and 8 is black, this will have the largest | ||||
* difference in brightness, this will always be in the center of | |||||
* the frame and can thus be used to find the start of the frame | |||||
* difference in brightness, this can be used to find the current | |||||
* position within the telemetry. | |||||
*/ | */ | ||||
double max = 0.0; | |||||
float max = 0.0f; | |||||
for (int n = nrow / 3 - 64; n < 2 * nrow / 3 - 64; n++) { | for (int n = nrow / 3 - 64; n < 2 * nrow / 3 - 64; n++) { | ||||
float df; | float df; | ||||
@@ -163,26 +164,26 @@ int calibrate(float **prow, int nrow, int offset, int width) { | |||||
} | } | ||||
} | } | ||||
// Find the start of the first frame | |||||
telestart = (mtelestart - FRAME_LEN/2) % FRAME_LEN; | |||||
telestart = (mtelestart - 64) % FRAME_LEN; | |||||
// Make sure that theres at least one full frame in the image | |||||
// Make sure that theres at least one full frame in the image | |||||
if (nrow < telestart + FRAME_LEN) { | if (nrow < telestart + FRAME_LEN) { | ||||
fprintf(stderr, ERR_TELE_ROW); | |||||
return(0); | |||||
fprintf(stderr, "Telemetry decoding error, not enough rows\n"); | |||||
return 0; | |||||
} | } | ||||
// Find the least noisy frame | // Find the least noisy frame | ||||
double minNoise = -1; | double minNoise = -1; | ||||
int bestFrame = telestart; | |||||
for (int n = telestart, k = 0; n < nrow - FRAME_LEN; n += FRAME_LEN, k++) { | |||||
// Turn pixels into wedge values | |||||
for (int j = 0; j < 16; j++) { | |||||
wedge[j] = 0.0; | |||||
int bestFrame = -1; | |||||
for (int n = telestart, k = 0; n < nrow - FRAME_LEN; n += FRAME_LEN, k++) { | |||||
int j; | |||||
for (j = 0; j < 16; j++) { | |||||
int i; | |||||
// Average the middle 6px | |||||
for (int i = 1; i < 7; i++) | |||||
wedge[j] += teleline[(j * 8) + i + n]; | |||||
wedge[j] = 0.0; | |||||
for (i = 1; i < 7; i++) | |||||
wedge[j] += teleline[n + j * 8 + i]; | |||||
wedge[j] /= 6; | wedge[j] /= 6; | ||||
} | } | ||||
@@ -210,12 +211,108 @@ int calibrate(float **prow, int nrow, int offset, int width) { | |||||
min = df; | min = df; | ||||
} | } | ||||
} | } | ||||
// Find the brightness of the minute marker, I don't really know what for | |||||
Cs = 0.0; | |||||
int i, j = n; | |||||
for (i = 0, j = n; j < n + FRAME_LEN; j++) { | |||||
float csline = 0.0; | |||||
for (int l = 3; l < 43; l++) | |||||
csline += prow[n][l + offset - SPC_WIDTH]; | |||||
csline /= 40.0; | |||||
if (csline > 50.0) { | |||||
Cs += csline; | |||||
i++; | |||||
} | |||||
} | |||||
Cs = rgcal(Cs / i, ®r[k]); | |||||
} | } | ||||
} | } | ||||
if(bestFrame == -1){ | |||||
fprintf(stderr, "Something has gone very wrong, please file a bug report."); | |||||
return 0; | |||||
} | |||||
calibrateImage(prow, nrow, offset, width, regr[bestFrame]); | calibrateImage(prow, nrow, offset, width, regr[bestFrame]); | ||||
return channel + 1; | return channel + 1; | ||||
} | |||||
void distrib(options_t *opts, image_t *img, char *chid) { | |||||
int max = 0; | |||||
// Options | |||||
options_t options; | |||||
options.path = opts->path; | |||||
options.effects = ""; | |||||
options.map = ""; | |||||
// 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); | |||||
// Biased median denoise, pretyt ugly | |||||
#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 | |||||
// Flips a channe, for southbound passes | |||||
void flipImage(image_t *img, int width, int offset){ | |||||
for(int y = 1; y < img->nrow; y++){ | |||||
for(int x = 1; x < ceil(width / 2.0); x++){ | |||||
// Flip top-left & bottom-right | |||||
float buffer = img->prow[img->nrow - y][offset + x]; | |||||
img->prow[img->nrow - y][offset + x] = img->prow[y][offset + (width - x)]; | |||||
img->prow[y][offset + (width - x)] = buffer; | |||||
} | |||||
} | |||||
} | } | ||||
// --- Temperature Calibration --- // | // --- Temperature Calibration --- // | ||||
@@ -233,7 +330,7 @@ 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; | ||||
tpr -> ch = ch - 4; | |||||
tpr->ch = ch - 4; | |||||
// Compute equivalent T blackbody temperature | // Compute equivalent T blackbody temperature | ||||
for (int n = 0; n < 4; n++) { | for (int n = 0; n < 4; n++) { | ||||
@@ -245,17 +342,17 @@ static void tempcomp(double t[16], int ch, int satnum, tempparam_t *tpr) { | |||||
d2 = satcal[satnum].d[n][2]; | d2 = satcal[satnum].d[n][2]; | ||||
T[n] = d0; | T[n] = d0; | ||||
T[n] += d1 * C; | T[n] += d1 * C; | ||||
C = C * C; | |||||
C *= C; | |||||
T[n] += d2 * C; | T[n] += d2 * C; | ||||
} | } | ||||
Tbb = (T[0] + T[1] + T[2] + T[3]) / 4.0; | Tbb = (T[0] + T[1] + T[2] + T[3]) / 4.0; | ||||
Tbb = satcal[satnum].rad[tpr->ch].A + satcal[satnum].rad[tpr->ch].B * Tbb; | Tbb = satcal[satnum].rad[tpr->ch].A + satcal[satnum].rad[tpr->ch].B * Tbb; | ||||
// Compute radiance blackbody | |||||
// Compute blackbody radiance temperature | |||||
C = satcal[satnum].rad[tpr->ch].vc; | C = satcal[satnum].rad[tpr->ch].vc; | ||||
tpr->Nbb = c1 * C * C * C / (expm1(c2 * C / Tbb)); | tpr->Nbb = c1 * C * C * C / (expm1(c2 * C / Tbb)); | ||||
// Store count blackbody and space | |||||
// Store blackbody count and space | |||||
tpr->Cs = Cs * 4.0; | tpr->Cs = Cs * 4.0; | ||||
tpr->Cb = t[14] * 4.0; | tpr->Cb = t[14] * 4.0; | ||||
} | } | ||||
@@ -274,13 +371,15 @@ static double tempcal(float Ce, int satnum, tempparam_t * rgpr) { | |||||
Ne = Nl + Nc; | Ne = Nl + Nc; | ||||
vc = satcal[satnum].rad[rgpr->ch].vc; | vc = satcal[satnum].rad[rgpr->ch].vc; | ||||
T = c2 * vc / log1p(c1 * vc * vc * vc / Ne); | |||||
T = c2 * vc / log(c1 * vc * vc * vc / Ne + 1.0); | |||||
T = (T - satcal[satnum].rad[rgpr->ch].A) / satcal[satnum].rad[rgpr->ch].B; | T = (T - satcal[satnum].rad[rgpr->ch].A) / satcal[satnum].rad[rgpr->ch].B; | ||||
// Rescale to 0-255 for -60'C to +40'C | |||||
T = (T - 273.15 + 60.0) / 100.0 * 256.0; | |||||
// Convert to celsius | |||||
T -= 273.15; | |||||
// Rescale to 0-255 for -100°C to +60°C | |||||
T = (T + 100.0) / 160.0 * 255.0; | |||||
return(T); | |||||
return T; | |||||
} | } | ||||
// Temperature calibration wrapper | // Temperature calibration wrapper | ||||
@@ -293,73 +392,9 @@ void temperature(options_t *opts, image_t *img, int offset, int width){ | |||||
tempcomp(tele, img->chB, opts->satnum - 15, &temp); | tempcomp(tele, img->chB, opts->satnum - 15, &temp); | ||||
for (int y = 0; y < img->nrow; y++) { | for (int y = 0; y < img->nrow; y++) { | ||||
float *pixelv = img->prow[y]; | |||||
for (int x = 0; x < width; x++) { | |||||
float pv = tempcal(pixelv[x + offset], opts->satnum - 15, &temp); | |||||
pixelv[x + offset] = CLIP(pv, 0, 255); | |||||
for (int x = 0; x < width; x++) { | |||||
img->prow[y][x + offset] = tempcal(img->prow[y][x + offset], opts->satnum - 15, &temp); | |||||
} | } | ||||
} | } | ||||
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 | |||||
} |
@@ -25,48 +25,30 @@ | |||||
#include <math.h> | #include <math.h> | ||||
#include <sndfile.h> | #include <sndfile.h> | ||||
#include <errno.h> | #include <errno.h> | ||||
#include <time.h> | |||||
#include "messages.h" | |||||
#include "common.h" | |||||
#include "offsets.h" | #include "offsets.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 | |||||
int realtime; | |||||
} options_t; | |||||
typedef struct { | |||||
float *prow[MAX_HEIGHT]; // Row buffers | |||||
int nrow; // Number of rows | |||||
int chA, chB; // ID of each channel | |||||
char name[256]; // Stripped filename | |||||
} image_t; | |||||
// DSP | // DSP | ||||
extern int getpixelrow(float *pixelv, int nrow, int *zenith); | |||||
extern int init_dsp(double F); | extern int init_dsp(double F); | ||||
extern int getpixelrow(float *pixelv, int nrow, int *zenith); | |||||
// I/O | // I/O | ||||
extern int readfcconf(char *file); | |||||
extern int readRawImage(char *filename, float **prow, int *nrow); | 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); | extern int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, char *chid, char *palette); | ||||
extern void closeWriter(); | |||||
extern void pushRow(float *row, int width); | |||||
extern int initWriter(options_t *opts, image_t *img, int width, int height, char *desc, char *chid); | extern int initWriter(options_t *opts, image_t *img, int width, int height, char *desc, char *chid); | ||||
extern void pushRow(float *row, int width); | |||||
extern void closeWriter(); | |||||
// Image functions | // Image functions | ||||
extern int calibrate(float **prow, int nrow, int offset, int width); | extern int calibrate(float **prow, int nrow, int offset, int width); | ||||
extern void histogramEqualise(float **prow, int nrow, int offset, int width); | extern void histogramEqualise(float **prow, int nrow, int offset, int width); | ||||
extern void linearEnhance(float **prow, int nrow, int offset, int width); | |||||
extern void temperature(options_t *opts, image_t *img, 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 denoise(float **prow, int nrow, int offset, int width); | ||||
extern void distrib(options_t *opts, image_t *img, char *chid); | extern void distrib(options_t *opts, image_t *img, char *chid); | ||||
extern void flipImage(image_t *img, int width, int offset); | |||||
// Palettes | // Palettes | ||||
extern char GviPalette[256*3]; | extern char GviPalette[256*3]; | ||||
@@ -77,6 +59,7 @@ int zenith = 0; | |||||
// Audio file | // Audio file | ||||
static SNDFILE *audioFile; | static SNDFILE *audioFile; | ||||
// Function predeclarations | |||||
static int initsnd(char *filename); | static int initsnd(char *filename); | ||||
int getsample(float *sample, int nb); | int getsample(float *sample, int nb); | ||||
static int processAudio(char *filename, options_t *opts); | static int processAudio(char *filename, options_t *opts); | ||||
@@ -85,11 +68,8 @@ static void usage(void); | |||||
int main(int argc, char **argv) { | int main(int argc, char **argv) { | ||||
fprintf(stderr, VERSION"\n"); | fprintf(stderr, VERSION"\n"); | ||||
if(argc == 1) | |||||
usage(); | |||||
// Check if there are actually any input files | // Check if there are actually any input files | ||||
if(optind == argc){ | |||||
if(argc == optind || argc == 1){ | |||||
fprintf(stderr, "No input files provided.\n"); | fprintf(stderr, "No input files provided.\n"); | ||||
usage(); | usage(); | ||||
} | } | ||||
@@ -98,14 +78,11 @@ int main(int argc, char **argv) { | |||||
// Parse arguments | // Parse arguments | ||||
int opt; | int opt; | ||||
while ((opt = getopt(argc, argv, "c:m:d:i:s:e:r")) != EOF) { | |||||
while ((opt = getopt(argc, argv, "m:d:i:s:e:r")) != EOF) { | |||||
switch (opt) { | switch (opt) { | ||||
case 'd': | case 'd': | ||||
opts.path = optarg; | opts.path = optarg; | ||||
break; | break; | ||||
case 'c': | |||||
readfcconf(optarg); | |||||
break; | |||||
case 'm': | case 'm': | ||||
opts.map = optarg; | opts.map = optarg; | ||||
break; | break; | ||||
@@ -160,6 +137,13 @@ static int processAudio(char *filename, options_t *opts){ | |||||
strcpy(path, dirname(path)); | strcpy(path, dirname(path)); | ||||
sscanf(basename(filename), "%[^.].%s", img.name, extension); | sscanf(basename(filename), "%[^.].%s", img.name, extension); | ||||
// Set output filename to current time when in realtime mode | |||||
if(opts->realtime){ | |||||
time_t t; | |||||
time(&t); | |||||
strncpy(img.name, ctime(&t), 24); | |||||
} | |||||
if(opts->realtime) initWriter(opts, &img, IMG_WIDTH, MAX_HEIGHT, "Unprocessed realtime image", "r"); | if(opts->realtime) initWriter(opts, &img, IMG_WIDTH, MAX_HEIGHT, "Unprocessed realtime image", "r"); | ||||
if(strcmp(extension, "png") == 0){ | if(strcmp(extension, "png") == 0){ | ||||
@@ -167,7 +151,7 @@ static int processAudio(char *filename, options_t *opts){ | |||||
printf("Reading %s", filename); | printf("Reading %s", filename); | ||||
if(readRawImage(filename, img.prow, &img.nrow) == 0){ | if(readRawImage(filename, img.prow, &img.nrow) == 0){ | ||||
fprintf(stderr, "Skipping %s; see above.\n", img.name); | fprintf(stderr, "Skipping %s; see above.\n", img.name); | ||||
return FAILURE; | |||||
return 0; | |||||
} | } | ||||
}else{ | }else{ | ||||
// Attempt to open the audio file | // Attempt to open the audio file | ||||
@@ -175,6 +159,7 @@ static int processAudio(char *filename, options_t *opts){ | |||||
exit(EPERM); | exit(EPERM); | ||||
// Build image | // Build image | ||||
// TODO: multithreading, would require some sort of input buffer | |||||
for (img.nrow = 0; img.nrow < MAX_HEIGHT; img.nrow++) { | for (img.nrow = 0; img.nrow < MAX_HEIGHT; img.nrow++) { | ||||
// Allocate memory for this row | // Allocate memory for this row | ||||
img.prow[img.nrow] = (float *) malloc(sizeof(float) * 2150); | img.prow[img.nrow] = (float *) malloc(sizeof(float) * 2150); | ||||
@@ -216,36 +201,43 @@ static int processAudio(char *filename, options_t *opts){ | |||||
denoise(img.prow, img.nrow, CHB_OFFSET, CH_WIDTH); | denoise(img.prow, img.nrow, CHB_OFFSET, CH_WIDTH); | ||||
} | } | ||||
// Flip, for southbound passes | |||||
if(CONTAINS(opts->effects, 'f')){ | |||||
flipImage(&img, CH_WIDTH, CHA_OFFSET); | |||||
flipImage(&img, CH_WIDTH, CHB_OFFSET); | |||||
} | |||||
// Temperature | // Temperature | ||||
if (CONTAINS(opts->type, 't') && img.chB >= 4) { | if (CONTAINS(opts->type, 't') && img.chB >= 4) { | ||||
temperature(opts, &img, CHB_OFFSET, CH_WIDTH); | temperature(opts, &img, CHB_OFFSET, CH_WIDTH); | ||||
ImageOut(opts, &img, CHB_OFFSET, CH_WIDTH, "Temperature", "t", (char *)TempPalette); | ImageOut(opts, &img, CHB_OFFSET, CH_WIDTH, "Temperature", "t", (char *)TempPalette); | ||||
} | } | ||||
// 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"); | |||||
} | |||||
} | |||||
// MCIR | // MCIR | ||||
if (CONTAINS(opts->type, 'm')) | if (CONTAINS(opts->type, 'm')) | ||||
ImageOut(opts, &img, 0, IMG_WIDTH, "MCIR", "m", NULL); | ImageOut(opts, &img, 0, IMG_WIDTH, "MCIR", "m", NULL); | ||||
// Linear equalise | |||||
if(CONTAINS(opts->effects, 'l')){ | |||||
linearEnhance(img.prow, img.nrow, CHA_OFFSET, CH_WIDTH); | |||||
linearEnhance(img.prow, img.nrow, CHB_OFFSET, CH_WIDTH); | |||||
} | |||||
// Histogram equalise | // Histogram equalise | ||||
if(CONTAINS(opts->effects, 'h')){ | if(CONTAINS(opts->effects, 'h')){ | ||||
histogramEqualise(img.prow, img.nrow, CHA_OFFSET, CH_WIDTH); | histogramEqualise(img.prow, img.nrow, CHA_OFFSET, CH_WIDTH); | ||||
histogramEqualise(img.prow, img.nrow, CHB_OFFSET, CH_WIDTH); | histogramEqualise(img.prow, img.nrow, CHB_OFFSET, CH_WIDTH); | ||||
} | } | ||||
// False color | |||||
if(CONTAINS(opts->type, 'c')){ | |||||
if(img.chA == 2 && img.chB >= 4){ | |||||
ImageOut(opts, &img, 0, CH_WIDTH, "False Color", "c", NULL); | |||||
}else{ | |||||
fprintf(stderr, "Lacking channels required for false color computation\n"); | |||||
} | |||||
} | |||||
// Raw image | // Raw image | ||||
if (CONTAINS(opts->type, 'r')) { | 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]); | sprintf(desc, "%s (%s) & %s (%s)", ch.id[img.chA], ch.name[img.chA], ch.id[img.chB], ch.name[img.chB]); | ||||
@@ -268,7 +260,7 @@ static int processAudio(char *filename, options_t *opts){ | |||||
if (CONTAINS(opts->type, 'd')) | if (CONTAINS(opts->type, 'd')) | ||||
distrib(opts, &img, "d"); | distrib(opts, &img, "d"); | ||||
return SUCCESS; | |||||
return 1; | |||||
} | } | ||||
static int initsnd(char *filename) { | static int initsnd(char *filename) { | ||||
@@ -279,28 +271,28 @@ static int initsnd(char *filename) { | |||||
infwav.format = 0; | infwav.format = 0; | ||||
audioFile = sf_open(filename, SFM_READ, &infwav); | audioFile = sf_open(filename, SFM_READ, &infwav); | ||||
if (audioFile == NULL) { | if (audioFile == NULL) { | ||||
fprintf(stderr, ERR_FILE_READ, filename); | |||||
return FAILURE; | |||||
fprintf(stderr, "Could not open %s for reading\n", filename); | |||||
return 0; | |||||
} | } | ||||
res = init_dsp(infwav.samplerate); | res = init_dsp(infwav.samplerate); | ||||
printf("Input file: %s\n", filename); | printf("Input file: %s\n", filename); | ||||
if(res < 0) { | if(res < 0) { | ||||
fprintf(stderr, "Input sample rate too low: %d\n", infwav.samplerate); | fprintf(stderr, "Input sample rate too low: %d\n", infwav.samplerate); | ||||
return FAILURE; | |||||
return 0; | |||||
}else if(res > 0) { | }else if(res > 0) { | ||||
fprintf(stderr, "Input sample rate too high: %d\n", infwav.samplerate); | fprintf(stderr, "Input sample rate too high: %d\n", infwav.samplerate); | ||||
return FAILURE; | |||||
return 0; | |||||
} | } | ||||
printf("Input sample rate: %d\n", infwav.samplerate); | printf("Input sample rate: %d\n", infwav.samplerate); | ||||
// TODO: accept stereo audio | // TODO: accept stereo audio | ||||
if (infwav.channels != 1) { | if (infwav.channels != 1) { | ||||
fprintf(stderr, "Too many channels in input file: %d\n", infwav.channels); | fprintf(stderr, "Too many channels in input file: %d\n", infwav.channels); | ||||
return FAILURE; | |||||
return 0; | |||||
} | } | ||||
return SUCCESS; | |||||
return 1; | |||||
} | } | ||||
// Read samples from the wave file | // Read samples from the wave file | ||||
@@ -312,11 +304,13 @@ static void usage(void) { | |||||
fprintf(stderr, | fprintf(stderr, | ||||
"Aptdec [options] audio files ...\n" | "Aptdec [options] audio files ...\n" | ||||
"Options:\n" | "Options:\n" | ||||
" -e [t|h] Effects\n" | |||||
" -e [t|h|d|p|f|l] Effects\n" | |||||
" t: Crop telemetry\n" | " t: Crop telemetry\n" | ||||
" h: Histogram equalise\n" | " h: Histogram equalise\n" | ||||
" d: Denoise\n" | " d: Denoise\n" | ||||
" p: Precipitation\n" | " p: Precipitation\n" | ||||
" f: Flip image\n" | |||||
" l: Linear equalise\n" | |||||
" -i [r|a|b|c|t|m] Output image\n" | " -i [r|a|b|c|t|m] Output image\n" | ||||
" r: Raw\n" | " r: Raw\n" | ||||
" a: Channel A\n" | " a: Channel A\n" | ||||
@@ -324,11 +318,11 @@ static void usage(void) { | |||||
" c: False color\n" | " c: False color\n" | ||||
" t: Temperature\n" | " t: Temperature\n" | ||||
" m: MCIR\n" | " m: MCIR\n" | ||||
" -d <dir> Image destination directory.\n" | |||||
" -s [15-19] Satellite number\n" | |||||
" -c <file> False color config file\n" | |||||
" -m <file> Map file\n" | |||||
" -r Realtime decode\n"); | |||||
" -d <dir> Image destination directory.\n" | |||||
" -s [15-19] Satellite number\n" | |||||
" -m <file> Map file\n" | |||||
" -r Realtime decode\n" | |||||
"\nRefer to the README for more infomation\n"); | |||||
exit(EINVAL); | exit(EINVAL); | ||||
} | } |
@@ -27,8 +27,3 @@ | |||||
#define CHA_OFFSET (SYNC_WIDTH+SPC_WIDTH) | #define CHA_OFFSET (SYNC_WIDTH+SPC_WIDTH) | ||||
#define CHB_OFFSET (SYNC_WIDTH+SPC_WIDTH+CH_WIDTH+TELE_WIDTH+SYNC_WIDTH+SPC_WIDTH) | #define CHB_OFFSET (SYNC_WIDTH+SPC_WIDTH+CH_WIDTH+TELE_WIDTH+SYNC_WIDTH+SPC_WIDTH) | ||||
#define TOTAL_TELE (SYNC_WIDTH+SPC_WIDTH+TELE_WIDTH+SYNC_WIDTH+SPC_WIDTH+TELE_WIDTH) | #define TOTAL_TELE (SYNC_WIDTH+SPC_WIDTH+TELE_WIDTH+SYNC_WIDTH+SPC_WIDTH+TELE_WIDTH) | ||||
#define MAX_HEIGHT 3000 | |||||
#define CLIP(val, bottom, top) (val > top ? top : (val > bottom ? val : bottom)) | |||||
#define CONTAINS(str, char) (strchr(str, (int) char) != NULL) | |||||
#define MCOMPOSITE(m1, a1, m2, a2) (m1*a1 + m2*a2*(1-a1)) |
@@ -23,34 +23,13 @@ | |||||
#include <string.h> | #include <string.h> | ||||
#include <stdint.h> | #include <stdint.h> | ||||
#include "common.h" | |||||
#include "offsets.h" | #include "offsets.h" | ||||
#include "messages.h" | |||||
typedef struct { | |||||
float r, g, b; | |||||
} rgb_t; | |||||
typedef struct { | |||||
float *prow[MAX_HEIGHT]; // 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 | |||||
int realtime; | |||||
} options_t; | |||||
extern int zenith; | extern int zenith; | ||||
extern char PrecipPalette[256*3]; | extern char PrecipPalette[256*3]; | ||||
extern rgb_t applyPalette(char *palette, int val); | extern rgb_t applyPalette(char *palette, int val); | ||||
extern rgb_t RGBcomposite(rgb_t top, float top_a, rgb_t bottom, float bottom_a); | extern rgb_t RGBcomposite(rgb_t top, float top_a, rgb_t bottom, float bottom_a); | ||||
extern rgb_t falsecolor(float vis, float temp); | |||||
int mapOverlay(char *filename, rgb_t **crow, int nrow, int zenith, int MCIR) { | int mapOverlay(char *filename, rgb_t **crow, int nrow, int zenith, int MCIR) { | ||||
FILE *fp = fopen(filename, "rb"); | FILE *fp = fopen(filename, "rb"); | ||||
@@ -204,7 +183,7 @@ int readRawImage(char *filename, float **prow, int *nrow) { | |||||
return 1; | return 1; | ||||
} | } | ||||
png_text text_ptr[] = { | |||||
png_text meta[] = { | |||||
{PNG_TEXT_COMPRESSION_NONE, "Software", VERSION}, | {PNG_TEXT_COMPRESSION_NONE, "Software", VERSION}, | ||||
{PNG_TEXT_COMPRESSION_NONE, "Channel", "Unknown", 7}, | {PNG_TEXT_COMPRESSION_NONE, "Channel", "Unknown", 7}, | ||||
{PNG_TEXT_COMPRESSION_NONE, "Description", "NOAA satellite image", 20} | {PNG_TEXT_COMPRESSION_NONE, "Description", "NOAA satellite image", 20} | ||||
@@ -214,13 +193,14 @@ int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, c | |||||
char outName[384]; | char outName[384]; | ||||
sprintf(outName, "%s/%s-%s.png", opts->path, img->name, chid); | sprintf(outName, "%s/%s-%s.png", opts->path, img->name, chid); | ||||
text_ptr[1].text = desc; | |||||
text_ptr[1].text_length = sizeof(desc); | |||||
meta[1].text = desc; | |||||
meta[1].text_length = sizeof(desc); | |||||
FILE *pngfile; | FILE *pngfile; | ||||
// Reduce the width of the image to componsate for the missing telemetry | // Reduce the width of the image to componsate for the missing telemetry | ||||
int fcimage = strcmp(desc, "False Color") == 0; | |||||
int fc = strcmp(desc, "False Color") == 0; | |||||
int greyscale = 0; | |||||
int skiptele = 0; | int skiptele = 0; | ||||
if(opts->effects != NULL && CONTAINS(opts->effects, 't')){ | if(opts->effects != NULL && CONTAINS(opts->effects, 't')){ | ||||
width -= TOTAL_TELE; | width -= TOTAL_TELE; | ||||
@@ -231,18 +211,17 @@ int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, c | |||||
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); | png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); | ||||
if (!png_ptr) { | if (!png_ptr) { | ||||
png_destroy_write_struct(&png_ptr, (png_infopp) NULL); | png_destroy_write_struct(&png_ptr, (png_infopp) NULL); | ||||
fprintf(stderr, ERR_PNG_WRITE); | |||||
fprintf(stderr, "Could not create a PNG writer\n"); | |||||
return 0; | return 0; | ||||
} | } | ||||
png_infop info_ptr = png_create_info_struct(png_ptr); | png_infop info_ptr = png_create_info_struct(png_ptr); | ||||
if (!info_ptr) { | if (!info_ptr) { | ||||
png_destroy_write_struct(&png_ptr, (png_infopp) NULL); | png_destroy_write_struct(&png_ptr, (png_infopp) NULL); | ||||
fprintf(stderr, ERR_PNG_INFO); | |||||
fprintf(stderr, "Could not create a PNG writer\n"); | |||||
return 0; | return 0; | ||||
} | } | ||||
int greyscale = 0; | |||||
if(palette == NULL && !CONTAINS(opts->effects, 'p') && !fcimage && opts->map[0] == '\0' && strcmp(chid, "MCIR") != 0){ | |||||
if(palette == NULL && !CONTAINS(opts->effects, 'p') && !fc && opts->map[0] == '\0' && strcmp(chid, "MCIR") != 0){ | |||||
greyscale = 1; | greyscale = 1; | ||||
// Greyscale image | // Greyscale image | ||||
@@ -256,13 +235,13 @@ int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, c | |||||
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); | PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); | ||||
} | } | ||||
png_set_text(png_ptr, info_ptr, text_ptr, 3); | |||||
png_set_text(png_ptr, info_ptr, meta, 3); | |||||
png_set_pHYs(png_ptr, info_ptr, 3636, 3636, PNG_RESOLUTION_METER); | png_set_pHYs(png_ptr, info_ptr, 3636, 3636, PNG_RESOLUTION_METER); | ||||
// Init I/O | // Init I/O | ||||
pngfile = fopen(outName, "wb"); | pngfile = fopen(outName, "wb"); | ||||
if (!pngfile) { | if (!pngfile) { | ||||
fprintf(stderr, ERR_FILE_WRITE, outName); | |||||
fprintf(stderr, "Could not open %s for writing\n", outName); | |||||
return 1; | return 1; | ||||
} | } | ||||
png_init_io(png_ptr, pngfile); | png_init_io(png_ptr, pngfile); | ||||
@@ -270,13 +249,13 @@ int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, c | |||||
// Move prow into crow, crow ~ color rows | // Move prow into crow, crow ~ color rows | ||||
rgb_t *crow[img->nrow]; | rgb_t *crow[img->nrow]; | ||||
if(!greyscale){ | |||||
if(!greyscale && !fc){ | |||||
for(int y = 0; y < img->nrow; y++){ | for(int y = 0; y < img->nrow; y++){ | ||||
crow[y] = (rgb_t *) malloc(sizeof(rgb_t) * IMG_WIDTH); | crow[y] = (rgb_t *) malloc(sizeof(rgb_t) * IMG_WIDTH); | ||||
for(int x = 0; x < IMG_WIDTH; x++){ | 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); | |||||
if(palette == NULL) | |||||
crow[y][x].r = crow[y][x].g = crow[y][x].b = img->prow[y][x]; | |||||
else | else | ||||
crow[y][x] = applyPalette(palette, img->prow[y][x]); | crow[y][x] = applyPalette(palette, img->prow[y][x]); | ||||
} | } | ||||
@@ -309,8 +288,8 @@ int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, c | |||||
// Build image | // Build image | ||||
for (int y = 0; y < img->nrow; y++) { | for (int y = 0; y < img->nrow; y++) { | ||||
png_color pix[width]; | |||||
png_byte gpix[width]; | |||||
png_color pix[width]; // Color | |||||
png_byte mpix[width]; // Mono | |||||
int skip = 0; | int skip = 0; | ||||
for (int x = 0; x < width; x++) { | for (int x = 0; x < width; x++) { | ||||
@@ -326,17 +305,24 @@ int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, c | |||||
} | } | ||||
if(greyscale){ | if(greyscale){ | ||||
gpix[x] = img->prow[y][x + skip + offset]; | |||||
}else if(fcimage){ | |||||
rgb_t pixel = falsecolor(img->prow[y][x + CHA_OFFSET], img->prow[y][x + CHB_OFFSET]); | |||||
pix[x] = (png_color){pixel.r, pixel.g, pixel.b}; | |||||
mpix[x] = img->prow[y][x + skip + offset]; | |||||
}else if(fc){ | |||||
pix[x] = (png_color){ | |||||
CLIP(img->prow[y][x + CHA_OFFSET], 0, 255), | |||||
CLIP(img->prow[y][x + CHA_OFFSET], 0, 255), | |||||
CLIP(img->prow[y][x + CHB_OFFSET], 0, 255) | |||||
}; | |||||
}else{ | }else{ | ||||
pix[x] = (png_color){crow[y][x + skip + offset].r, crow[y][x + skip + offset].g, crow[y][x + skip + offset].b}; | |||||
pix[x] = (png_color){ | |||||
CLIP(crow[y][x + skip + offset].r, 0, 255), | |||||
CLIP(crow[y][x + skip + offset].g, 0, 255), | |||||
CLIP(crow[y][x + skip + offset].b, 0, 255) | |||||
}; | |||||
} | } | ||||
} | } | ||||
if(greyscale){ | if(greyscale){ | ||||
png_write_row(png_ptr, (png_bytep) gpix); | |||||
png_write_row(png_ptr, (png_bytep) mpix); | |||||
}else{ | }else{ | ||||
png_write_row(png_ptr, (png_bytep) pix); | png_write_row(png_ptr, (png_bytep) pix); | ||||
} | } | ||||
@@ -351,7 +337,7 @@ int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, c | |||||
return 1; | return 1; | ||||
} | } | ||||
// TODO: remove these from the global scope | |||||
// TODO: clean up everthing below this comment | |||||
png_structp rt_png_ptr; | png_structp rt_png_ptr; | ||||
png_infop rt_info_ptr; | png_infop rt_info_ptr; | ||||
FILE *rt_pngfile; | FILE *rt_pngfile; | ||||
@@ -360,20 +346,20 @@ int initWriter(options_t *opts, image_t *img, int width, int height, char *desc, | |||||
char outName[384]; | char outName[384]; | ||||
sprintf(outName, "%s/%s-%s.png", opts->path, img->name, chid); | sprintf(outName, "%s/%s-%s.png", opts->path, img->name, chid); | ||||
text_ptr[1].text = desc; | |||||
text_ptr[1].text_length = sizeof(desc); | |||||
meta[1].text = desc; | |||||
meta[1].text_length = sizeof(desc); | |||||
// Create writer | // Create writer | ||||
rt_png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); | rt_png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); | ||||
if (!rt_png_ptr) { | if (!rt_png_ptr) { | ||||
png_destroy_write_struct(&rt_png_ptr, (png_infopp) NULL); | png_destroy_write_struct(&rt_png_ptr, (png_infopp) NULL); | ||||
fprintf(stderr, ERR_PNG_WRITE); | |||||
fprintf(stderr, "Could not create a PNG writer\n"); | |||||
return 0; | return 0; | ||||
} | } | ||||
rt_info_ptr = png_create_info_struct(rt_png_ptr); | rt_info_ptr = png_create_info_struct(rt_png_ptr); | ||||
if (!rt_info_ptr) { | if (!rt_info_ptr) { | ||||
png_destroy_write_struct(&rt_png_ptr, (png_infopp) NULL); | png_destroy_write_struct(&rt_png_ptr, (png_infopp) NULL); | ||||
fprintf(stderr, ERR_PNG_INFO); | |||||
fprintf(stderr, "Could not create a PNG writer\n"); | |||||
return 0; | return 0; | ||||
} | } | ||||
@@ -382,7 +368,7 @@ int initWriter(options_t *opts, image_t *img, int width, int height, char *desc, | |||||
8, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, | 8, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, | ||||
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); | PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); | ||||
png_set_text(rt_png_ptr, rt_info_ptr, text_ptr, 3); | |||||
png_set_text(rt_png_ptr, rt_info_ptr, meta, 3); | |||||
// Channel = 25cm wide | // Channel = 25cm wide | ||||
png_set_pHYs(rt_png_ptr, rt_info_ptr, 3636, 3636, PNG_RESOLUTION_METER); | png_set_pHYs(rt_png_ptr, rt_info_ptr, 3636, 3636, PNG_RESOLUTION_METER); | ||||
@@ -390,7 +376,7 @@ int initWriter(options_t *opts, image_t *img, int width, int height, char *desc, | |||||
// Init I/O | // Init I/O | ||||
rt_pngfile = fopen(outName, "wb"); | rt_pngfile = fopen(outName, "wb"); | ||||
if (!rt_pngfile) { | if (!rt_pngfile) { | ||||
fprintf(stderr, ERR_FILE_WRITE, outName); | |||||
fprintf(stderr, "Could not open %s for writing\n", outName); | |||||
return 0; | return 0; | ||||
} | } | ||||
png_init_io(rt_png_ptr, rt_pngfile); | png_init_io(rt_png_ptr, rt_pngfile); | ||||