diff --git a/.gitignore b/.gitignore index 85d013c..69c083f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ -# Created by https://www.gitignore.io/api/c -# Edit at https://www.gitignore.io/?templates=c +# Created by https://www.toptal.com/developers/gitignore/api/c,cmake +# Edit at https://www.toptal.com/developers/gitignore?templates=c,cmake + +### C ### +# Prerequisites +*.d # Object files *.o @@ -26,6 +30,7 @@ *.dll *.so *.so.* +*.dylib # Executables *.exe @@ -33,6 +38,7 @@ *.app *.i*86 *.x86_64 +*.hex # Debug files *.dSYM/ @@ -40,19 +46,40 @@ *.idb *.pdb -# Program specifics +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +### CMake ### +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +### CMake Patch ### +# External projects +*-prefix/ + +# End of https://www.toptal.com/developers/gitignore/api/c,cmake + *.png !textlogo.png -!palettes/*.png +!luts/*.png !util/*.png + *.wav -aptdec +*.ogg -.vscode/ build/ -libpng/ -zlib/ -winbuild/ -winpath/ -libsndfile-1.0.29-win64.zip -libsndfile-1.0.29-win64/ diff --git a/.gitmodules b/.gitmodules index f1e9861..619fa46 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "src/argparse"] - path = src/argparse +[submodule "aptdec-cli/argparse"] + path = aptdec-cli/argparse url = https://github.com/cofyc/argparse diff --git a/CMakeLists.txt b/CMakeLists.txt index adf190c..843a97d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,81 +1,86 @@ -cmake_minimum_required (VERSION 3.0.0) -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") +cmake_minimum_required(VERSION 3.0.0) -project(aptdec C) +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_EXTENSIONS OFF) + +# Get version +find_package(Git) +if (GIT_FOUND) + execute_process(COMMAND ${GIT_EXECUTABLE} describe --tag WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} OUTPUT_VARIABLE GIT_TAG OUTPUT_STRIP_TRAILING_WHITESPACE) + set(VERSION "${GIT_TAG}") +else() + set(VERSION "Unknown") +endif() +project(aptdec C) include(GNUInstallDirs) -# libpng +# aptdec-cli find_package(PNG) - -# libsndfile -find_package(LibSndFile) - -set(LIB_C_SOURCE_FILES src/color.c src/dsp.c src/filter.c src/image.c src/algebra.c src/libs/median.c src/util.c src/calibration.c) -set(EXE_C_SOURCE_FILES src/main.c src/pngio.c src/argparse/argparse.c src/util.c) -set(LIB_C_HEADER_FILES src/apt.h) - -# Link with static library for aptdec executable, so we don't need to set the path -add_library(aptstatic STATIC ${LIB_C_SOURCE_FILES}) - -# Create shared library for 3rd party apps -add_library(apt SHARED ${LIB_C_SOURCE_FILES}) -set_target_properties(apt PROPERTIES PUBLIC_HEADER ${LIB_C_HEADER_FILES}) - -add_compile_definitions(PALETTE_DIR="${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}/palettes") - -if (PNG_FOUND AND LIBSNDFILE_FOUND) - add_executable(aptdec ${EXE_C_SOURCE_FILES}) - include_directories(${PNG_PNG_INCLUDE_DIR}) - include_directories(${LIBSNDFILE_INCLUDE_DIR}) - target_link_libraries(aptdec PRIVATE PNG::PNG) - target_link_libraries(aptdec PRIVATE ${LIBSNDFILE_LIBRARY}) - target_link_libraries(aptdec PRIVATE aptstatic) - if (MSVC) - target_compile_options(aptdec PRIVATE /D_CRT_SECURE_NO_WARNINGS=1 /DAPT_API_STATIC) +find_path(SNDFILE_INCLUDE_DIR sndfile.h) +find_library(SNDFILE_LIBRARIES NAMES sndfile libsndfile PATH) + +if(PNG_FOUND AND SNDFILE_LIBRARIES AND SNDFILE_INCLUDE_DIR) + set(APTDEC_CLI_SOURCE_FILES + aptdec-cli/argparse/argparse.c + aptdec-cli/main.c + aptdec-cli/pngio.c + aptdec-cli/util.c + ) + add_executable(aptdec-cli ${APTDEC_CLI_SOURCE_FILES}) + + target_compile_definitions(aptdec-cli PRIVATE "VERSION=\"${VERSION}\"") + target_include_directories(aptdec-cli PRIVATE ${PNG_INCLUDE_DIRS}) + target_include_directories(aptdec-cli PRIVATE ${SNDFILE_INCLUDE_DIR}) + target_include_directories(aptdec-cli PRIVATE libaptdec/include/) + target_link_libraries(aptdec-cli PRIVATE ${PNG_LIBRARIES}) + target_link_libraries(aptdec-cli PRIVATE ${SNDFILE_LIBRARIES}) + target_link_libraries(aptdec-cli PRIVATE aptdec) + + if(MSVC) + target_compile_options(aptdec-cli PRIVATE /D_CRT_SECURE_NO_WARNINGS=1) else() - # Math - target_link_libraries(aptdec PRIVATE m) - - # Throw errors on warnings on release builds - if(CMAKE_BUILD_TYPE MATCHES "Release") - target_compile_options(aptdec PRIVATE -Wall -Wextra -pedantic -Wno-missing-field-initializers) - else() - target_compile_options(aptdec PRIVATE -Wall -Wextra -pedantic -Wno-missing-field-initializers) - endif() + target_link_libraries(aptdec-cli PRIVATE m) + target_compile_options(aptdec-cli PRIVATE -Wall -Wextra -pedantic -Wno-missing-field-initializers) endif() else() - MESSAGE(WARNING "Only building apt library, as not all of the required libraries were found for aptdec.") + message(WARNING "Not building aptdec-cli as some/all required dependencies are missing, libaptdec will still be built") endif() -if (MSVC) - target_compile_options(apt PRIVATE /D_CRT_SECURE_NO_WARNINGS=1 /DAPT_API_EXPORT) - target_compile_options(aptstatic PRIVATE /D_CRT_SECURE_NO_WARNINGS=1 /DAPT_API_STATIC) +# libaptdec +set(LIBAPTDEC_HEADER_FILES libaptdec/include/aptdec.h) +set(LIBAPTDEC_SOURCE_FILES + libaptdec/algebra.c + libaptdec/calibration.c + libaptdec/color.c + libaptdec/dsp.c + libaptdec/filter.c + libaptdec/image.c + libaptdec/util.c + libaptdec/effects.c +) +add_library(aptdec SHARED ${LIBAPTDEC_SOURCE_FILES}) + +set_target_properties(aptdec PROPERTIES PUBLIC_HEADER ${LIBAPTDEC_HEADER_FILES}) +target_include_directories(aptdec PRIVATE libaptdec/include/) +target_compile_definitions(aptdec PRIVATE "VERSION=\"${VERSION}\"") + +if(MSVC) + target_compile_options(aptdec PRIVATE /D_CRT_SECURE_NO_WARNINGS=1 /DAPTDEC_API_EXPORT) else() - # Math - target_link_libraries(apt PRIVATE m) - target_link_libraries(aptstatic PRIVATE m) - - if(CMAKE_BUILD_TYPE MATCHES "Release") - target_compile_options(apt PRIVATE -Wall -Wextra -pedantic -Wno-missing-field-initializers) - else() - target_compile_options(apt PRIVATE -Wall -Wextra -pedantic -Wno-missing-field-initializers) - endif() + target_link_libraries(aptdec PRIVATE m) + target_compile_options(aptdec PRIVATE -Wall -Wextra -pedantic -Wno-missing-field-initializers) endif() -# TODO: get this from git -set(PROJECT_VERSION "1.7.0") - -# CPack -set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") +# Packaging +set(CPACK_PACKAGE_VERSION "${VERSION}") set(CPACK_PACKAGE_NAME "aptdec") set(CPACK_PACKAGE_CONTACT "Xerbo (xerbo@protonmail.com)") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "NOAA APT satellite imagery decoder") -set(CPACK_PACKAGE_DESCRIPTION "Aptdec is a FOSS program that decodes images transmitted by NOAA weather satellites. These satellites transmit constantly (among other things) medium resolution (4km/px) images of the earth over a analog mode called APT.") -set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64") +set(CPACK_PACKAGE_DESCRIPTION "Aptdec is a FOSS library/program that decodes images transmitted by the NOAA POES weather satellites. These satellites transmit constantly (among other things) medium resolution (4km/px) images of the earth over a analog mode called APT.") set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) -IF(NOT WIN32) +if(NOT WIN32) set(CPACK_GENERATOR "DEB;TGZ") set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}.${CMAKE_SYSTEM_PROCESSOR}") else() @@ -89,11 +94,10 @@ else() endif() endif() -if (TARGET aptdec) +if(TARGET aptdec-cli) install(TARGETS aptdec RUNTIME DESTINATION bin) - install(DIRECTORY "${PROJECT_SOURCE_DIR}/palettes" DESTINATION ${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}) endif() -install(TARGETS apt PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/apt LIBRARY DESTINATION lib) +install(TARGETS aptdec PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/aptdec LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) include(CPack) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ff8b624..988ccf9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,28 +1,27 @@ # How To Contribute -Thank you for showing interest to contributing to aptdec, these guidelines layout how you should go about reporting issues and contributing. +Thanks for showing an interest in improving aptdec, these guidelines outline how you should go about contributing to the project. -## Did you Find A Bug? +## Did you find a bug? -1. Ensure the bug was not already reported by searching on GitHub under Issues. -2. If you're unable to find an closed issue addressing the problem, open a new one. Be sure to include a title and clear description, as much relevant information as possible and an audio file that demonstrates what is unexpected behaviour. -3. If possible, use the report templates to create the issue. +1. Make sure the bug has not already been reported +2. If an issue already exists, leave a thumbs up to "bump" the issue +3. If an issue doesn't exist, open a new one with a clear title and description and, if relevant, any files that cause the behavior -## Do you have a patch that fixes a bug? +## Do you have a patch that fixes a bug/adds a feature? -1. Fork the repository and push your changes on a new branch. -2. Add your changes on that branch, make sure to use sensible commit names. -3. Open a new GitHub pull request to pull into master. -4. Ensure the pull request description clearly describes the problem and solution. Include the relevant issue number if applicable. +1. Fork this repository and put your changes on a *new branch* +2. Make sure to use sensible commit names +3. Open a new pull request into master + +If you don't have a GitHub account, you can email me the patch at `xerbo (at) protonmail (dot) com` ## Coding style -- Whitespaces should be denoted with tabs -- This is FOSS software, consider that people will read your code, so make it easily readable. -- If you're making major changes, make sure to create an issue to discuss it. +The coding style of LeanHRPT is based off the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) with minor modifications (see `.clang-format`), in addition to this all files should use LF line endings and end in a newline. Use American english (i.e. "color", not "colour"). ## Commit message style -- Keep titles under 80 characters to prevent wrapping. -- Make sure the commit message is descriptive of the change, dont be afraid to go into detail. -- Split up large changes into multiple commits. +- Keep titles short to prevent wrapping (descriptions exist) +- Split up large changes into multiple commits +- Never use hastags for sequential counting in commits, as this interferes with issue/PR referencing diff --git a/README.md b/README.md index 7865e84..1d8acab 100644 --- a/README.md +++ b/README.md @@ -6,58 +6,63 @@ Copyright (c) 2004-2009 Thierry Leconte (F4DWV), Xerbo (xerbo@protonmail.com) 20 ## Introduction -Aptdec is a FOSS program that decodes images transmitted by NOAA weather satellites. These satellites transmit constantly (among other things) medium resolution (4km/px) images of the earth over a analog mode called APT. -These transmissions can easily be received with a cheap SDR and simple antenna. Then the transmission can be decoded in narrow FM mode. +Aptdec is a FOSS library/program that decodes images transmitted by the NOAA POES weather satellites. These satellites transmit constantly (among other things) medium resolution (4km/px) images of the earth over a analog mode called APT. +These transmissions can easily be received with a cheap SDR and simple antenna, the transmission can be demodulated in narrow FM mode. -Aptdec can turn the audio recordings into PNG images and generate images such as: +Aptdec can turn the audio into PNG images and generate other products such as: - - Raw image: both channels with full telemetry included - - Individual channel: one of the channels form the image - - Temperature image: a temperature compensated image derived from the IR channel - - Palleted image: a image where the color is derived from a palette (false color, etc) + - Raw image: both channels (including telemetry) + - Individual channel: one channel (including telemetry) + - Visible image: a calibrated visible image of either channel 1 or 2 + - Thermal image: a calibrated thermal image from channel B + - LUT image: a image where the color is derived from a LUT (used for false color, etc) -The input audio format can be anything supported by `libsndfile` (although only tested with WAV and FLAC). Sample rate doesn't matter, although lower samples rates will process faster. +The input audio format can be anything supported by `libsndfile` (although only tested with WAV, FLAC and Ogg Vorbis). While sample rate doesn't matter, it is recommended to use 16640 Hz (4x oversampling). ## Quick start +Grab a release from [Releases](https://github.com/Xerbo/aptdec/releases) or compile from source: + ```sh sudo apt install cmake git gcc libsndfile-dev libpng-dev git clone --recursive https://github.com/Xerbo/aptdec.git && cd aptdec cmake -B build cmake --build build -# Resulting binary is build/aptdec +# Resulting binary is build/aptdec-cli ``` +In place builds are not supported. + ## Examples -To create an image from `gqrx_20200527_115730_137914960.wav` (output filename will be `gqrx_20200527_115730_137914960-r.png`) +To create an image from `gqrx_20200527_115730_137914960.wav` (output filename will be `gqrx_20200527_115730_137914960-raw.png`) ```sh -./aptdec gqrx_20200527_115730_137914960.wav +aptdec-cli gqrx_20200527_115730_137914960.wav ``` -To manually set the output filename +To manually specify the output filename ```sh -./aptdec -o image.png gqrx_20200527_115730_137914960.wav +aptdec-cli -o image.png gqrx_20200527_115730_137914960.wav ``` -Decode all WAV files in the current directory and put them in `images` +Decode all WAV files in the current directory: ```sh -mkdir images && ./aptdec -d images *.wav +aptdec-cli *.wav ``` Apply a denoise filter (see [Post-Processing Effects](#post-processing-effects) for a full list of post-processing effects) ```sh -./aptdec -e d gqrx_20200527_115730_137914960.wav +aptdec-cli -e denoise gqrx_20200527_115730_137914960.wav ``` -Create a temperature compensated image for NOAA 18 +Create a calibrated IR image from NOAA 18 ```sh -./aptdec -i t -s 18 gqrx_20200527_115730_137914960.wav +aptdec-cli -i thermal gqrx_20200527_115730_137914960.wav ``` -Apply a falsecolor palette +Apply a falsecolor LUT ```sh -./aptdec -i p -p palettes/WXtoImg-N18-HVC.png gqrx_20200527_115730_137914960.wav +aptdec-cli -i lut -l luts/WXtoImg-N18-HVC.png gqrx_20200527_115730_137914960.wav ``` ## Usage @@ -65,72 +70,74 @@ Apply a falsecolor palette ### Arguments ``` --i [r|a|b|t|m|p] Output type (stackable) --e [t|h|l|d|p|f] Effects (stackable) --o Output filename --d Destination directory --s (15-19) Satellite number --p Path to palette --r Realtime decode --g Gamma adjustment (1.0 = off) +-h, --help show a help message and exit +-i, --image= set output image type (see below) +-e, --effect= add an effect (see below) +-g, --gamma= gamma adjustment (1.0 = off) +-s, --satellite= satellite ID, must be between 15, 18 or 19 or NORAD +-l, --lut= path to a LUT +-o, --output= path of output image +-r, --realtime decode in realtime ``` ### Image output types - - `r`: Raw Image - - `a`: Channel A - - `b`: Channel B - - `t`: Temperature - - `p`: Palleted + - `raw`: Raw Image + - `a`: Channel A (including telemetry) + - `b`: Channel B (including telemetry) + - `thermal`: Calibrated thermal (MWIR/LWIR) image + - `visible`: calibrated visible/NIR image + - `lut`: LUT image, see also `-l/--lut` ### Post-Processing Effects - - `t`: Crop telemetry (only effects raw image) - - `h`: Histogram equalise - - `l`: Linear equalise - - `d`: Denoise - - `p`: Precipitation overlay - - `f`: Flip image (for northbound passes) - - `c`: Crop noise from ends of image + - `strip`: Strip telemetry (only effects raw/a/b images) + - `equalize`: Histogram equalise + - `stretch`: Linear equalise + - `denoise`: Denoise + - ~~`precipitation`: Precipitation overlay~~ + - `flip`: Flip image (for northbound passes) + - `crop`: Crop noise from ends of image ## Realtime decoding -Aptdec even supports decoding in realtime. The following decodes the audio coming from the audio device `pulseaudio alsa_output.pci-0000_00_1b.0.analog-stereo` +Aptdec supports decoding in realtime. The following captures and decodes audio from the `pipewire` interface: +```sh +arecord -f cd -D pipewire | aptdec -r - ``` -mkfifo /tmp/aptaudio -aptdec -r /tmp/aptaudio -sox -t pulseaudio alsa_output.pci-0000_00_1b.0.analog-stereo.monitor -c 1 -t wav /tmp/aptaudio + +or directly from an SDR: + +```sh +rtl_fm -f 137.1M -g 40 -s 40k | sox -t raw -r 40k -e signed-integer -b 16 - -t wav - | aptdec -r - ``` -To stop the decode and calibrate the image simply kill the `sox` process. +Image data will be streamed to `CURRENT_TIME.png` (deleted when finished). To stop the decode and normalize the image simply `Ctrl+C` the process. -## Palette formatting +## LUT format -Palettes are just simple PNG images, 256x256px in size with 24bit RGB color. The X axis represents the value of Channel A and the Y axis the value of Channel B. +LUT's are just plain PNG images, 256x256px in size with 24bit RGB color. The X axis represents the value of Channel A and the Y axis the value of Channel B. ## Building for Windows -You can cross build for Windows from Linux with the `build_windows.sh` script, you will need the following: -``` +You can cross build for Windows from Linux (recommended) using with the following commands: +```sh sudo apt install wget cmake make mingw-w64 git unzip +./build_windows.sh ``` -To build natively on Windows using MSVC, you will also need: git, ninja and cmake. Then run: +To build natively on Windows using MSVC, you will need: git, ninja and cmake. Then run: ``` .\build_windows.bat ``` -If you just wish to build libaptdec on Windows, libpng and libsndfile aren't needed. - -## Further Reading - -[User's Guide for Building and Operating -Environmental Satellite Receiving Stations](https://noaasis.noaa.gov/NOAASIS/pubs/Users_Guide-Building_Receive_Stations_March_2009.pdf) +If you only want to build libaptdec, libpng and libsndfile aren't needed. -[NOAA KLM coefficients](https://web.archive.org/web/20141220021557/https://www.ncdc.noaa.gov/oa/pod-guide/ncdc/docs/klm/tables.htm) +## References -[NOAA Satellite specifications and more information](https://www1.ncdc.noaa.gov/pub/data/satellite/publications/podguides/N-15%20thru%20N-19/pdf/) +- [User's Guide for Building and Operating Environmental Satellite Receiving Stations](https://noaasis.noaa.gov/NOAASIS/pubs/Users_Guide-Building_Receive_Stations_March_2009.pdf) +- [NOAA KLM Users Guide](https://archive.org/details/noaa-klm-guide) ## License diff --git a/aptdec-cli/main.c b/aptdec-cli/main.c new file mode 100644 index 0000000..7441354 --- /dev/null +++ b/aptdec-cli/main.c @@ -0,0 +1,490 @@ +/* + * aptdec - A lightweight FOSS (NOAA) APT decoder + * Copyright (C) 2004-2009 Thierry Leconte (F4DWV) 2019-2023 Xerbo (xerbo@protonmail.com) + * + * This program 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 . + */ + +#include +#include +#include +#ifndef _MSC_VER +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include + +#include "argparse/argparse.h" +#include "pngio.h" +#include "util.h" + +// Maximum height of an APT image that can be decoded +#define APTDEC_MAX_HEIGHT 3000 + +#define STRING_SPLIT(src, dst, delim, n) \ +char *dst[n]; \ +size_t dst##_len = 0; \ +{char *token = strtok(src, delim); \ +while (token != NULL && dst##_len < n) { \ + dst[dst##_len++] = token; \ + token = strtok(NULL, delim); \ +}} + +typedef struct { + char *type; // Output image type + char *effects; // Effects on the image + int satellite; // The satellite number + int realtime; // Realtime decoding + char *filename; // Output filename + char *lut; // Filename of lut +} options_t; + +typedef struct { + SNDFILE *file; + SF_INFO info; +} SNDFILE_t; + +// Function declarations +static int process_file(const char *path, options_t *opts); +static int freq_from_filename(const char *filename); +static int satid_from_freq(int freq); +static size_t callback(float *samples, size_t count, void *context); +static void write_line(writer_t *png, float *row); + +static volatile int sigint_stop = 0; +void sigint_handler(int signum) { + (void)signum; + sigint_stop = 1; +} + +// Basename is unsupported by MSVC +// This implementation is GNU style +#ifdef _MSC_VER +char *basename(const char *filename) { + char *p = strrchr(filename, '/'); + return p ? p + 1 : (char *)filename; +} +#endif + +int main(int argc, const char **argv) { + char version[128]; + get_version(version); + printf("%s\n", version); + // clang-format off + options_t opts = { + .type = "raw", + .effects = "", + .satellite = 0, + .realtime = 0, + .filename = "", + .lut = "", + }; + // clang-format on + + static const char *const usages[] = { + "aptdec-cli [options] [[--] sources]", + "aptdec-cli [sources]", + NULL, + }; + + struct argparse_option options[] = { + OPT_HELP(), + OPT_GROUP("Image options"), + OPT_STRING('i', "image", &opts.type, "set output image type (see the README for a list)", NULL, 0, 0), + OPT_STRING('e', "effect", &opts.effects, "add an effect (see the README for a list)", NULL, 0, 0), + OPT_GROUP("Satellite options"), + OPT_INTEGER('s', "satellite", &opts.satellite, "satellite ID, must be either NORAD or between 15 and 19", NULL, 0, 0), + OPT_GROUP("Paths"), + OPT_STRING('l', "lut", &opts.lut, "path to a LUT", NULL, 0, 0), + OPT_STRING('o', "output", &opts.filename, "path of output image", NULL, 0, 0), + OPT_GROUP("Misc"), + OPT_BOOLEAN('r', "realtime", &opts.realtime, "decode in realtime", NULL, 0, 0), + OPT_END(), + }; + + struct argparse argparse; + argparse_init(&argparse, options, usages, 0); + argparse_describe(&argparse, + "\nA lightweight FOSS NOAA APT satellite imagery decoder.", + "\nSee `README.md` for a full description of command line arguments and `LICENSE` for licensing conditions." + ); + argc = argparse_parse(&argparse, argc, argv); + + if (argc == 0) { + argparse_usage(&argparse); + } + if (argc > 1 && opts.realtime) { + error("Cannot use -r/--realtime with multiple input files"); + } + + // Actually decode the files + for (int i = 0; i < argc; i++) { + if (opts.satellite == 25338) opts.satellite = 15; + if (opts.satellite == 28654) opts.satellite = 18; + if (opts.satellite == 33591) opts.satellite = 19; + + if (opts.satellite == 0) { + int freq = freq_from_filename(argv[i]); + if (freq == 0) { + opts.satellite = 19; + warning("Satellite not specified, defaulting to NOAA-19"); + } else { + opts.satellite = satid_from_freq(freq); + printf("Satellite not specified, choosing to NOAA-%i based on filename\n", opts.satellite); + } + } + + if (opts.satellite != 15 && opts.satellite != 18 && opts.satellite != 19) { + error("Invalid satellite ID"); + } + + process_file(argv[i], &opts); + } + + return 0; +} + +static int process_file(const char *path, options_t *opts) { + const char *path_basename = basename((char *)path); + const char *dot = strrchr(path_basename, '.'); + char name[256]; + if (dot == NULL) { + strncpy(name, path_basename, 255); + } else { + strncpy(name, path_basename, clamp_int(dot - path_basename, 0, 255)); + } + + // Set filename to time when reading from stdin + if (strcmp(name, "-") == 0) { + time_t t = time(NULL); + strcpy(name, ctime(&t)); + } + + writer_t *realtime_png; + if (opts->realtime) { + char filename[269]; + sprintf(filename, "%s-decoding.png", name); + realtime_png = writer_init(filename, APT_REGION_FULL, APTDEC_MAX_HEIGHT, PNG_COLOR_TYPE_GRAY, "Unknown"); + + // Capture Ctrl+C + signal(SIGINT, sigint_handler); + } + + // Create a libsndfile instance + SNDFILE_t audioFile; + audioFile.file = sf_open(path, SFM_READ, &audioFile.info); + if (audioFile.file == NULL) { + error_noexit("Could not open file"); + return 0; + } + + printf("Input file: %s\n", path_basename); + printf("Input sample rate: %d\n", audioFile.info.samplerate); + + // Create a libaptdec instances + aptdec_t *aptdec = aptdec_init(audioFile.info.samplerate); + if (aptdec == NULL) { + sf_close(audioFile.file); + error_noexit("Error initializing libaptdec, sample rate too high/low?"); + return 0; + } + + // Decode image + float *data = (float *)malloc(APT_IMG_WIDTH * (APTDEC_MAX_HEIGHT+1) * sizeof(float)); + size_t rows; + for (rows = 0; rows < APTDEC_MAX_HEIGHT; rows++) { + float *row = &data[rows * APT_IMG_WIDTH]; + + // Break the loop when there are no more samples or the process has been sent SIGINT + if (aptdec_getrow(aptdec, row, callback, &audioFile) == 0 || sigint_stop) { + break; + } + + if (opts->realtime) { + write_line(realtime_png, row); + } + + fprintf(stderr, "Row: %zu/%zu\r", rows+1, audioFile.info.frames/audioFile.info.samplerate * 2); + fflush(stderr); + } + printf("\n"); + + // Close stream + sf_close(audioFile.file); + aptdec_free(aptdec); + + if (opts->realtime) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + writer_free(realtime_png); +#pragma GCC diagnostic pop + + char filename[269]; + sprintf(filename, "%s-decoding.png", name); + remove(filename); + } + + // Normalize + int error; + apt_image_t img = apt_normalize(data, rows, opts->satellite, &error); + if (error) { + error_noexit("Normalization failed"); + return 0; + } + + // clang-format off + const char *channel_name[] = { "?", "1", "2", "3A", "4", "5", "3B" }; + const char *channel_desc[] = { + "unknown", + "visible (0.58-0.68 um)", + "near-infrared (0.725-1.0 um)", + "near-infrared (1.58-1.64 um)", + "thermal-infrared (10.3-11.3 um)", + "thermal-infrared (11.5-12.5 um)", + "mid-infrared (3.55-3.93 um)" + }; + + printf("Channel A: %s - %s\n", channel_name[img.ch[0]], channel_desc[img.ch[0]]); + printf("Channel B: %s - %s\n", channel_name[img.ch[1]], channel_desc[img.ch[1]]); + // clang-format on + + STRING_SPLIT(opts->type, images, ",", 10) + STRING_SPLIT(opts->effects, effects, ",", 10) + STRING_SPLIT(opts->filename, filenames, ",", 10) + + for (size_t i = 0; i < effects_len; i++) { + if (strcmp(effects[i], "crop") == 0) { + apt_crop(&img); + } else if (strcmp(effects[i], "denoise") == 0) { + apt_denoise(&img, APT_REGION_CHA); + apt_denoise(&img, APT_REGION_CHB); + } else if (strcmp(effects[i], "flip") == 0) { + apt_flip(&img, APT_REGION_CHA); + apt_flip(&img, APT_REGION_CHB); + } + } + + for (size_t i = 0; i < images_len; i++) { + const char *base; + if (i < filenames_len) { + base = filenames[i]; + } else { + base = name; + } + + if (strcmp(images[i], "thermal") == 0) { + if (img.ch[1] >= 4) { + char filename[269]; + sprintf(filename, "%s-thermal.png", base); + char description[128]; + sprintf(description, "Calibrated thermal image, channel %s - %s", channel_name[img.ch[1]], channel_desc[img.ch[1]]); + + // Perform visible calibration + apt_image_t _img = apt_image_clone(img); + apt_calibrate_thermal(&_img, APT_REGION_CHA); + + writer_t *writer = writer_init(filename, APT_REGION_CHB, img.rows, PNG_COLOR_TYPE_RGB, description); + writer_write_image_gradient(writer, &_img, temperature_gradient); + writer_free(writer); + + free(_img.data); + } else { + error_noexit("Could not generate thermal image, no infrared channel"); + } + } else if (strcmp(images[i], "visible") == 0) { + if (img.ch[0] <= 2) { + char filename[269]; + sprintf(filename, "%s-visible.png", base); + char description[128]; + sprintf(description, "Calibrated visible image, channel %s - %s", channel_name[img.ch[0]], channel_desc[img.ch[0]]); + + // Perform visible calibration + apt_image_t _img = apt_image_clone(img); + apt_calibrate_visible(&_img, APT_REGION_CHA); + + writer_t *writer = writer_init(filename, APT_REGION_CHA, img.rows, PNG_COLOR_TYPE_GRAY, description); + writer_write_image(writer, &_img); + writer_free(writer); + + free(_img.data); + } else { + error_noexit("Could not generate visible image, no visible channel"); + } + } + } + + for (size_t i = 0; i < effects_len; i++) { + if (strcmp(effects[i], "stretch") == 0) { + apt_stretch(&img, APT_REGION_CHA); + apt_stretch(&img, APT_REGION_CHB); + } else if (strcmp(effects[i], "equalize") == 0) { + apt_equalize(&img, APT_REGION_CHA); + apt_equalize(&img, APT_REGION_CHB); + } + } + + for (size_t i = 0; i < images_len; i++) { + const char *base; + if (i < filenames_len) { + base = filenames[i]; + } else { + base = name; + } + + if (strcmp(images[i], "raw") == 0) { + char filename[269]; + sprintf(filename, "%s-raw.png", base); + char description[128]; + sprintf(description, + "Raw image, channel %s - %s / %s - %s", + channel_name[img.ch[0]], + channel_desc[img.ch[0]], + channel_name[img.ch[1]], + channel_desc[img.ch[1]] + ); + + writer_t *writer = writer_init(filename, APT_REGION_FULL, img.rows, PNG_COLOR_TYPE_GRAY, description); + writer_write_image(writer, &img); + writer_free(writer); + } else if (strcmp(images[i], "lut") == 0) { + if (opts->lut != NULL && opts->lut[0] != '\0') { + char filename[269]; + sprintf(filename, "%s-lut.png", base); + char description[128]; + sprintf(description, + "LUT image, channel %s - %s / %s - %s", + channel_name[img.ch[0]], + channel_desc[img.ch[0]], + channel_name[img.ch[1]], + channel_desc[img.ch[1]] + ); + + png_colorp lut = (png_colorp)malloc(sizeof(png_color)*256*256); + if (read_lut(opts->lut, lut)) { + writer_t *writer = writer_init(filename, APT_REGION_CHA, img.rows, PNG_COLOR_TYPE_RGB, description); + writer_write_image_lut(writer, &img, lut); + writer_free(writer); + } + free(lut); + } else { + warning("Cannot create LUT image, missing -l/--lut"); + } + } else if (strcmp(images[i], "a") == 0) { + char filename[269]; + sprintf(filename, "%s-a.png", base); + char description[128]; + sprintf(description, "Channel A: %s - %s", channel_name[img.ch[0]], channel_desc[img.ch[0]]); + + writer_t *writer = writer_init(filename, APT_REGION_CHA_FULL, img.rows, PNG_COLOR_TYPE_GRAY, description); + writer_write_image(writer, &img); + writer_free(writer); + } else if (strcmp(images[i], "b") == 0) { + char filename[269]; + sprintf(filename, "%s-b.png", base); + char description[128]; + sprintf(description, "Channel B: %s - %s", channel_name[img.ch[1]], channel_desc[img.ch[1]]); + + writer_t *writer = writer_init(filename, APT_REGION_CHB_FULL, img.rows, PNG_COLOR_TYPE_GRAY, description); + writer_write_image(writer, &img); + writer_free(writer); + } + } + + free(img.data); + return 1; +} + +static int freq_from_filename(const char *filename) { + char frequency_text[10] = {'\0'}; + + if (strlen(filename) >= 30+4 && strncmp(filename, "gqrx_", 5) == 0) { + memcpy(frequency_text, &filename[21], 9); + return atoi(frequency_text); + } else if (strlen(filename) == 40+4 && strncmp(filename, "SDRSharp_", 9) == 0) { + memcpy(frequency_text, &filename[26], 9); + return atoi(frequency_text); + } else if (strlen(filename) == 37+4 && strncmp(filename, "audio_", 6) == 0) { + memcpy(frequency_text, &filename[6], 9); + return atoi(frequency_text); + } + + return 0; +} + +static int satid_from_freq(int freq) { + int differences[3] = { + abs(freq - 137620000), // NOAA-15 + abs(freq - 137912500), // NOAA-18 + abs(freq - 137100000) // NOAA-19 + }; + + int best = 0; + for (size_t i = 0; i < 3; i++) { + if (differences[i] < differences[best]) { + best = i; + } + } + + const int lut[3] = {15, 18, 19}; + return lut[best]; +} + +// Read samples from a SNDFILE_t instance (passed through context) +static size_t callback(float *samples, size_t count, void *context) { + SNDFILE_t *file = (SNDFILE_t *)context; + + switch (file->info.channels) { + case 1: + return sf_read_float(file->file, samples, count); + case 2: { + float _samples[count * 2]; + size_t read = sf_read_float(file->file, _samples, count * 2); + + for (size_t i = 0; i < count; i++) { + // Average of left and right + samples[i] = (_samples[i*2] + _samples[i*2 + 1]) / 2.0f; + } + return read / 2; + } + default: + error_noexit("Only mono and stereo audio files are supported\n"); + return 0; + } +} + +// Write a line with very basic equalization +static void write_line(writer_t *png, float *row) { + float min = FLT_MAX; + float max = FLT_MIN; + for (int i = 0; i < APT_IMG_WIDTH; i++) { + if (row[i] < min) min = row[i]; + if (row[i] > max) max = row[i]; + } + + png_byte pixels[APT_IMG_WIDTH]; + for (int i = 0; i < APT_IMG_WIDTH; i++) { + pixels[i] = clamp_int(roundf((row[i]-min) / (max-min) * 255.0f), 0, 255); + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + png_write_row(png->png, pixels); +#pragma GCC diagnostic pop +} diff --git a/aptdec-cli/pngio.c b/aptdec-cli/pngio.c new file mode 100644 index 0000000..b267d12 --- /dev/null +++ b/aptdec-cli/pngio.c @@ -0,0 +1,153 @@ +/* + * aptdec - A lightweight FOSS (NOAA) APT decoder + * Copyright (C) 2004-2009 Thierry Leconte (F4DWV) 2019-2023 Xerbo (xerbo@protonmail.com) + * + * This program 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 . + */ + +#include "pngio.h" + +#include +#include +#include + +#include "util.h" + +writer_t *writer_init(const char *filename, apt_region_t region, uint32_t height, int color, char *channel) { + writer_t *png = (writer_t *)malloc(sizeof(writer_t)); + png->region = region; + + // Create writer + png->png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png->png) { + error_noexit("Could not create PNG write struct"); + return NULL; + } + + png->info = png_create_info_struct(png->png); + if (!png->info) { + error_noexit("Could not create PNG info struct"); + return NULL; + } + + png_set_IHDR(png->png, png->info, png->region.width, height, 8, color, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + char version[128]; + int version_len = get_version(version); + + png_text text[] = { + {PNG_TEXT_COMPRESSION_NONE, "Software", version, version_len}, + {PNG_TEXT_COMPRESSION_NONE, "Channel", channel, strlen(channel)} + }; + png_set_text(png->png, png->info, text, 2); + + png->file = fopen(filename, "wb"); + if (!png->file) { + error_noexit("Could not open PNG file"); + return NULL; + } + png_init_io(png->png, png->file); + png_write_info(png->png, png->info); + + printf("Writing %s...\n", filename); + return png; +} + +void writer_write_image(writer_t *png, const apt_image_t *img) { + for (size_t y = 0; y < img->rows; y++) { + png_write_row(png->png, &img->data[y*APT_IMG_WIDTH + png->region.offset]); + } +} + +void writer_write_image_gradient(writer_t *png, const apt_image_t *img, const uint32_t *gradient) { + for (size_t y = 0; y < img->rows; y++) { + png_color pixels[APT_IMG_WIDTH]; + for (size_t x = 0; x < APT_IMG_WIDTH; x++) { + apt_rgb_t pixel = apt_gradient(gradient, img->data[y*APT_IMG_WIDTH + x + png->region.offset]); + pixels[x] = (png_color){ pixel.r, pixel.g, pixel.b }; + } + + png_write_row(png->png, (png_bytep)pixels); + } +} + +void writer_write_image_lut(writer_t *png, const apt_image_t *img, const png_colorp lut) { + for (size_t y = 0; y < img->rows; y++) { + png_color pixels[APT_CH_WIDTH]; + for (size_t x = 0; x < APT_CH_WIDTH; x++) { + uint8_t a = img->data[y*APT_IMG_WIDTH + x + APT_CHA_OFFSET]; + uint8_t b = img->data[y*APT_IMG_WIDTH + x + APT_CHB_OFFSET]; + pixels[x] = lut[b*256 + a]; + } + + png_write_row(png->png, (png_bytep)pixels); + } +} + +void writer_free(writer_t *png) { + png_write_end(png->png, png->info); + png_destroy_write_struct(&png->png, &png->info); + fclose(png->file); + free(png); +} + +int read_lut(const char *filename, png_colorp out) { + png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png) { + error_noexit("Could not create PNG read struct"); + return 0; + } + png_infop info = png_create_info_struct(png); + if (!info) { + error_noexit("Could not create PNG info struct"); + return 0; + } + + FILE *file = fopen(filename, "rb"); + if (!file) { + error_noexit("Cannot open LUT"); + return 0; + } + + png_init_io(png, file); + + // Read metadata + png_read_info(png, info); + uint32_t width = png_get_image_width(png, info); + uint32_t 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); + + if (width != 256 && height != 256) { + error_noexit("LUT must be 256x256"); + return 0; + } + if (bit_depth != 8) { + error_noexit("LUT must be 8 bit"); + return 0; + } + if (color_type != PNG_COLOR_TYPE_RGB) { + error_noexit("LUT must be RGB"); + return 0; + } + + for (uint32_t i = 0; i < height; i++) { + png_read_row(png, (png_bytep)&out[i*width], NULL); + } + + png_read_end(png, info); + png_destroy_read_struct(&png, &info, NULL); + fclose(file); + return 1; +} diff --git a/aptdec-cli/pngio.h b/aptdec-cli/pngio.h new file mode 100644 index 0000000..e71d632 --- /dev/null +++ b/aptdec-cli/pngio.h @@ -0,0 +1,41 @@ +/* + * aptdec - A lightweight FOSS (NOAA) APT decoder + * Copyright (C) 2019-2023 Xerbo (xerbo@protonmail.com) + * + * This program 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 . + */ + +#ifndef APTDEC_CLI_PNGIO_H_ +#define APTDEC_CLI_PNGIO_H_ + +#include +#include + +typedef struct { + png_structp png; + png_infop info; + FILE *file; + apt_region_t region; +} writer_t; + +writer_t *writer_init(const char *filename, apt_region_t region, uint32_t height, int color, char *channel); +void writer_free(writer_t *png); + +void writer_write_image(writer_t *png, const apt_image_t *img); +void writer_write_image_gradient(writer_t *png, const apt_image_t *img, const uint32_t *gradient); +void writer_write_image_lut(writer_t *png, const apt_image_t *img, const png_colorp lut); + +int read_lut(const char *filename, png_colorp out); + +#endif diff --git a/src/util.c b/aptdec-cli/util.c similarity index 83% rename from src/util.c rename to aptdec-cli/util.c index 612267a..64a3f32 100644 --- a/src/util.c +++ b/aptdec-cli/util.c @@ -1,6 +1,6 @@ /* * aptdec - A lightweight FOSS (NOAA) APT decoder - * Copyright (C) 2019-2022 Xerbo (xerbo@protonmail.com) + * Copyright (C) 2019-2023 Xerbo (xerbo@protonmail.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,6 +18,7 @@ #include "util.h" +#include #include #include @@ -28,10 +29,12 @@ void error_noexit(const char *text) { fprintf(stderr, "\033[31mError: %s\033[0m\n", text); #endif } + void error(const char *text) { error_noexit(text); exit(1); } + void warning(const char *text) { #ifdef _WIN32 fprintf(stderr, "Warning: %s\r\n", text); @@ -40,10 +43,12 @@ void warning(const char *text) { #endif } -float clamp(float x, float hi, float lo) { +int clamp_int(int x, int lo, int hi) { if (x > hi) return hi; if (x < lo) return lo; return x; } -float clamp_half(float x, float hi) { return clamp(x, hi, -hi); } +int get_version(char *str) { + return sprintf(str, "aptdec-cli %s using libaptdec %s", VERSION, aptdec_get_version()); +} diff --git a/src/util.h b/aptdec-cli/util.h similarity index 68% rename from src/util.h rename to aptdec-cli/util.h index 7fd01ec..c8ab688 100644 --- a/src/util.h +++ b/aptdec-cli/util.h @@ -1,6 +1,6 @@ /* * aptdec - A lightweight FOSS (NOAA) APT decoder - * Copyright (C) 2019-2022 Xerbo (xerbo@protonmail.com) + * Copyright (C) 2019-2023 Xerbo (xerbo@protonmail.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,19 +16,17 @@ * along with this program. If not, see . */ -#include -#define M_PIf 3.14159265358979323846f -#define M_TAUf (M_PIf * 2.0f) +#ifndef APTDEC_CLI_UTIL_H_ +#define APTDEC_CLI_UTIL_H_ -#define MIN(a, b) (((a) < (b)) ? (a) : (b)) -#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#include -#ifndef UTIL_H -#define UTIL_H -float clamp(float x, float hi, float lo); -float clamp_half(float x, float hi); - -void error(const char *text); void error_noexit(const char *text); +__attribute__((noreturn)) void error(const char *text); void warning(const char *text); + +int clamp_int(int x, int lo, int hi); + +int get_version(char *str); + #endif diff --git a/build_windows.bat b/build_windows.bat index b51f2a2..259beaa 100644 --- a/build_windows.bat +++ b/build_windows.bat @@ -2,7 +2,7 @@ REM Build using Visual Studio 2019 on Windows REM Additional tools needed: git, cmake and ninja REM Build zlib -git clone https://github.com/madler/zlib +git clone -b v1.2.13 https://github.com/madler/zlib cd zlib mkdir build cd build @@ -11,7 +11,7 @@ ninja install cd ../../ REM Build libpng -git clone https://github.com/glennrp/libpng +git clone -b v1.6.39 https://github.com/glennrp/libpng cd libpng mkdir build cd build @@ -20,7 +20,7 @@ ninja install cd ../.. REM Build libsndfile - Could build Vorbis, FLAC and Opus first for extra support -git clone https://github.com/libsndfile/libsndfile +git clone -b 1.1.0 https://github.com/libsndfile/libsndfile cd libsndfile mkdir build cd build diff --git a/build_windows.sh b/build_windows.sh index 4cb578a..5d9558f 100755 --- a/build_windows.sh +++ b/build_windows.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Cross compile for Windows from Linux TEMP_PATH="$(pwd)/winpath" @@ -39,7 +39,6 @@ cp "libsndfile-1.0.29-win64/bin/sndfile.dll" $TEMP_PATH/bin/sndfile.dll cp "libsndfile-1.0.29-win64/include/sndfile.h" $TEMP_PATH/include/sndfile.h cp "libsndfile-1.0.29-win64/lib/sndfile.lib" $TEMP_PATH/lib/sndfile.lib - # Copy DLL's into root for CPack cp $TEMP_PATH/bin/*.dll ../ diff --git a/cmake/FindLibSndFile.cmake b/cmake/FindLibSndFile.cmake deleted file mode 100644 index 6b381b1..0000000 --- a/cmake/FindLibSndFile.cmake +++ /dev/null @@ -1,19 +0,0 @@ -# Find libsndfile -FIND_PATH(LIBSNDFILE_INCLUDE_DIR sndfile.h) - -SET(LIBSNDFILE_NAMES ${LIBSNDFILE_NAMES} sndfile libsndfile) -FIND_LIBRARY(LIBSNDFILE_LIBRARY NAMES ${LIBSNDFILE_NAMES} PATH) - -IF(LIBSNDFILE_INCLUDE_DIR AND LIBSNDFILE_LIBRARY) - SET(LIBSNDFILE_FOUND TRUE) -ENDIF(LIBSNDFILE_INCLUDE_DIR AND LIBSNDFILE_LIBRARY) - -IF(LIBSNDFILE_FOUND) - IF(NOT LibSndFile_FIND_QUIETLY) - MESSAGE(STATUS "Found LibSndFile: ${LIBSNDFILE_LIBRARY}") - ENDIF(NOT LibSndFile_FIND_QUIETLY) -ELSE(LIBSNDFILE_FOUND) - IF(LibSndFile_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find sndfile") - ENDIF(LibSndFile_FIND_REQUIRED) -ENDIF(LIBSNDFILE_FOUND) diff --git a/src/algebra.c b/libaptdec/algebra.c similarity index 56% rename from src/algebra.c rename to libaptdec/algebra.c index bc7b9f6..e922a1e 100644 --- a/src/algebra.c +++ b/libaptdec/algebra.c @@ -1,6 +1,6 @@ /* * aptdec - A lightweight FOSS (NOAA) APT decoder - * Copyright (C) 2019-2022 Xerbo (xerbo@protonmail.com) + * Copyright (C) 2019-2023 Xerbo (xerbo@protonmail.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,6 +18,7 @@ #include "algebra.h" +#include #include // Find the best linear equation to estimate the value of the @@ -51,6 +52,67 @@ linear_t linear_regression(const float *independent, const float *dependent, siz return (linear_t){a, b}; } -float linear_calc(float x, linear_t line) { return x * line.a + line.b; } +float standard_deviation(const float *data, size_t len) { + float mean = 0.0f; + for (size_t i = 0; i < len; i++) { + mean += data[i]; + } + mean /= (float)len; + + float deviation_mean = 0.0f; + for (size_t i = 0; i < len; i++) { + float deviation = data[i] - mean; + deviation_mean += deviation * deviation; + } + + return sqrtf(deviation_mean / (float)len); +} + +float sumf(const float *x, size_t len) { + float sum = 0.0f; + for (size_t i = 0; i < len; i++) { + sum += x[i]; + } + return sum; +} + +float meanf(const float *x, size_t len) { + return sumf(x, len) / (float)len; +} + +void normalizef(float *x, size_t len) { + float sum = sumf(x, len); + + for (size_t i = 0; i < len; i++) { + x[i] /= sum; + } +} + +static int sort_func(const void *a, const void *b) { + return *(float *)b > *(float *)a ? 1 : -1; +} + +float medianf(float *data, size_t len) { + qsort(data, len, sizeof(float), sort_func); -float quadratic_calc(float x, quadratic_t quadratic) { return x * x * quadratic.a + x * quadratic.b + quadratic.c; } + if (len % 2 == 0) { + return (data[len/2] + data[len/2 - 1]) / 2.0f; + } else { + return data[len/2]; + } +} + +float linear_calc(float x, linear_t line) { + return x * line.a + line.b; +} + +float quadratic_calc(float x, quadratic_t quadratic) { + return x*x * quadratic.a + x * quadratic.b + quadratic.c; +} + +float sincf(float x) { + if (x == 0.0f) { + return 1.0f; + } + return sinf(M_PIf * x) / (M_PIf * x); +} diff --git a/src/algebra.h b/libaptdec/algebra.h similarity index 71% rename from src/algebra.h rename to libaptdec/algebra.h index 08b46af..c7f654d 100644 --- a/src/algebra.h +++ b/libaptdec/algebra.h @@ -1,6 +1,6 @@ /* * aptdec - A lightweight FOSS (NOAA) APT decoder - * Copyright (C) 2019-2022 Xerbo (xerbo@protonmail.com) + * Copyright (C) 2019-2023 Xerbo (xerbo@protonmail.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,10 +16,13 @@ * along with this program. If not, see . */ -#ifndef APTDEC_ALGEBRA_H -#define APTDEC_ALGEBRA_H +#ifndef LIBAPTDEC_ALGEBRA_H_ +#define LIBAPTDEC_ALGEBRA_H_ #include +#define M_PIf 3.14159265358979323846f +#define M_TAUf (M_PIf * 2.0f) + // A linear equation in the form of y(x) = ax + b typedef struct { float a, b; @@ -31,8 +34,16 @@ typedef struct { } quadratic_t; linear_t linear_regression(const float *independent, const float *dependent, size_t len); +float standard_deviation(const float *data, size_t len); +float sumf(const float *x, size_t len); +float meanf(const float *x, size_t len); +void normalizef(float *x, size_t len); + +// NOTE: Modifies input array +float medianf(float *data, size_t len); float linear_calc(float x, linear_t line); float quadratic_calc(float x, quadratic_t quadratic); +float sincf(float x); #endif diff --git a/libaptdec/calibration.c b/libaptdec/calibration.c new file mode 100644 index 0000000..0bec609 --- /dev/null +++ b/libaptdec/calibration.c @@ -0,0 +1,121 @@ +/* + * aptdec - A lightweight FOSS (NOAA) APT decoder + * Copyright (C) 2019-2023 Xerbo (xerbo@protonmail.com) + * + * This program 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 . + */ + +#include "calibration.h" + +#include "util.h" + +// clang-format off +const calibration_t calibration[3] = { + { + .name = "NOAA-15", + .prt = { + { 1.36328e-06f, 0.051045f, 276.60157f }, // PRT 1 + { 1.47266e-06f, 0.050909f, 276.62531f }, // PRT 2 + { 1.47656e-06f, 0.050907f, 276.67413f }, // PRT 3 + { 1.47656e-06f, 0.050966f, 276.59258f } // PRT 4 + }, + .visible = { + { + .low = { 0.0568f, -2.1874f }, + .high = { 0.1633f, -54.9928f }, + .cutoff = 496.0f + }, { + .low = { 0.0596f, -2.4096f }, + .high = { 0.1629f, -55.2436f }, + .cutoff = 511.0f + } + }, + .rad = { + { 925.4075f, 0.337810f, 0.998719f }, // Channel 4 + { 839.8979f, 0.304558f, 0.999024f }, // Channel 5 + { 2695.9743f, 1.621256f, 0.998015f } // Channel 3B + }, + .cor = { + { -4.50f, { 0.0004524f, -0.0932f, 4.76f } }, // Channel 4 + { -3.61f, { 0.0002811f, -0.0659f, 3.83f } }, // Channel 5 + { 0.0f, { 0.0f, 0.0f , 0.0f } } // Channel 3B + } + }, { + .name = "NOAA-18", + .prt = { + { 1.657e-06f, 0.05090f, 276.601f }, // PRT 1 + { 1.482e-06f, 0.05101f, 276.683f }, // PRT 2 + { 1.313e-06f, 0.05117f, 276.565f }, // PRT 3 + { 1.484e-06f, 0.05103f, 276.615f } // PRT 4 + }, + .visible = { + { + .low = { 0.06174f, -2.434f }, + .high = { 0.1841f, -63.31f }, + .cutoff = 501.54f + }, { + .low = { 0.07514f, -2.960f }, + .high = { 0.2254f, -78.55f }, + .cutoff = 500.40f + } + }, + .rad = { + { 928.1460f, 0.436645f, 0.998607f }, // Channel 4 + { 833.2532f, 0.253179f, 0.999057f }, // Channel 5 + { 2659.7952f, 1.698704f, 0.996960f } // Channel 3B + }, + .cor = { + { -5.53f, { 0.00052337f, -0.11069f, 5.82f } }, // Channel 4 + { -2.22f, { 0.00017715f, -0.04360f, 2.67f } }, // Channel 5 + { 0.0f, { 0.0f, 0.0f, 0.0f } } // Channel 3B + } + }, { + .name = "NOAA-19", + .prt = { + { 1.405783e-06f, 0.051111f, 276.6067f }, // PRT 1 + { 1.496037e-06f, 0.051090f, 276.6119f }, // PRT 2 + { 1.496990e-06f, 0.051033f, 276.6311f }, // PRT 3 + { 1.493110e-06f, 0.051058f, 276.6268f } // PRT 4 + }, + .visible = { + { + .low = { 0.05555f, -2.159f }, + .high = { 0.1639f, -56.33f }, + .cutoff = 496.43f + }, { + .low = { 0.06614f, -2.565f }, + .high = { 0.1970f, -68.01f }, + .cutoff = 500.37f + } + }, + .rad = { + { 928.9f, 0.53959f, 0.998534f }, // Channel 4 + { 831.9f, 0.36064f, 0.998913f }, // Channel 5 + { 2670.0f, 1.67396f, 0.997364f } // Channel 3B + }, + .cor = { + { -5.49f, { 0.00054668f, -0.11187f, 5.70f } }, // Channel 4 + { -3.39f, { 0.00024985f, -0.05991f, 3.58f } }, // Channel 5 + { 0.0f, { 0.0f, 0.0f, 0.0f } } // Channel 3B + } + } +}; + +calibration_t get_calibration(apt_satellite_t satid) { + switch (satid) { + case NOAA15: return calibration[0]; + case NOAA18: return calibration[1]; + default: return calibration[2]; + } +} diff --git a/src/calibration.h b/libaptdec/calibration.h similarity index 87% rename from src/calibration.h rename to libaptdec/calibration.h index 5580dd6..46313e2 100644 --- a/src/calibration.h +++ b/libaptdec/calibration.h @@ -1,6 +1,6 @@ /* * aptdec - A lightweight FOSS (NOAA) APT decoder - * Copyright (C) 2019-2022 Xerbo (xerbo@protonmail.com) + * Copyright (C) 2019-2023 Xerbo (xerbo@protonmail.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,9 +16,11 @@ * along with this program. If not, see . */ -#ifndef APTDEC_CALIBRATION_H -#define APTDEC_CALIBRATION_H +#ifndef LIBAPTDEC_CALIBRATION_H_ +#define LIBAPTDEC_CALIBRATION_H_ + #include "algebra.h" +#include typedef struct { char *name; @@ -50,6 +52,6 @@ static const float C1 = 1.1910427e-5f; // Second radiation constant (cm-K) static const float C2 = 1.4387752f; -calibration_t get_calibration(int satid); +calibration_t get_calibration(apt_satellite_t satid); #endif diff --git a/libaptdec/color.c b/libaptdec/color.c new file mode 100644 index 0000000..43bacb2 --- /dev/null +++ b/libaptdec/color.c @@ -0,0 +1,94 @@ +/* + * aptdec - A lightweight FOSS (NOAA) APT decoder + * Copyright (C) 2019-2023 Xerbo (xerbo@protonmail.com) + * + * This program 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 . + */ + +#include +#include "util.h" + +#define MCOMPOSITE(m1, a1, m2, a2) (m1 * a1 + m2 * a2 * (1 - a1)) + +// clang-format off + +apt_rgb_t apt_gradient(const uint32_t *gradient, uint8_t val) { + return (apt_rgb_t) { + (gradient[val] & 0x00FF0000) >> 16, + (gradient[val] & 0x0000FF00) >> 8, + (gradient[val] & 0x000000FF) + }; +} + +apt_rgb_t apt_composite_rgb(apt_rgb_t top, float top_alpha, apt_rgb_t bottom, float bottom_alpha) { + return (apt_rgb_t) { + MCOMPOSITE(top.r, top_alpha, bottom.r, bottom_alpha), + MCOMPOSITE(top.g, top_alpha, bottom.g, bottom_alpha), + MCOMPOSITE(top.b, top_alpha, bottom.b, bottom_alpha) + }; +} + +// Taken from WXtoImg +const uint32_t temperature_gradient[256] = { + 0x45008F, 0x460091, 0x470092, 0x480094, 0x490096, 0x4A0098, 0x4B009B, + 0x4D009D, 0x4E00A0, 0x5000A2, 0x5100A5, 0x5200A7, 0x5400AA, 0x5600AE, + 0x5700B1, 0x5800B4, 0x5A00B7, 0x5C00BA, 0x5E00BD, 0x5F00C0, 0x6100C4, + 0x6400C8, 0x6600CB, 0x6800CE, 0x6900D1, 0x6800D4, 0x6500D7, 0x6300DA, + 0x6100DD, 0x5D00E1, 0x5B00E4, 0x5900E6, 0x5600E9, 0x5300EB, 0x5000EE, + 0x4D00F0, 0x4900F3, 0x4700FC, 0x4300FA, 0x3100BF, 0x200089, 0x200092, + 0x1E0095, 0x1B0097, 0x19009A, 0x17009C, 0x15009E, 0x1200A0, 0x0F00A3, + 0x0F02A5, 0x0E06A8, 0x0E0AAB, 0x0E0DAD, 0x0E11B1, 0x0D15B4, 0x0D18B7, + 0x0D1CBA, 0x0B21BD, 0x0A25C0, 0x0A29C3, 0x092DC6, 0x0833CA, 0x0736CD, + 0x073BD0, 0x0741D3, 0x0545D6, 0x044BD9, 0x0450DC, 0x0355DE, 0x025DE2, + 0x0161E5, 0x0066E7, 0x006CEA, 0x0072EC, 0x0078EE, 0x007DF0, 0x0082F3, + 0x008DFC, 0x0090FA, 0x0071BF, 0x005489, 0x005C91, 0x006194, 0x006496, + 0x006897, 0x006D99, 0x00719B, 0x00759D, 0x00799F, 0x007EA0, 0x0082A2, + 0x0087A4, 0x008CA6, 0x0092AA, 0x0096AC, 0x009CAE, 0x00A2B1, 0x00A6B3, + 0x00AAB5, 0x00ADB7, 0x00B1BA, 0x00B6BE, 0x00BAC0, 0x00BEC2, 0x00C2C5, + 0x00C6C6, 0x00CAC9, 0x00CCCA, 0x00CFCB, 0x00D2CC, 0x00D4CC, 0x00D6CC, + 0x00D9CB, 0x00DBCB, 0x00DECB, 0x00E0CB, 0x00E2CC, 0x00EAD2, 0x00EACF, + 0x00B9A4, 0x008E7A, 0x01947C, 0x049779, 0x079975, 0x099B71, 0x0D9D6B, + 0x109F67, 0x12A163, 0x15A35F, 0x17A559, 0x1AA855, 0x1DAA50, 0x20AC4B, + 0x24AF45, 0x28B241, 0x2BB53B, 0x2EB835, 0x31BA30, 0x34BD2B, 0x39BF24, + 0x3FC117, 0x49C508, 0x4FC801, 0x4FCA00, 0x4ECD00, 0x4ECF00, 0x4FD200, + 0x54D500, 0x5DD800, 0x68DB00, 0x6EDD00, 0x74DF00, 0x7AE200, 0x7FE400, + 0x85E700, 0x8BE900, 0x8FEB00, 0x9BF300, 0x9EF200, 0x7EBB00, 0x608A00, + 0x689200, 0x6D9500, 0x719600, 0x759800, 0x7B9A00, 0x7F9D00, 0x839F00, + 0x87A100, 0x8CA200, 0x8FA500, 0x92A700, 0x96A900, 0x9AAD00, 0x9DB000, + 0xA1B200, 0xA5B500, 0xA9B700, 0xADBA00, 0xB2BD00, 0xB6BF00, 0xBBC300, + 0xBFC600, 0xC3C800, 0xC8CB00, 0xCCCE00, 0xD0D100, 0xD3D200, 0xD5D400, + 0xD9D400, 0xDCD400, 0xDED500, 0xE1D500, 0xE3D500, 0xE6D400, 0xE8D100, + 0xEACE00, 0xF2CF00, 0xF2CA00, 0xBB9900, 0x8A6E00, 0x927200, 0x957200, + 0x977100, 0x9A7000, 0x9C6E00, 0x9E6D00, 0xA06B00, 0xA36A00, 0xA56800, + 0xA86700, 0xAB6600, 0xAE6500, 0xB26300, 0xB46100, 0xB75F00, 0xBA5D00, + 0xBD5C00, 0xC05900, 0xC35700, 0xC65400, 0xCA5000, 0xCD4D00, 0xD04A00, + 0xD34700, 0xD64300, 0xD94000, 0xDC3D00, 0xDE3900, 0xE23300, 0xE52F00, + 0xE72C00, 0xEA2800, 0xEC2300, 0xEF1F00, 0xF11A00, 0xF31400, 0xFB0F00, + 0xFA0D00, 0xC10500, 0x8E0000, 0x970000, 0x9B0000, 0x9E0000, 0xA10000, + 0xA50000, 0xA90000, 0xAD0000, 0xB10000, 0xB60000, 0xBA0000, 0xBD0000, + 0xC20000, 0xC80000, 0xCC0000, 0xCC0000 +}; + +// Taken from WXtoImg +const uint32_t precipitation_gradient[58] = { + 0x088941, 0x00C544, 0x00D12C, 0x00E31C, 0x00F906, 0x14FF00, 0x3EFF00, + 0x5DFF00, 0x80FF00, 0xABFF00, 0xCDFE00, 0xF8FF00, 0xFFE600, 0xFFB800, + 0xFF9800, 0xFF7500, 0xFF4900, 0xFE2600, 0xFF0400, 0xDF0000, 0xA80000, + 0x870000, 0x5A0000, 0x390000, 0x110000, 0x0E1010, 0x232222, 0x333333, + 0x414141, 0x535353, 0x606060, 0x6E6E6E, 0x808080, 0x8E8E8E, 0xA0A0A0, + 0xAEAEAE, 0xC0C0C0, 0xCECECE, 0xDCDCDC, 0xEFEFEF, 0xFAFAFA, 0xFFFFFF, + 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, + 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, + 0xFFFFFF, 0xFFFFFF +}; diff --git a/libaptdec/dsp.c b/libaptdec/dsp.c new file mode 100644 index 0000000..3c5f486 --- /dev/null +++ b/libaptdec/dsp.c @@ -0,0 +1,237 @@ +/* + * aptdec - A lightweight FOSS (NOAA) APT decoder + * Copyright (C) 2004-2009 Thierry Leconte (F4DWV) 2019-2023 Xerbo (xerbo@protonmail.com) + * + * This program 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 . + */ + +#include +#include +#include +#include +#include +#include + +#include "filter.h" +#include "util.h" +#include "algebra.h" + +#define BUFFER_SIZE 16384 +#define LOW_PASS_SIZE 101 + +#define CARRIER_FREQ 2400.0f +#define MAX_CARRIER_OFFSET 10.0f + +typedef struct { + float alpha; + float beta; + float min_freq; + float max_freq; + + float freq; + float phase; +} pll_t; + +typedef struct { + float *ring_buffer; + size_t ring_size; + + float *taps; + size_t ntaps; +} fir_t; + +struct aptdec_t { + float sample_rate; + float sync_frequency; + + pll_t *pll; + fir_t *hilbert; + + float low_pass[LOW_PASS_SIZE]; +}; + +char *aptdec_get_version(void) { + return VERSION; +} + +fir_t *fir_init(size_t max_size, size_t ntaps) { + fir_t *fir = (fir_t *)malloc(sizeof(fir_t)); + fir->ntaps = ntaps; + fir->ring_size = max_size + ntaps; + fir->taps = (float *)malloc(ntaps * sizeof(float)); + fir->ring_buffer = (float *)malloc((max_size + ntaps) * sizeof(float)); + return fir; +} + +void fir_free(fir_t *fir) { + free(fir->ring_buffer); + free(fir->taps); + free(fir); +} + +pll_t *pll_init(float alpha, float beta, float min_freq, float max_freq, float sample_rate) { + pll_t *pll = (pll_t *)malloc(sizeof(pll_t)); + pll->alpha = alpha; + pll->beta = beta; + pll->min_freq = M_TAUf * min_freq / sample_rate; + pll->max_freq = M_TAUf * max_freq / sample_rate; + pll->phase = 0.0f; + pll->freq = 0.0f; + return pll; +} + +aptdec_t *aptdec_init(float sample_rate) { + if (sample_rate > 96000 || sample_rate < (CARRIER_FREQ + APT_IMG_WIDTH) * 2.0f) { + return NULL; + } + + aptdec_t *apt = (aptdec_t *)malloc(sizeof(aptdec_t)); + apt->sample_rate = sample_rate; + apt->sync_frequency = 1.0f; + + // PLL configuration + // https://www.trondeau.com/blog/2011/8/13/control-loop-gain-values.html + float damp = 0.7f; + float bw = 0.005f; + float alpha = (4.0f * damp * bw) / (1.0f + 2.0f * damp * bw + bw * bw); + float beta = (4.0f * bw * bw) / (1.0f + 2.0f * damp * bw + bw * bw); + apt->pll = pll_init(alpha, beta, CARRIER_FREQ-MAX_CARRIER_OFFSET, CARRIER_FREQ+MAX_CARRIER_OFFSET, sample_rate); + if (apt->pll == NULL) { + free(apt); + return NULL; + } + + // Hilbert transform + apt->hilbert = fir_init(BUFFER_SIZE, 31); + if (apt->hilbert == NULL) { + free(apt->pll); + free(apt); + return NULL; + } + design_hilbert(apt->hilbert->taps, apt->hilbert->ntaps); + + design_low_pass(apt->low_pass, apt->sample_rate, (2080.0f + CARRIER_FREQ) / 2.0f, LOW_PASS_SIZE); + + return apt; +} + +void aptdec_free(aptdec_t *apt) { + fir_free(apt->hilbert); + free(apt->pll); + free(apt); +} + +static complexf_t pll_work(pll_t *pll, complexf_t in) { + // Internal oscillator (90deg offset) + complexf_t osc = complex_build(cosf(pll->phase), -sinf(pll->phase)); + in = complex_multiply(in, osc); + + // Error detector + float error = cargf(in); + + // Loop filter (single pole IIR) + pll->freq += pll->beta * error; + pll->freq = clamp(pll->freq, pll->min_freq, pll->max_freq); + pll->phase += pll->freq + (pll->alpha * error); + pll->phase = remainderf(pll->phase, M_TAUf); + + return in; +} + +static int am_demod(aptdec_t *apt, float *out, size_t count, aptdec_callback_t callback, void *context) { + size_t read = callback(&apt->hilbert->ring_buffer[apt->hilbert->ntaps], count, context); + + for (size_t i = 0; i < read; i++) { + complexf_t sample = hilbert_transform(&apt->hilbert->ring_buffer[i], apt->hilbert->taps, apt->hilbert->ntaps); + out[i] = crealf(pll_work(apt->pll, sample)); + } + + memcpy(apt->hilbert->ring_buffer, &apt->hilbert->ring_buffer[read], apt->hilbert->ntaps*sizeof(float)); + + return read; +} + +static int get_pixels(aptdec_t *apt, float *out, size_t count, aptdec_callback_t callback, void *context) { + static float buffer[BUFFER_SIZE]; + static size_t n = BUFFER_SIZE; + static float offset = 0.0; + + float ratio = apt->sample_rate / (4160.0f * apt->sync_frequency); + + for (size_t i = 0; i < count; i++) { + // Get more samples if there are less than `LOW_PASS_SIZE` available + if (n + LOW_PASS_SIZE > BUFFER_SIZE) { + memcpy(buffer, &buffer[n], (BUFFER_SIZE-n) * sizeof(float)); + + size_t read = am_demod(apt, &buffer[BUFFER_SIZE-n], n, callback, context); + if (read != n) { + return i; + } + n = 0; + } + + out[i] = interpolating_convolve(&buffer[n], apt->low_pass, LOW_PASS_SIZE, offset); + + // Do not question the sacred code + int shift = ceilf(ratio - offset); + offset = shift + offset - ratio; + n += shift; + } + + return count; +} + +const float sync_pattern[] = {-1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, + 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 0}; +#define SYNC_SIZE (sizeof(sync_pattern)/sizeof(sync_pattern[0])) + +// Get an entire row of pixels, aligned with sync markers +int aptdec_getrow(aptdec_t *apt, float *row, aptdec_callback_t callback, void *context) { + static float pixels[APT_IMG_WIDTH + SYNC_SIZE + 2]; + + // Wrap the circular buffer + memcpy(pixels, &pixels[APT_IMG_WIDTH], (SYNC_SIZE + 2) * sizeof(float)); + // Get a lines worth (APT_IMG_WIDTH) of samples + if (get_pixels(apt, &pixels[SYNC_SIZE + 2], APT_IMG_WIDTH, callback, context) != APT_IMG_WIDTH) { + return 0; + } + + // Error detector + float left = FLT_MIN; + float middle = FLT_MIN; + float right = FLT_MIN; + size_t phase = 0; + + for (size_t i = 0; i < APT_IMG_WIDTH; i++) { + float _left = convolve(&pixels[i + 0], sync_pattern, SYNC_SIZE); + float _middle = convolve(&pixels[i + 1], sync_pattern, SYNC_SIZE); + float _right = convolve(&pixels[i + 2], sync_pattern, SYNC_SIZE); + if (_middle > middle) { + left = _left; + middle = _middle; + right = _right; + phase = i + 1; + } + } + + // Frequency + float bias = (left / middle) - (right / middle); + apt->sync_frequency = 1.0f + bias / APT_IMG_WIDTH / 2.0f; + + // Phase + memcpy(&row[APT_IMG_WIDTH], &pixels[phase], (APT_IMG_WIDTH - phase) * sizeof(float)); + memcpy(&row[APT_IMG_WIDTH - phase], pixels, phase * sizeof(float)); + + return 1; +} diff --git a/libaptdec/effects.c b/libaptdec/effects.c new file mode 100644 index 0000000..f8eff14 --- /dev/null +++ b/libaptdec/effects.c @@ -0,0 +1,175 @@ +/* + * aptdec - A lightweight FOSS (NOAA) APT decoder + * Copyright (C) 2019-2023 Xerbo (xerbo@protonmail.com) + * + * This program 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 . + */ + +#include +#include +#include +#include + +#include "algebra.h" +#include "util.h" +#include "filter.h" + +void apt_equalize(apt_image_t *img, apt_region_t region) { + // Plot histogram + size_t histogram[256] = {0}; + for (size_t y = 0; y < img->rows; y++) { + for (size_t x = 0; x < region.width; x++) { + histogram[img->data[y * APT_IMG_WIDTH + x + region.offset]]++; + } + } + + // Calculate cumulative frequency + size_t sum = 0, cf[256] = {0}; + for (int i = 0; i < 256; i++) { + sum += histogram[i]; + cf[i] = sum; + } + + // Apply histogram + int area = img->rows * region.width; + for (size_t y = 0; y < img->rows; y++) { + for (size_t x = 0; x < region.width; x++) { + int k = (int)img->data[y * APT_IMG_WIDTH + x + region.offset]; + img->data[y * APT_IMG_WIDTH + x + region.offset] = (255.0f / area) * cf[k]; + } + } +} + +// Brightness calibrate, including telemetry +static void image_apply_linear(uint8_t *data, int rows, int offset, int width, linear_t regr) { + for (int y = 0; y < rows; y++) { + for (int x = 0; x < width; x++) { + float pv = linear_calc(data[y * APT_IMG_WIDTH + x + offset], regr); + data[y * APT_IMG_WIDTH + x + offset] = clamp_int(roundf(pv), 0, 255); + } + } +} + +void apt_stretch(apt_image_t *img, apt_region_t region) { + // Plot histogram + size_t histogram[256] = { 0 }; + for (size_t y = 0; y < img->rows; y++) { + for (size_t x = 0; x < region.width; x++) { + histogram[img->data[y*APT_IMG_WIDTH + x + region.offset]]++; + } + } + + // Calculate cumulative frequency + size_t sum = 0; + size_t cf[256] = { 0 }; + for (size_t i = 0; i < 256; i++) { + sum += histogram[i]; + cf[i] = sum; + } + + // Find min/max points (1st percentile) + int min = -1, max = -1; + for (size_t i = 0; i < 256; i++) { + if ((float)cf[i] / (float)sum < 0.01f && min == -1) { + min = i; + } + if ((float)cf[i] / (float)sum > 0.99f && max == -1) { + max = i; + break; + } + } + + float a = 255.0f / (max - min); + float b = a * -min; + image_apply_linear(img->data, img->rows, region.offset, region.width, (linear_t){a, b}); +} + + + +// Median denoise (with deviation threshold) +void apt_denoise(apt_image_t *img, apt_region_t region) { + for (size_t y = 1; y < img->rows - 1; y++) { + for (size_t x = 1; x < region.width - 1; x++) { + float pixels[9]; + int pixeln = 0; + for (int y2 = -1; y2 < 2; y2++) { + for (int x2 = -1; x2 < 2; x2++) { + pixels[pixeln++] = img->data[(y + y2) * APT_IMG_WIDTH + (x + region.offset) + x2]; + } + } + + if (standard_deviation(pixels, 9) > 15) { + img->data[y * APT_IMG_WIDTH + x + region.offset] = medianf(pixels, 9); + } + } + } +} + +// Flips a channel, for northbound passes +void apt_flip(apt_image_t *img, apt_region_t region) { + for (size_t y = 1; y < img->rows; y++) { + for (int x = 1; x < ceil(region.width / 2.0f); x++) { + // Flip top-left & bottom-right + swap_uint8( + &img->data[(img->rows - y) * APT_IMG_WIDTH + region.offset + x], + &img->data[y * APT_IMG_WIDTH + region.offset + (region.width - x)] + ); + } + } +} + +// Calculate crop to remove noise from the start and end of an image +#define NOISE_THRESH 2600.0 + +#include "filter.h" + +int apt_crop(apt_image_t *img) { + const float sync_pattern[] = {-1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, + 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 0}; + + float spc_rows[img->rows]; + int startCrop = 0; + int endCrop = img->rows; + + for (size_t y = 0; y < img->rows; y++) { + float temp[39]; + for (size_t i = 0; i < 39; i++) { + temp[i] = img->data[y * APT_IMG_WIDTH + i]; + } + + spc_rows[y] = convolve(temp, &sync_pattern[0], 39); + } + + // Find ends + for (size_t y = 0; y < img->rows - 1; y++) { + if (spc_rows[y] > NOISE_THRESH) { + endCrop = y; + } + } + for (size_t y = img->rows; y > 0; y--) { + if (spc_rows[y] > NOISE_THRESH) { + startCrop = y; + } + } + + printf("Crop rows: %i -> %i\n", startCrop, endCrop); + + // Ignore the noisy rows at the end + img->rows = (endCrop - startCrop); + + // Remove the noisy rows at start + memmove(img->data, &img->data[startCrop * APT_IMG_WIDTH], img->rows * APT_IMG_WIDTH * sizeof(float)); + + return startCrop; +} diff --git a/libaptdec/filter.c b/libaptdec/filter.c new file mode 100644 index 0000000..e45fbf8 --- /dev/null +++ b/libaptdec/filter.c @@ -0,0 +1,109 @@ +/* + * aptdec - A lightweight FOSS (NOAA) APT decoder + * Copyright (C) 2019-2023 Xerbo (xerbo@protonmail.com) + * + * This program 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 . + */ + +#include "filter.h" + +#include +// SSE2 intrinsics +#ifdef __x86_64__ +#include +#endif + +#include "algebra.h" + +// Blackman window +// https://en.wikipedia.org/wiki/Window_function#Blackman_window +static float blackman(float n, size_t ntaps) { + n = (M_PIf * n) / (float)(ntaps - 1); + return 0.42f - 0.5f*cosf(2 * n) + 0.08f*cosf(4 * n); +} + +// Sinc low pass with blackman window. +// https://tomroelandts.com/articles/how-to-create-a-simple-low-pass-filter +void design_low_pass(float *taps, float samp_rate, float cutoff, size_t ntaps) { + for (size_t i = 0; i < ntaps; i++) { + int x = i - ntaps/2; + taps[i] = sincf(2.0f * cutoff/samp_rate * (float)x); + taps[i] *= blackman(i, ntaps); + } + + // Achieve unity gain + normalizef(taps, ntaps); +} + +// Hilbert filter with blackman window. +// https://www.recordingblogs.com/wiki/hilbert-transform +void design_hilbert(float *taps, size_t ntaps) { + for (size_t i = 0; i < ntaps; i++) { + int x = i - ntaps/2; + + if (x % 2 == 0) { + taps[i] = 0.0f; + } else { + taps[i] = 2.0f / (M_PIf * (float)x); + taps[i] *= blackman(i, ntaps); + } + } + + // Achieve unity gain + normalizef(taps, ntaps); +} + +float convolve(const float *in, const float *taps, size_t len) { +#ifdef __SSE2__ + __m128 sum = _mm_setzero_ps(); + + size_t i; + for (i = 0; i < len - 3; i += 4) { + __m128 _taps = _mm_loadu_ps(&taps[i]); + __m128 _in = _mm_loadu_ps(&in[i]); + sum = _mm_add_ps(sum, _mm_mul_ps(_taps, _in)); + } + + float residual = 0.0f; + for (; i < len; i++) { + residual += in[i] * taps[i]; + } + + __attribute__((aligned(16))) float _sum[4]; + _mm_store_ps(_sum, sum); + return _sum[0] + _sum[1] + _sum[2] + _sum[3] + residual; +#else + float sum = 0.0f; + for (size_t i = 0; i < len; i++) { + sum += in[i] * taps[i]; + } + + return sum; +#endif +} + +complexf_t hilbert_transform(const float *in, const float *taps, size_t len) { + return complex_build(in[len / 2], convolve(in, taps, len)); +} + +float interpolating_convolve(const float *in, const float *taps, size_t len, float offset) { + float _taps[len]; + + for (size_t i = 0; i < len; i++) { + float next = (i == len-1) ? 0.0f : taps[i+1]; + _taps[i] = taps[i]*(1.0f-offset) + next*offset; + } + + return convolve(in, _taps, len); +} diff --git a/src/filter.h b/libaptdec/filter.h similarity index 66% rename from src/filter.h rename to libaptdec/filter.h index 124a59b..d80a7df 100644 --- a/src/filter.h +++ b/libaptdec/filter.h @@ -1,6 +1,6 @@ /* * aptdec - A lightweight FOSS (NOAA) APT decoder - * Copyright (C) 2019-2022 Xerbo (xerbo@protonmail.com) + * Copyright (C) 2019-2023 Xerbo (xerbo@protonmail.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,15 +16,27 @@ * along with this program. If not, see . */ +#ifndef LIBAPTDEC_FILTER_H_ +#define LIBAPTDEC_FILTER_H_ + #include #include #ifdef _MSC_VER typedef _Fcomplex complexf_t; +#define complex_build(real, imag) _FCbuild(real, imag) +#define complex_multiply(a, b) _FCmulcc(a, b) #else typedef complex float complexf_t; +#define complex_build(real, imag) ((real) + (imag)*I) +#define complex_multiply(a, b) ((a) * (b)) #endif +void design_low_pass(float *taps, float samp_rate, float cutoff, size_t ntaps); +void design_hilbert(float *taps, size_t ntaps); + float convolve(const float *in, const float *taps, size_t len); complexf_t hilbert_transform(const float *in, const float *taps, size_t len); -float interpolating_convolve(const float *in, const float *taps, size_t len, float offset, float delta); +float interpolating_convolve(const float *in, const float *taps, size_t len, float offset); + +#endif diff --git a/libaptdec/image.c b/libaptdec/image.c new file mode 100644 index 0000000..7093563 --- /dev/null +++ b/libaptdec/image.c @@ -0,0 +1,236 @@ +/* + * aptdec - A lightweight FOSS (NOAA) APT decoder + * Copyright (C) 2004-2009 Thierry Leconte (F4DWV) 2019-2023 Xerbo (xerbo@protonmail.com) + * + * This program 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 . + */ + +#include +#include +#include +#include +#include + +#include "algebra.h" +#include +#include "util.h" +#include "calibration.h" + +#define APT_COUNT_RATIO (1023.0f/255.0f) + +apt_image_t apt_image_clone(apt_image_t img) { + apt_image_t _img = img; + _img.data = (uint8_t *)malloc(APT_IMG_WIDTH * img.rows); + memcpy(_img.data, img.data, APT_IMG_WIDTH * img.rows); + return _img; +} + +static void decode_telemetry(const float *data, size_t rows, size_t offset, float *wedges) { + // Calculate row average + float telemetry_rows[rows]; + for (size_t y = 0; y < rows; y++) { + telemetry_rows[y] = meanf(&data[y*APT_IMG_WIDTH + offset + APT_CH_WIDTH], APT_TELEMETRY_WIDTH); + } + + // Calculate relative telemetry offset (via step detection, i.e. wedge 8 to 9) + size_t telemetry_offset = 0; + float max_difference = 0.0f; + for (size_t y = APT_WEDGE_HEIGHT; y <= rows - APT_WEDGE_HEIGHT; y++) { + float difference = sumf(&telemetry_rows[y - APT_WEDGE_HEIGHT], APT_WEDGE_HEIGHT) - sumf(&telemetry_rows[y], APT_WEDGE_HEIGHT); + + // Find the maximum difference + if (difference > max_difference) { + max_difference = difference; + telemetry_offset = (y + 64) % APT_FRAME_LEN; + } + } + + // Find the least noisy frame (via standard deviation) + float best_noise = FLT_MAX; + size_t best_frame = 0; + for (size_t y = telemetry_offset; y < rows; y += APT_FRAME_LEN) { + float noise = 0.0f; + for (size_t i = 0; i < APT_FRAME_WEDGES; i++) { + noise += standard_deviation(&telemetry_rows[y + i*APT_WEDGE_HEIGHT], APT_WEDGE_HEIGHT); + } + + if (noise < best_noise) { + best_noise = noise; + best_frame = y; + } + } + + for (size_t i = 0; i < APT_FRAME_WEDGES; i++) { + wedges[i] = meanf(&telemetry_rows[best_frame + i*APT_WEDGE_HEIGHT], APT_WEDGE_HEIGHT); + } +} + +static float average_spc(apt_image_t *img, size_t offset) { + float rows[img->rows]; + float average = 0.0f; + for (size_t y = 0; y < img->rows; y++) { + float row_average = 0.0f; + for (size_t x = 0; x < APT_SPC_WIDTH; x++) { + row_average += img->data[y*APT_IMG_WIDTH + offset - APT_SPC_WIDTH + x]; + } + row_average /= (float)APT_SPC_WIDTH; + + rows[y] = row_average; + average += row_average; + } + average /= (float)img->rows; + + float weighted_average = 0.0f; + size_t n = 0; + for (size_t y = 0; y < img->rows; y++) { + if (fabsf(rows[y] - average) < 50.0f) { + weighted_average += rows[y]; + n++; + } + } + + return weighted_average / (float)n; +} + +apt_image_t apt_normalize(const float *data, size_t rows, apt_satellite_t satellite, int *error) { + apt_image_t img; + img.rows = rows; + img.satellite = satellite; + + *error = 0; + if (rows < APTDEC_NORMALIZE_ROWS) { + *error = -1; + return img; + } + + // Decode and average wedges + float wedges[APT_FRAME_WEDGES]; + float wedges_cha[APT_FRAME_WEDGES]; + float wedges_chb[APT_FRAME_WEDGES]; + decode_telemetry(data, rows, APT_CHA_OFFSET, wedges_cha); + decode_telemetry(data, rows, APT_CHB_OFFSET, wedges_chb); + for (size_t i = 0; i < APT_FRAME_WEDGES; i++) { + wedges[i] = (wedges_cha[i] + wedges_chb[i]) / 2.0f; + } + + // Calculate normalization + const float reference[9] = { 31, 63, 95, 127, 159, 191, 223, 255, 0 }; + linear_t normalization = linear_regression(wedges, reference, 9); + if (normalization.a < 0.0f) { + *error = -1; + return img; + } + + // Normalize telemetry + for (size_t i = 0; i < APT_FRAME_WEDGES; i++) { + img.telemetry[0][i] = linear_calc(wedges_cha[i], normalization); + img.telemetry[1][i] = linear_calc(wedges_chb[i], normalization); + } + + // Decode channel ID wedges + img.ch[0] = roundf(img.telemetry[0][15] / 32.0f); + img.ch[1] = roundf(img.telemetry[1][15] / 32.0f); + if (img.ch[0] < 1 || img.ch[0] > 6) img.ch[0] = AVHRR_CHANNEL_UNKNOWN; + if (img.ch[1] < 1 || img.ch[1] > 6) img.ch[1] = AVHRR_CHANNEL_UNKNOWN; + + // Normalize and quantize image data + img.data = (uint8_t *)malloc(rows * APT_IMG_WIDTH); + for (size_t i = 0; i < rows * APT_IMG_WIDTH; i++) { + float count = linear_calc(data[i], normalization); + img.data[i] = clamp_int(roundf(count), 0, 255); + } + + // Get space brightness + img.space_view[0] = average_spc(&img, APT_CHA_OFFSET); + img.space_view[1] = average_spc(&img, APT_CHB_OFFSET); + + return img; +} + +static void make_thermal_lut(apt_image_t *img, avhrr_channel_t ch, int satellite, float *lut) { + ch -= 4; + const calibration_t calibration = get_calibration(satellite); + const float Ns = calibration.cor[ch].Ns; + const float Vc = calibration.rad[ch].vc; + const float A = calibration.rad[ch].A; + const float B = calibration.rad[ch].B; + + // Compute PRT temperature + float T[4]; + for (size_t n = 0; n < 4; n++) { + T[n] = quadratic_calc(img->telemetry[1][n + 9] * APT_COUNT_RATIO, calibration.prt[n]); + } + + float Tbb = meanf(T, 4); // Blackbody temperature + float Tbbstar = A + Tbb * B; // Effective blackbody temperature + + float Nbb = C1 * pow(Vc, 3) / (expf(C2 * Vc / Tbbstar) - 1.0f); // Blackbody radiance + + float Cs = img->space_view[1] * APT_COUNT_RATIO; + float Cb = img->telemetry[1][14] * APT_COUNT_RATIO; + + for (size_t i = 0; i < 256; i++) { + float Nl = Ns + (Nbb - Ns) * (Cs - i * APT_COUNT_RATIO) / (Cs - Cb); // Linear radiance estimate + float Nc = quadratic_calc(Nl, calibration.cor[ch].quadratic); // Non-linear correction + float Ne = Nl + Nc; // Corrected radiance + + float Testar = C2 * Vc / logf(C1 * powf(Vc, 3) / Ne + 1.0); // Equivalent black body temperature + float Te = (Testar - A) / B; // Temperature (kelvin) + + // Convert to celsius + lut[i] = Te - 273.15; + } +} + +int apt_calibrate_thermal(apt_image_t *img, apt_region_t region) { + if (img->ch[1] != AVHRR_CHANNEL_4 && img->ch[1] != AVHRR_CHANNEL_5 && img->ch[1] != AVHRR_CHANNEL_3B) { + return 1; + } + + float lut[256]; + make_thermal_lut(img, img->ch[1], img->satellite, lut); + + for (size_t y = 0; y < img->rows; y++) { + for (size_t x = 0; x < region.width; x++) { + float temperature = lut[img->data[y * APT_IMG_WIDTH + region.offset + x]]; + img->data[y * APT_IMG_WIDTH + region.offset + x] = clamp_int(roundf((temperature + 100.0) / 160.0 * 255.0), 0, 255); + } + } + + return 0; +} + +static float calibrate_pixel_visible(float value, int channel, calibration_t cal) { + if (value > cal.visible[channel].cutoff) { + return linear_calc(value * APT_COUNT_RATIO, cal.visible[channel].high) / 100.0f * 255.0f; + } else { + return linear_calc(value * APT_COUNT_RATIO, cal.visible[channel].low) / 100.0f * 255.0f; + } +} + +int apt_calibrate_visible(apt_image_t *img, apt_region_t region) { + if (img->ch[0] != AVHRR_CHANNEL_1 && img->ch[0] != AVHRR_CHANNEL_2) { + return 1; + } + + calibration_t calibration = get_calibration(img->satellite); + for (size_t y = 0; y < img->rows; y++) { + for (size_t x = 0; x < region.width; x++) { + float albedo = calibrate_pixel_visible(img->data[y * APT_IMG_WIDTH + region.offset + x], img->ch[0]-1, calibration); + img->data[y * APT_IMG_WIDTH + region.offset + x] = clamp_int(roundf(albedo), 0, 255); + } + } + + return 0; +} diff --git a/libaptdec/include/aptdec.h b/libaptdec/include/aptdec.h new file mode 100644 index 0000000..669537c --- /dev/null +++ b/libaptdec/include/aptdec.h @@ -0,0 +1,169 @@ +/* + * aptdec - A lightweight FOSS (NOAA) APT decoder + * Copyright (C) 2004-2009 Thierry Leconte (F4DWV) 2019-2023 Xerbo (xerbo@protonmail.com) + * Copyright (C) 2021 Jon Beniston (M7RCE) + * + * This program 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 . + */ + +#ifndef APTDEC_H_ +#define APTDEC_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__GNUC__) && (__GNUC__ >= 4) +# define APTDEC_API __attribute__((visibility("default"))) +#elif defined(_MSC_VER) +# ifdef APTDEC_API_EXPORT +# define APTDEC_API __declspec(dllexport) +# else +# define APTDEC_API __declspec(dllimport) +# endif +#else +# define APTDEC_API +#endif + +// Height of a single telemetry wedge +#define APT_WEDGE_HEIGHT 8 +// Numbers of wedges in a frame +#define APT_FRAME_WEDGES 16 +// Height of a telemetry frame +#define APT_FRAME_LEN (APT_WEDGE_HEIGHT * APT_FRAME_WEDGES) + +// Width of the overall image +#define APT_IMG_WIDTH 2080 +// Width of sync marker +#define APT_SYNC_WIDTH 39 +// Width of space view +#define APT_SPC_WIDTH 47 +// Width of telemetry +#define APT_TELEMETRY_WIDTH 45 +// Width of a single video channel +#define APT_CH_WIDTH 909 + +// Offset to channel A video data +#define APT_CHA_OFFSET (APT_SYNC_WIDTH + APT_SPC_WIDTH) +// Offset to channel B video data +#define APT_CHB_OFFSET (APT_SYNC_WIDTH + APT_SPC_WIDTH + APT_CH_WIDTH + APT_TELEMETRY_WIDTH + APT_SYNC_WIDTH + APT_SPC_WIDTH) + +// Number of rows needed for apt_normalize to (reliably) work +#define APTDEC_NORMALIZE_ROWS (APT_FRAME_LEN * 2) + +// Channel 1: visible (0.58-0.68 um) +// Channel 2: near-IR (0.725-1.0 um) +// Channel 3A: near-IR (1.58-1.64 um) +// Channel 3B: mid-infrared (3.55-3.93 um) +// Channel 4: thermal-infrared (10.3-11.3 um) +// Channel 5: thermal-infrared (11.5-12.5 um) +typedef enum apt_channel { + AVHRR_CHANNEL_UNKNOWN, + AVHRR_CHANNEL_1, + AVHRR_CHANNEL_2, + AVHRR_CHANNEL_3A, + AVHRR_CHANNEL_4, + AVHRR_CHANNEL_5, + AVHRR_CHANNEL_3B +} avhrr_channel_t; + +typedef enum apt_satellite { + NOAA15, + NOAA18, + NOAA19 +} apt_satellite_t; + +typedef struct { + uint8_t *data; // Image data + size_t rows; // Number of rows + + // Telemetry + apt_satellite_t satellite; + avhrr_channel_t ch[2]; + float space_view[2]; + float telemetry[2][16]; +} apt_image_t; + +typedef struct { + uint8_t r, g, b; +} apt_rgb_t; + +typedef struct { + size_t offset; + size_t width; +} apt_region_t; + +typedef struct aptdec_t aptdec_t; + +// Callback function to get samples +// `context` is the same as passed to aptdec_getrow +typedef size_t (*aptdec_callback_t)(float *samples, size_t count, void *context); + +// Clone an apt_image_t struct +// Useful for calibration +apt_image_t apt_image_clone(apt_image_t img); + +// Returns version of libaptdec in git tag format +// i.e. v2.0.0 or v2.0.0-1-xxxxxx +APTDEC_API char *aptdec_get_version(void); + +// Create and destroy libaptdec instances +// If aptdec_init fails it will return NULL +APTDEC_API aptdec_t *aptdec_init(float sample_rate); +APTDEC_API void aptdec_free(aptdec_t *apt); + +// Normalize and quantize raw image data +// Data is arranged so that each row starts at APT_IMG_WIDTH*y +APTDEC_API apt_image_t apt_normalize(const float *data, size_t rows, apt_satellite_t satellite, int *error); + +// Get an entire row of pixels +// Requires that `row` has enough space to store APT_IMG_WIDTH*2 +// Returns 0 when `callback` return value != count +APTDEC_API int aptdec_getrow(aptdec_t *apt, float *row, aptdec_callback_t callback, void *context); + +// Calibrate channels +APTDEC_API int apt_calibrate_thermal(apt_image_t *img, apt_region_t region); +APTDEC_API int apt_calibrate_visible(apt_image_t *img, apt_region_t region); + +APTDEC_API void apt_denoise (apt_image_t *img, apt_region_t region); +APTDEC_API void apt_flip (apt_image_t *img, apt_region_t region); +APTDEC_API void apt_stretch (apt_image_t *img, apt_region_t region); +APTDEC_API void apt_equalize(apt_image_t *img, apt_region_t region); +APTDEC_API int apt_crop (apt_image_t *img); + +// Composite two RGB values as layers, in most cases bottom_a will be 1.0f +APTDEC_API apt_rgb_t apt_composite_rgb(apt_rgb_t top, float top_a, apt_rgb_t bottom, float bottom_a); + +// Apply a gradient such as temperature_gradient +// If gradient is less than 256 elements it is the callers responsibility +// that `val` does not exceed the length of the gradient +APTDEC_API apt_rgb_t apt_gradient(const uint32_t *gradient, uint8_t val); + +static const apt_region_t APT_REGION_CHA = { APT_CHA_OFFSET, APT_CH_WIDTH }; +static const apt_region_t APT_REGION_CHB = { APT_CHB_OFFSET, APT_CH_WIDTH }; +static const apt_region_t APT_REGION_CHA_FULL = { 0, APT_IMG_WIDTH/2 }; +static const apt_region_t APT_REGION_CHB_FULL = { APT_IMG_WIDTH/2, APT_IMG_WIDTH/2 }; +static const apt_region_t APT_REGION_FULL = { 0, APT_IMG_WIDTH }; + +extern const uint32_t temperature_gradient[256]; +extern const uint32_t precipitation_gradient[58]; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libaptdec/util.c b/libaptdec/util.c new file mode 100644 index 0000000..e2139bb --- /dev/null +++ b/libaptdec/util.c @@ -0,0 +1,40 @@ +/* + * aptdec - A lightweight FOSS (NOAA) APT decoder + * Copyright (C) 2019-2023 Xerbo (xerbo@protonmail.com) + * + * This program 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 . + */ + +#include "util.h" + +#include +#include + +float clamp(float x, float lo, float hi) { + if (x > hi) return hi; + if (x < lo) return lo; + return x; +} + +int clamp_int(int x, int lo, int hi) { + if (x > hi) return hi; + if (x < lo) return lo; + return x; +} + +void swap_uint8(uint8_t *a, uint8_t *b) { + uint8_t tmp = *a; + *a = *b; + *b = tmp; +} diff --git a/libaptdec/util.h b/libaptdec/util.h new file mode 100644 index 0000000..fbb1e1d --- /dev/null +++ b/libaptdec/util.h @@ -0,0 +1,29 @@ +/* + * aptdec - A lightweight FOSS (NOAA) APT decoder + * Copyright (C) 2019-2023 Xerbo (xerbo@protonmail.com) + * + * This program 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 . + */ + +#ifndef LIBAPTDEC_UTIL_H_ +#define LIBAPTDEC_UTIL_H_ + +#include + +float clamp(float x, float lo, float hi); +int clamp_int(int x, int lo, int hi); + +void swap_uint8(uint8_t *a, uint8_t *b); + +#endif diff --git a/luts/N19-HRPT-Falsecolor.png b/luts/N19-HRPT-Falsecolor.png new file mode 100644 index 0000000..eee6d89 Binary files /dev/null and b/luts/N19-HRPT-Falsecolor.png differ diff --git a/luts/WXtoImg-BD.png b/luts/WXtoImg-BD.png new file mode 100644 index 0000000..25a1f9a Binary files /dev/null and b/luts/WXtoImg-BD.png differ diff --git a/luts/WXtoImg-CC.png b/luts/WXtoImg-CC.png new file mode 100644 index 0000000..60c6065 Binary files /dev/null and b/luts/WXtoImg-CC.png differ diff --git a/luts/WXtoImg-EC.png b/luts/WXtoImg-EC.png new file mode 100644 index 0000000..e0f0348 Binary files /dev/null and b/luts/WXtoImg-EC.png differ diff --git a/luts/WXtoImg-HE.png b/luts/WXtoImg-HE.png new file mode 100644 index 0000000..7508910 Binary files /dev/null and b/luts/WXtoImg-HE.png differ diff --git a/luts/WXtoImg-HF.png b/luts/WXtoImg-HF.png new file mode 100644 index 0000000..89f5d6f Binary files /dev/null and b/luts/WXtoImg-HF.png differ diff --git a/luts/WXtoImg-JF.png b/luts/WXtoImg-JF.png new file mode 100644 index 0000000..7cfcebe Binary files /dev/null and b/luts/WXtoImg-JF.png differ diff --git a/luts/WXtoImg-JJ.png b/luts/WXtoImg-JJ.png new file mode 100644 index 0000000..f4dac33 Binary files /dev/null and b/luts/WXtoImg-JJ.png differ diff --git a/luts/WXtoImg-MB.png b/luts/WXtoImg-MB.png new file mode 100644 index 0000000..c4cc6d1 Binary files /dev/null and b/luts/WXtoImg-MB.png differ diff --git a/luts/WXtoImg-MD.png b/luts/WXtoImg-MD.png new file mode 100644 index 0000000..6a3dfc4 Binary files /dev/null and b/luts/WXtoImg-MD.png differ diff --git a/palettes/WXtoImg-N15-HVC.png b/luts/WXtoImg-N15-HVC.png similarity index 70% rename from palettes/WXtoImg-N15-HVC.png rename to luts/WXtoImg-N15-HVC.png index be33289..5ed0892 100644 Binary files a/palettes/WXtoImg-N15-HVC.png and b/luts/WXtoImg-N15-HVC.png differ diff --git a/palettes/WXtoImg-N18-HVC.png b/luts/WXtoImg-N18-HVC.png similarity index 70% rename from palettes/WXtoImg-N18-HVC.png rename to luts/WXtoImg-N18-HVC.png index ebfdbed..2cfe53a 100644 Binary files a/palettes/WXtoImg-N18-HVC.png and b/luts/WXtoImg-N18-HVC.png differ diff --git a/palettes/WXtoImg-N19-HVC.png b/luts/WXtoImg-N19-HVC.png similarity index 71% rename from palettes/WXtoImg-N19-HVC.png rename to luts/WXtoImg-N19-HVC.png index e6465ac..a1e8896 100644 Binary files a/palettes/WXtoImg-N19-HVC.png and b/luts/WXtoImg-N19-HVC.png differ diff --git a/luts/WXtoImg-NO.png b/luts/WXtoImg-NO.png new file mode 100644 index 0000000..bf3ea0d Binary files /dev/null and b/luts/WXtoImg-NO.png differ diff --git a/luts/WXtoImg-TA.png b/luts/WXtoImg-TA.png new file mode 100644 index 0000000..dfda195 Binary files /dev/null and b/luts/WXtoImg-TA.png differ diff --git a/luts/WXtoImg-ZA.png b/luts/WXtoImg-ZA.png new file mode 100644 index 0000000..e388023 Binary files /dev/null and b/luts/WXtoImg-ZA.png differ diff --git a/palettes/WXtoImg-class.png b/luts/WXtoImg-class.png similarity index 63% rename from palettes/WXtoImg-class.png rename to luts/WXtoImg-class.png index f247df7..3671566 100644 Binary files a/palettes/WXtoImg-class.png and b/luts/WXtoImg-class.png differ diff --git a/luts/WXtoImg-fire.png b/luts/WXtoImg-fire.png new file mode 100644 index 0000000..35b997a Binary files /dev/null and b/luts/WXtoImg-fire.png differ diff --git a/luts/WXtoImg-sea.png b/luts/WXtoImg-sea.png new file mode 100644 index 0000000..b32e677 Binary files /dev/null and b/luts/WXtoImg-sea.png differ diff --git a/palettes/N19-HRPT-Falsecolor.png b/palettes/N19-HRPT-Falsecolor.png deleted file mode 100644 index df1c1fd..0000000 Binary files a/palettes/N19-HRPT-Falsecolor.png and /dev/null differ diff --git a/palettes/WXtoImg-BD.png b/palettes/WXtoImg-BD.png deleted file mode 100644 index 0a15645..0000000 Binary files a/palettes/WXtoImg-BD.png and /dev/null differ diff --git a/palettes/WXtoImg-CC.png b/palettes/WXtoImg-CC.png deleted file mode 100644 index e4b3181..0000000 Binary files a/palettes/WXtoImg-CC.png and /dev/null differ diff --git a/palettes/WXtoImg-EC.png b/palettes/WXtoImg-EC.png deleted file mode 100644 index c0a0cd5..0000000 Binary files a/palettes/WXtoImg-EC.png and /dev/null differ diff --git a/palettes/WXtoImg-HE.png b/palettes/WXtoImg-HE.png deleted file mode 100644 index b89dfd1..0000000 Binary files a/palettes/WXtoImg-HE.png and /dev/null differ diff --git a/palettes/WXtoImg-HF.png b/palettes/WXtoImg-HF.png deleted file mode 100644 index 5998c71..0000000 Binary files a/palettes/WXtoImg-HF.png and /dev/null differ diff --git a/palettes/WXtoImg-JF.png b/palettes/WXtoImg-JF.png deleted file mode 100644 index 1ebefab..0000000 Binary files a/palettes/WXtoImg-JF.png and /dev/null differ diff --git a/palettes/WXtoImg-JJ.png b/palettes/WXtoImg-JJ.png deleted file mode 100644 index fb46ca8..0000000 Binary files a/palettes/WXtoImg-JJ.png and /dev/null differ diff --git a/palettes/WXtoImg-MB.png b/palettes/WXtoImg-MB.png deleted file mode 100644 index 07eeb50..0000000 Binary files a/palettes/WXtoImg-MB.png and /dev/null differ diff --git a/palettes/WXtoImg-MD.png b/palettes/WXtoImg-MD.png deleted file mode 100644 index f350074..0000000 Binary files a/palettes/WXtoImg-MD.png and /dev/null differ diff --git a/palettes/WXtoImg-NO.png b/palettes/WXtoImg-NO.png deleted file mode 100644 index 44febbc..0000000 Binary files a/palettes/WXtoImg-NO.png and /dev/null differ diff --git a/palettes/WXtoImg-TA.png b/palettes/WXtoImg-TA.png deleted file mode 100644 index adcec9b..0000000 Binary files a/palettes/WXtoImg-TA.png and /dev/null differ diff --git a/palettes/WXtoImg-ZA.png b/palettes/WXtoImg-ZA.png deleted file mode 100644 index f385fa4..0000000 Binary files a/palettes/WXtoImg-ZA.png and /dev/null differ diff --git a/palettes/WXtoImg-fire.png b/palettes/WXtoImg-fire.png deleted file mode 100644 index 7c1dc3f..0000000 Binary files a/palettes/WXtoImg-fire.png and /dev/null differ diff --git a/palettes/WXtoImg-sea.png b/palettes/WXtoImg-sea.png deleted file mode 100644 index d92837a..0000000 Binary files a/palettes/WXtoImg-sea.png and /dev/null differ diff --git a/src/apt.h b/src/apt.h deleted file mode 100644 index 2e4d9ba..0000000 --- a/src/apt.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * This file is part of Aptdec. - * Copyright (c) 2004-2009 Thierry Leconte (F4DWV), Xerbo (xerbo@protonmail.com) 2019-2022 - * Copyright (c) 2021 Jon Beniston (M7RCE) - * - * 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 . - * - */ - -#ifndef APT_H -#define APT_H - -#ifdef __cplusplus -extern "C" { -#endif - -#if defined(__GNUC__) && (__GNUC__ >= 4) -#define APT_API __attribute__((visibility("default"))) -#elif defined(_MSC_VER) -#ifdef APT_API_EXPORT -#define APT_API __declspec(dllexport) -#elif APT_API_STATIC -#define APT_API -#else if -#define APT_API __declspec(dllimport) -#endif -#else -#define APT_API -#endif - -// Maximum height of an APT image in number of rows -#define APT_MAX_HEIGHT 3000 -// Width in pixels of sync -#define APT_SYNC_WIDTH 39 -// Width in pixels of space -#define APT_SPC_WIDTH 47 -// Width in pixels of telemetry -#define APT_TELE_WIDTH 45 -// Width in pixels of a single channel image -#define APT_CH_WIDTH 909 -#define APT_FRAME_LEN 128 -#define APT_CH_OFFSET (APT_SYNC_WIDTH + APT_SPC_WIDTH + APT_CH_WIDTH + APT_TELE_WIDTH) -// Width in pixels of full frame, including sync, space, images and telemetry -#define APT_IMG_WIDTH 2080 -// Offset in pixels to channel A -#define APT_CHA_OFFSET (APT_SYNC_WIDTH + APT_SPC_WIDTH) -// Offset in pixels to channel B -#define APT_CHB_OFFSET (APT_SYNC_WIDTH + APT_SPC_WIDTH + APT_CH_WIDTH + APT_TELE_WIDTH + APT_SYNC_WIDTH + APT_SPC_WIDTH) -#define APT_TOTAL_TELE (APT_SYNC_WIDTH + APT_SPC_WIDTH + APT_TELE_WIDTH + APT_SYNC_WIDTH + APT_SPC_WIDTH + APT_TELE_WIDTH) - -// Number of rows required for apt_calibrate -#define APT_CALIBRATION_ROWS 192 -// Channel ID returned by apt_calibrate -// NOAA-15: https://nssdc.gsfc.nasa.gov/nmc/experiment/display.action?id=1998-030A-01 -// Channel 1: visible (0.58-0.68 um) -// Channel 2: near-IR (0.725-1.0 um) -// Channel 3A: near-IR (1.58-1.64 um) -// Channel 3B: mid-infrared (3.55-3.93 um) -// Channel 4: thermal-infrared (10.3-11.3 um) -// Channel 5: thermal-infrared (11.5-12.5 um) -typedef enum apt_channel { - APT_CHANNEL_UNKNOWN, - APT_CHANNEL_1, - APT_CHANNEL_2, - APT_CHANNEL_3A, - APT_CHANNEL_4, - APT_CHANNEL_5, - APT_CHANNEL_3B -} apt_channel_t; - -// Width in elements of apt_image_t.prow arrays -#define APT_PROW_WIDTH 2150 - -// apt_getpixelrow callback function to get audio samples. -// context is the same as passed to apt_getpixelrow. -typedef int (*apt_getsamples_t)(void *context, float *samples, int count); - -typedef struct { - float *prow[APT_MAX_HEIGHT]; // Row buffers - int nrow; // Number of rows - int zenith; // Row in image where satellite reaches peak elevation - apt_channel_t chA, chB; // ID of each channel - char name[256]; // Stripped filename - char *palette; // Filename of palette -} apt_image_t; - -typedef struct { - float r, g, b; -} apt_rgb_t; - -int APT_API apt_init(double sample_rate); -int APT_API apt_getpixelrow(float *pixelv, int nrow, int *zenith, int reset, apt_getsamples_t getsamples, void *context); - -void APT_API apt_histogramEqualise(float **prow, int nrow, int offset, int width); -void APT_API apt_linearEnhance(float **prow, int nrow, int offset, int width); -apt_channel_t APT_API apt_calibrate(float **prow, int nrow, int offset, int width); -void APT_API apt_denoise(float **prow, int nrow, int offset, int width); -void APT_API apt_flipImage(apt_image_t *img, int width, int offset); -int APT_API apt_cropNoise(apt_image_t *img); -void APT_API apt_calibrate_thermal(int satnum, apt_image_t *img, int offset, int width); -void APT_API apt_calibrate_visible(int satnum, apt_image_t *img, int offset, int width); -// Moved to apt_calibrate_thermal -#define apt_temperature apt_calibrate_thermal - -apt_rgb_t APT_API apt_applyPalette(char *palette, int val); -apt_rgb_t APT_API apt_RGBcomposite(apt_rgb_t top, float top_a, apt_rgb_t bottom, float bottom_a); - -extern char APT_API apt_TempPalette[256 * 3]; -extern char APT_API apt_PrecipPalette[58 * 3]; - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/argparse b/src/argparse deleted file mode 160000 index c612dc0..0000000 --- a/src/argparse +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c612dc03958cdbd538ca306d61853b643a435933 diff --git a/src/calibration.c b/src/calibration.c deleted file mode 100644 index e5b32fa..0000000 --- a/src/calibration.c +++ /dev/null @@ -1,101 +0,0 @@ -/* - * aptdec - A lightweight FOSS (NOAA) APT decoder - * Copyright (C) 2019-2022 Xerbo (xerbo@protonmail.com) - * - * This program 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 . - */ - -#include "calibration.h" - -#include "util.h" - -const calibration_t calibration[3] = {{.name = "NOAA-15", - .prt = - { - {1.36328e-06f, 0.051045f, 276.60157f}, // PRT 1 - {1.47266e-06f, 0.050909f, 276.62531f}, // PRT 2 - {1.47656e-06f, 0.050907f, 276.67413f}, // PRT 3 - {1.47656e-06f, 0.050966f, 276.59258f} // PRT 4 - }, - .visible = {{.low = {0.0568f, -2.1874f}, .high = {0.1633f, -54.9928f}, .cutoff = 496.0f}, - {.low = {0.0596f, -2.4096f}, .high = {0.1629f, -55.2436f}, .cutoff = 511.0f}}, - .rad = - { - {925.4075f, 0.337810f, 0.998719f}, // Channel 4 - {839.8979f, 0.304558f, 0.999024f}, // Channel 5 - {2695.9743f, 1.621256f, 0.998015f} // Channel 3B - }, - .cor = - { - {-4.50f, {0.0004524f, -0.0932f, 4.76f}}, // Channel 4 - {-3.61f, {0.0002811f, -0.0659f, 3.83f}}, // Channel 5 - {0.0f, {0.0f, 0.0f, 0.0f}} // Channel 3B - }}, - {.name = "NOAA-18", - .prt = - { - {1.657e-06f, 0.05090f, 276.601f}, // PRT 1 - {1.482e-06f, 0.05101f, 276.683f}, // PRT 2 - {1.313e-06f, 0.05117f, 276.565f}, // PRT 3 - {1.484e-06f, 0.05103f, 276.615f} // PRT 4 - }, - .visible = {{.low = {0.06174f, -2.434f}, .high = {0.1841f, -63.31f}, .cutoff = 501.54f}, - {.low = {0.07514f, -2.960f}, .high = {0.2254f, -78.55f}, .cutoff = 500.40f}}, - .rad = - { - {928.1460f, 0.436645f, 0.998607f}, // Channel 4 - {833.2532f, 0.253179f, 0.999057f}, // Channel 5 - {2659.7952f, 1.698704f, 0.996960f} // Channel 3B - }, - .cor = - { - {-5.53f, {0.00052337f, -0.11069f, 5.82f}}, // Channel 4 - {-2.22f, {0.00017715f, -0.04360f, 2.67f}}, // Channel 5 - {0.0f, {0.0f, 0.0f, 0.0f}} // Channel 3B - }}, - {.name = "NOAA-19", - .prt = - { - {1.405783e-06f, 0.051111f, 276.6067f}, // PRT 1 - {1.496037e-06f, 0.051090f, 276.6119f}, // PRT 2 - {1.496990e-06f, 0.051033f, 276.6311f}, // PRT 3 - {1.493110e-06f, 0.051058f, 276.6268f} // PRT 4 - }, - .visible = {{.low = {0.05555f, -2.159f}, .high = {0.1639f, -56.33f}, .cutoff = 496.43f}, - {.low = {0.06614f, -2.565f}, .high = {0.1970f, -68.01f}, .cutoff = 500.37f}}, - .rad = - { - {928.9f, 0.53959f, 0.998534f}, // Channel 4 - {831.9f, 0.36064f, 0.998913f}, // Channel 5 - {2670.0f, 1.67396f, 0.997364f} // Channel 3B - }, - .cor = { - {-5.49f, {0.00054668f, -0.11187f, 5.70f}}, // Channel 4 - {-3.39f, {0.00024985f, -0.05991f, 3.58f}}, // Channel 5 - {0.0f, {0.0f, 0.0f, 0.0f}} // Channel 3B - }}}; - -calibration_t get_calibration(int satid) { - switch (satid) { - case 15: - return calibration[0]; - case 18: - return calibration[1]; - case 19: - return calibration[2]; - default: - error("Invalid satid in get_calibration()"); /* following is only to shut up the compiler */ - return calibration[0]; - } -} diff --git a/src/color.c b/src/color.c deleted file mode 100644 index 50401c4..0000000 --- a/src/color.c +++ /dev/null @@ -1,83 +0,0 @@ -/* - * This file is part of Aptdec. - * Copyright (c) 2004-2009 Thierry Leconte (F4DWV), Xerbo (xerbo@protonmail.com) 2019-2022 - * - * 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 . - * - */ - -#include "color.h" - -#include "apt.h" - -apt_rgb_t apt_applyPalette(char *palette, int val) { - return (apt_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]}; -} - -apt_rgb_t apt_RGBcomposite(apt_rgb_t top, float top_a, apt_rgb_t bottom, float bottom_a) { - return (apt_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)}; -} - -// The "I totally didn't just steal this from WXtoImg" palette -char apt_TempPalette[256 * 3] = { - "\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 apt_PrecipPalette[58 * 3] = { - "\x8\x89\x41\x0\xc5\x44\x0\xd1\x2c\x0\xe3\x1c\x0\xf9\x6\x14\xff\x0\x3e\xff\x0\x5d\xff\x0" - "\x80\xff\x0\xab\xff\x0\xcd\xfe\x0\xf8\xff\x0\xff\xe6\x0\xff\xb8\x0\xff\x98\x0" - "\xff\x75\x0\xff\x49\x0\xfe\x26\x0\xff\x4\x0\xdf\x0\x0\xa8\x0\x0\x87\x0\x0" - "\x5a\x0\x0\x39\x0\x0\x11\x0\x0\xe\x10\x10\x23\x22\x22\x33\x33\x33\x41\x41\x41" - "\x53\x53\x53\x60\x60\x60\x6e\x6e\x6e\x80\x80\x80\x8e\x8e\x8e\xa0\xa0\xa0\xae\xae\xae" - "\xc0\xc0\xc0\xce\xce\xce\xdc\xdc\xdc\xef\xef\xef\xfa\xfa\xfa\xff\xff\xff\xff\xff\xff" - "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" - "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" - "\xff\xff\xff"}; diff --git a/src/color.h b/src/color.h deleted file mode 100644 index 1fc253e..0000000 --- a/src/color.h +++ /dev/null @@ -1,3 +0,0 @@ -#include "common.h" - -#define MCOMPOSITE(m1, a1, m2, a2) (m1 * a1 + m2 * a2 * (1 - a1)) diff --git a/src/common.h b/src/common.h deleted file mode 100644 index 7130d46..0000000 --- a/src/common.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of Aptdec. - * Copyright (c) 2004-2009 Thierry Leconte (F4DWV), Xerbo (xerbo@protonmail.com) 2019-2022 - * - * 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 . - * - */ - -// Constants -#define VERSION "Aptdec; (c) 2004-2009 Thierry Leconte F4DWV, Xerbo (xerbo@protonmail.com) 2019-2022" - -// Useful macros -#define CLIP(v, lo, hi) (v > hi ? hi : (v > lo ? v : lo)) -#define CONTAINS(str, char) (strchr(str, (int)char) != NULL) - -// Typedefs -#ifndef STRUCTS_DEFINED -#define STRUCTS_DEFINED -typedef struct { - char *type; // Output image type - char *effects; // Effects on the image - int satnum; // The satellite number - char *path; // Output directory - int realtime; // Realtime decoding - char *filename; // Output filename - char *palette; // Filename of palette - float gamma; // Gamma -} options_t; - -enum imagetypes { - Raw_Image = 'r', - Palleted = 'p', - Temperature = 't', - Channel_A = 'a', - Channel_B = 'b', - Distribution = 'd', - Visible = 'v' -}; -enum effects { - Crop_Telemetry = 't', - Histogram_Equalise = 'h', - Denoise = 'd', - Precipitation_Overlay = 'p', - Flip_Image = 'f', - Linear_Equalise = 'l', - Crop_Noise = 'c' -}; - -#endif diff --git a/src/dsp.c b/src/dsp.c deleted file mode 100644 index befffe7..0000000 --- a/src/dsp.c +++ /dev/null @@ -1,258 +0,0 @@ -/* - * aptdec - A lightweight FOSS (NOAA) APT decoder - * Copyright (C) 2004-2009 Thierry Leconte (F4DWV) 2019-2022 Xerbo (xerbo@protonmail.com) - * - * This program 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 . - */ - -#include -#include -#include -#include - -#include "apt.h" -#include "filter.h" -#include "taps.h" -#include "util.h" - -// Block sizes -#define BLKAMP 32768 -#define BLKIN 32768 - -#define CARRIER_FREQ 2400.0 -#define MAX_CARRIER_OFFSET 20.0 - -#define RSMULT 15 -#define Fi (APT_IMG_WIDTH * 2 * RSMULT) - -static float _sample_rate; - -static float offset = 0.0; -static float FreqLine = 1.0; - -static float oscillator_freq; -static float pll_alpha; -static float pll_beta; - -// Initalise and configure PLL -int apt_init(double sample_rate) { - if (sample_rate > Fi) return 1; - if (sample_rate < APT_IMG_WIDTH * 2) return -1; - _sample_rate = sample_rate; - - // Pll configuration - pll_alpha = 50 / _sample_rate; - pll_beta = pll_alpha * pll_alpha / 2.0; - oscillator_freq = CARRIER_FREQ / sample_rate; - - return 0; -} - -static float pll(complexf_t in) { - static float oscillator_phase = 0.0; - - // Internal oscillator -#ifdef _MSC_VER - complexf_t osc = _FCbuild(cos(oscillator_phase), -sin(oscillator_phase)); - in = _FCmulcc(in, osc); -#else - complexf_t osc = cos(oscillator_phase) + -sin(oscillator_phase) * I; - in *= osc; -#endif - - // Error detector - float error = cargf(in); - - // Adjust frequency and phase - oscillator_freq += pll_beta * error; - oscillator_freq = clamp_half(oscillator_freq, (CARRIER_FREQ + MAX_CARRIER_OFFSET) / _sample_rate); - oscillator_phase += M_TAUf * (pll_alpha * error + oscillator_freq); - oscillator_phase = remainderf(oscillator_phase, M_TAUf); - - return crealf(in); -} - -// Convert samples into pixels -static int getamp(float *ampbuff, int count, apt_getsamples_t getsamples, void *context) { - static float inbuff[BLKIN]; - static int idxin = 0; - static int nin = 0; - - for (int n = 0; n < count; n++) { - float I2, Q; - - // Get some more samples when needed - if (nin < HILBERT_FILTER_SIZE * 2 + 2) { - // Number of samples read - int res; - memmove(inbuff, &(inbuff[idxin]), nin * sizeof(float)); - idxin = 0; - - // Read some samples - res = getsamples(context, &(inbuff[nin]), BLKIN - nin); - nin += res; - - // Make sure there is enough samples to continue - if (nin < HILBERT_FILTER_SIZE * 2 + 2) return n; - } - - // Process read samples into a brightness value - complexf_t sample = hilbert_transform(&inbuff[idxin], hilbert_filter, HILBERT_FILTER_SIZE); - ampbuff[n] = pll(sample); - - // Increment current sample - idxin++; - nin--; - } - - return count; -} - -// Sub-pixel offsetting -int getpixelv(float *pvbuff, int count, apt_getsamples_t getsamples, void *context) { - // Amplitude buffer - static float ampbuff[BLKAMP]; - static int nam = 0; - static int idxam = 0; - - float mult; - - // Gaussian resampling factor - mult = (float)Fi / _sample_rate * FreqLine; - int m = (int)(LOW_PASS_SIZE / mult + 1); - - for (int n = 0; n < count; n++) { - int shift; - - if (nam < m) { - int res; - memmove(ampbuff, &(ampbuff[idxam]), nam * sizeof(float)); - idxam = 0; - res = getamp(&(ampbuff[nam]), BLKAMP - nam, getsamples, context); - nam += res; - if (nam < m) return n; - } - - pvbuff[n] = interpolating_convolve(&(ampbuff[idxam]), low_pass, LOW_PASS_SIZE, offset, mult) * mult * 256.0; - - shift = ((int)floor((RSMULT - offset) / mult)) + 1; - offset = shift * mult + offset - RSMULT; - - idxam += shift; - nam -= shift; - } - - return count; -} - -// Get an entire row of pixels, aligned with sync markers -int apt_getpixelrow(float *pixelv, int nrow, int *zenith, int reset, apt_getsamples_t getsamples, void *context) { - static float pixels[APT_IMG_WIDTH + SYNC_PATTERN_SIZE]; - static size_t npv; - static int synced = 0; - static float max = 0.0; - static float minDoppler = 1000000000, previous = 0; - - if (reset) synced = 0; - - float corr, ecorr, lcorr; - int res; - - // Move the row buffer into the the image buffer - if (npv > 0) memmove(pixelv, pixels, npv * sizeof(float)); - - // Get the sync line - if (npv < SYNC_PATTERN_SIZE + 2) { - res = getpixelv(&(pixelv[npv]), SYNC_PATTERN_SIZE + 2 - npv, getsamples, context); - npv += res; - if (npv < SYNC_PATTERN_SIZE + 2) return 0; - } - - // Calculate the frequency offset - ecorr = convolve(pixelv, sync_pattern, SYNC_PATTERN_SIZE); - corr = convolve(&pixelv[1], sync_pattern, SYNC_PATTERN_SIZE - 1); - lcorr = convolve(&pixelv[2], sync_pattern, SYNC_PATTERN_SIZE - 2); - FreqLine = 1.0 + ((ecorr - lcorr) / corr / APT_IMG_WIDTH / 4.0); - - float val = fabs(lcorr - ecorr) * 0.25 + previous * 0.75; - if (val < minDoppler && nrow > 10) { - minDoppler = val; - *zenith = nrow; - } - previous = fabs(lcorr - ecorr); - - // The point in which the pixel offset is recalculated - if (corr < 0.75 * max) { - synced = 0; - FreqLine = 1.0; - } - max = corr; - - if (synced < 8) { - int mshift; - static int lastmshift; - - if (npv < APT_IMG_WIDTH + SYNC_PATTERN_SIZE) { - res = getpixelv(&(pixelv[npv]), APT_IMG_WIDTH + SYNC_PATTERN_SIZE - npv, getsamples, context); - npv += res; - if (npv < APT_IMG_WIDTH + SYNC_PATTERN_SIZE) return 0; - } - - // Test every possible position until we get the best result - mshift = 0; - for (int shift = 0; shift < APT_IMG_WIDTH; shift++) { - float corr; - - corr = convolve(&(pixelv[shift + 1]), sync_pattern, SYNC_PATTERN_SIZE); - if (corr > max) { - mshift = shift; - max = corr; - } - } - - // Stop rows dissapearing into the void - int mshiftOrig = mshift; - if (abs(lastmshift - mshift) > 3 && nrow != 0) { - mshift = 0; - } - lastmshift = mshiftOrig; - - // If we are already as aligned as we can get, just continue - if (mshift == 0) { - synced++; - } else { - memmove(pixelv, &(pixelv[mshift]), (npv - mshift) * sizeof(float)); - npv -= mshift; - synced = 0; - FreqLine = 1.0; - } - } - - // Get the rest of this row - if (npv < APT_IMG_WIDTH) { - res = getpixelv(&(pixelv[npv]), APT_IMG_WIDTH - npv, getsamples, context); - npv += res; - if (npv < APT_IMG_WIDTH) return 0; - } - - // Move the sync lines into the output buffer with the calculated offset - if (npv == APT_IMG_WIDTH) { - npv = 0; - } else { - memmove(pixels, &(pixelv[APT_IMG_WIDTH]), (npv - APT_IMG_WIDTH) * sizeof(float)); - npv -= APT_IMG_WIDTH; - } - - return 1; -} diff --git a/src/filter.c b/src/filter.c deleted file mode 100644 index 06746aa..0000000 --- a/src/filter.c +++ /dev/null @@ -1,62 +0,0 @@ -/* - * aptdec - A lightweight FOSS (NOAA) APT decoder - * Copyright (C) 2004-2009 Thierry Leconte (F4DWV) 2019-2022 Xerbo (xerbo@protonmail.com) - * - * This program 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 . - */ - -#include "filter.h" - -#include - -#include "util.h" - -float convolve(const float *in, const float *taps, size_t len) { - float sum = 0.0; - for (size_t i = 0; i < len; i++) { - sum += in[i] * taps[i]; - } - - return sum; -} - -complexf_t hilbert_transform(const float *in, const float *taps, size_t len) { - float i = 0.0; - float q = 0.0; - - for (size_t k = 0; k < len; k++) { - q += in[2 * k] * taps[k]; - i += in[2 * k]; - } - - i = in[len - 1] - (i / len); -#ifdef _MSC_VER - return _FCbuild(i, q); -#else - return i + q * I; -#endif -} - -float interpolating_convolve(const float *in, const float *taps, size_t len, float offset, float delta) { - float out = 0.0; - float n = offset; - - for (size_t i = 0; i < (len - 1) / delta - 1; n += delta, i++) { - int k = (int)floor(n); - float alpha = n - k; - - out += in[i] * (taps[k] * (1.0f - alpha) + taps[k + 1] * alpha); - } - return out; -} diff --git a/src/image.c b/src/image.c deleted file mode 100644 index 8c9cbc9..0000000 --- a/src/image.c +++ /dev/null @@ -1,388 +0,0 @@ -/* - * This file is part of Aptdec. - * Copyright (c) 2004-2009 Thierry Leconte (F4DWV), Xerbo (xerbo@protonmail.com) 2019-2022 - * - * 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 . - * - */ - -#include "image.h" - -#include -#include -#include -#include - -#include "algebra.h" -#include "apt.h" -#include "util.h" - -static linear_t compute_regression(float *wedges) { - // { 0.106, 0.215, 0.324, 0.433, 0.542, 0.652, 0.78, 0.87, 0.0 } - const float teleramp[9] = {31.07, 63.02, 94.96, 126.9, 158.86, 191.1, 228.62, 255.0, 0.0}; - - return linear_regression(wedges, teleramp, 9); -} - -static float tele[16]; -static float Cs; - -void apt_histogramEqualise(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)]++; - - // Calculate cumulative frequency - long sum = 0, cf[256] = {0}; - for (int i = 0; i < 255; i++) { - sum += histogram[i]; - cf[i] = sum; - } - - // Apply histogram - int area = nrow * width; - for (int y = 0; y < nrow; y++) { - for (int x = 0; x < width; x++) { - int k = (int)prow[y][x + offset]; - prow[y][x + offset] = (256.0f / area) * cf[k]; - } - } -} - -void apt_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.1) { - 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.0f; - prow[y][x + offset] = CLIP(prow[y][x + offset], 0.0f, 255.0f); - } - } -} - -// Brightness calibrate, including telemetry -void calibrateImage(float **prow, int nrow, int offset, int width, linear_t regr) { - offset -= APT_SYNC_WIDTH + APT_SPC_WIDTH; - - for (int y = 0; y < nrow; y++) { - for (int x = 0; x < width + APT_SYNC_WIDTH + APT_SPC_WIDTH + APT_TELE_WIDTH; x++) { - float pv = linear_calc(prow[y][x + offset], regr); - prow[y][x + offset] = CLIP(pv, 0, 255); - } - } -} - -float teleNoise(float *wedges) { - float pattern[9] = {31.07, 63.02, 94.96, 126.9, 158.86, 191.1, 228.62, 255.0, 0.0}; - float noise = 0; - for (int i = 0; i < 9; i++) noise += fabs(wedges[i] - pattern[i]); - - return noise; -} - -// Get telemetry data for thermal calibration -apt_channel_t apt_calibrate(float **prow, int nrow, int offset, int width) { - float teleline[APT_MAX_HEIGHT] = {0.0}; - float wedge[16]; - linear_t regr[APT_MAX_HEIGHT / APT_FRAME_LEN + 1]; - int telestart, mtelestart = 0; - int channel = -1; - - // The minimum rows required to decode a full frame - if (nrow < APT_CALIBRATION_ROWS) { - error_noexit("Telemetry decoding error, not enough rows"); - return APT_CHANNEL_UNKNOWN; - } - - // Calculate average of a row of telemetry - 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 - * difference in brightness, this can be used to find the current - * position within the telemetry. - */ - float max = 0.0f; - for (int n = nrow / 3 - 64; n < 2 * nrow / 3 - 64; n++) { - float df; - - // (sum 4px below) - (sum 4px above) - df = (float)((teleline[n - 4] + teleline[n - 3] + teleline[n - 2] + teleline[n - 1]) - - (teleline[n + 0] + teleline[n + 1] + teleline[n + 2] + teleline[n + 3])); - - // Find the maximum difference - if (df > max) { - mtelestart = n; - max = df; - } - } - - telestart = (mtelestart + 64) % APT_FRAME_LEN; - - // Make sure that theres at least one full frame in the image - if (nrow < telestart + APT_FRAME_LEN) { - error_noexit("Telemetry decoding error, not enough rows"); - return APT_CHANNEL_UNKNOWN; - } - - // Find the least noisy frame - float minNoise = -1; - int bestFrame = -1; - for (int n = telestart, k = 0; n < nrow - APT_FRAME_LEN; n += APT_FRAME_LEN, k++) { - int j; - - for (j = 0; j < 16; j++) { - int i; - - wedge[j] = 0.0; - for (i = 1; i < 7; i++) wedge[j] += teleline[n + j * 8 + i]; - wedge[j] /= 6; - } - - float noise = teleNoise(wedge); - if (noise < minNoise || minNoise == -1) { - minNoise = noise; - bestFrame = k; - - // Compute & apply regression on the wedges - regr[k] = compute_regression(wedge); - for (int j = 0; j < 16; j++) tele[j] = linear_calc(wedge[j], regr[k]); - - /* Compare the channel ID wedge to the reference - * wedges, the wedge with the closest match will - * be the channel ID - */ - float min = -1; - for (int j = 0; j < 6; j++) { - float df = (float)(tele[15] - tele[j]); - df *= df; - - if (df < min || min == -1) { - channel = j; - 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 + APT_FRAME_LEN; j++) { - float csline = 0.0; - for (int l = 3; l < 43; l++) csline += prow[n][l + offset - APT_SPC_WIDTH]; - csline /= 40.0; - - if (csline > 50.0) { - Cs += csline; - i++; - } - } - Cs = linear_calc((Cs / i), regr[k]); - } - } - - if (bestFrame == -1) { - error_noexit("Something has gone very wrong, please file a bug report"); - return APT_CHANNEL_UNKNOWN; - } - - calibrateImage(prow, nrow, offset, width, regr[bestFrame]); - - return (apt_channel_t)(channel + 1); -} - -extern float quick_select(float arr[], int n); - -// Biased median denoise, pretyt ugly -#define TRIG_LEVEL 40 -void apt_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 channel, for northbound passes -void apt_flipImage(apt_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; - } - } -} - -// Calculate crop to reomve noise from the start and end of an image -int apt_cropNoise(apt_image_t *img) { -#define NOISE_THRESH 180.0 - - // Average value of minute marker - float spc_rows[APT_MAX_HEIGHT] = {0.0}; - int startCrop = 0; - int endCrop = img->nrow; - for (int y = 0; y < img->nrow; y++) { - for (int x = 0; x < APT_SPC_WIDTH; x++) { - spc_rows[y] += img->prow[y][x + (APT_CHB_OFFSET - APT_SPC_WIDTH)]; - } - spc_rows[y] /= APT_SPC_WIDTH; - - // Skip minute markings - if (spc_rows[y] < 10) { - spc_rows[y] = spc_rows[y - 1]; - } - } - - // 3 row average - for (int y = 0; y < img->nrow; y++) { - spc_rows[y] = (spc_rows[y + 1] + spc_rows[y + 2] + spc_rows[y + 3]) / 3; - // img.prow[y][0] = spc_rows[y]; - } - - // Find ends - for (int y = 0; y < img->nrow - 1; y++) { - if (spc_rows[y] > NOISE_THRESH) { - endCrop = y; - } - } - for (int y = img->nrow; y > 0; y--) { - if (spc_rows[y] > NOISE_THRESH) { - startCrop = y; - } - } - - // printf("Crop rows: %i -> %i\n", startCrop, endCrop); - - // Remove the noisy rows at start - for (int y = 0; y < img->nrow - startCrop; y++) { - memmove(img->prow[y], img->prow[y + startCrop], sizeof(float) * APT_PROW_WIDTH); - } - - // Ignore the noisy rows at the end - img->nrow = (endCrop - startCrop); - - return startCrop; -} - -// --- Visible and Temperature Calibration --- // -#include "calibration.h" - -typedef struct { - float Nbb; - float Cs; - float Cb; - int ch; -} tempparam_t; - -// IR channel temperature compensation -tempparam_t tempcomp(float t[16], int ch, int satnum) { - tempparam_t tpr; - tpr.ch = ch - 4; - - const calibration_t calibration = get_calibration(satnum); - const float Vc = calibration.rad[tpr.ch].vc; - const float A = calibration.rad[tpr.ch].A; - const float B = calibration.rad[tpr.ch].B; - - // Compute PRT temperature - float T[4]; - for (size_t n = 0; n < 4; n++) { - T[n] = quadratic_calc(t[n + 9] * 4.0, calibration.prt[n]); - } - - float Tbb = (T[0] + T[1] + T[2] + T[3]) / 4.0; // Blackbody temperature - float Tbbstar = A + Tbb * B; // Effective blackbody temperature - - tpr.Nbb = C1 * pow(Vc, 3) / (expf(C2 * Vc / Tbbstar) - 1.0f); // Blackbody radiance - - tpr.Cs = 246.4 * 4.0; // FIXME - tpr.Cb = t[14] * 4.0; - return tpr; -} - -// IR channel temperature calibration -static float tempcal(float Ce, int satnum, tempparam_t tpr) { - const calibration_t calibration = get_calibration(satnum); - const float Ns = calibration.cor[tpr.ch].Ns; - const float Vc = calibration.rad[tpr.ch].vc; - const float A = calibration.rad[tpr.ch].A; - const float B = calibration.rad[tpr.ch].B; - - float Nl = Ns + (tpr.Nbb - Ns) * (tpr.Cs - Ce * 4.0) / (tpr.Cs - tpr.Cb); // Linear radiance estimate - float Nc = quadratic_calc(Nl, calibration.cor[tpr.ch].quadratic); // Non-linear correction - float Ne = Nl + Nc; // Corrected radiance - - float Testar = C2 * Vc / logf(C1 * powf(Vc, 3) / Ne + 1.0); // Equivlent black body temperature - float Te = (Testar - A) / B; // Temperature (kelvin) - - // Convert to celsius - Te -= 273.15; - // Rescale to 0-255 for -100°C to +60°C - return (Te + 100.0) / 160.0 * 255.0; -} - -// Temperature calibration wrapper -void apt_calibrate_thermal(int satnum, apt_image_t *img, int offset, int width) { - tempparam_t temp = tempcomp(tele, img->chB, satnum); - - for (int y = 0; y < img->nrow; y++) { - for (int x = 0; x < width; x++) { - img->prow[y][x + offset] = (float)tempcal(img->prow[y][x + offset], satnum, temp); - } - } -} - -float calibrate_pixel(float value, int channel, calibration_t cal) { - if (value > cal.visible[channel].cutoff) { - return linear_calc(value * 4.0f, cal.visible[channel].high) * 255.0f / 100.0f; - } else { - return linear_calc(value * 4.0f, cal.visible[channel].low) * 255.0f / 100.0f; - } -} - -void apt_calibrate_visible(int satnum, apt_image_t *img, int offset, int width) { - const calibration_t calibration = get_calibration(satnum); - int channel = img->chA - 1; - - for (int y = 0; y < img->nrow; y++) { - for (int x = 0; x < width; x++) { - img->prow[y][x + offset] = clamp(calibrate_pixel(img->prow[y][x + offset], channel, calibration), 255.0f, 0.0f); - } - } -} diff --git a/src/image.h b/src/image.h deleted file mode 100644 index 94d52eb..0000000 --- a/src/image.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "apt.h" -#include "common.h" diff --git a/src/libs/median.c b/src/libs/median.c deleted file mode 100644 index 2d4e510..0000000 --- a/src/libs/median.c +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 1ebc488..0000000 --- a/src/main.c +++ /dev/null @@ -1,316 +0,0 @@ -/* - * This file is part of Aptdec. - * Copyright (c) 2004-2009 Thierry Leconte (F4DWV), Xerbo (xerbo@protonmail.com) 2019-2022 - * - * 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 . - * - */ - -#include -#include -#include -#ifndef _MSC_VER -#include -#else -#include -#endif -#include -#include -#include -#include - -#include "apt.h" -#include "argparse/argparse.h" -#include "color.h" -#include "common.h" -#include "image.h" -#include "pngio.h" -#include "util.h" - -// Audio file -static SNDFILE *audioFile; -// Number of channels in audio file -int channels = 1; - -// Function declarations -static int initsnd(char *filename); -int getsamples(void *context, float *samples, int nb); -static int processAudio(char *filename, options_t *opts); - -#ifdef _MSC_VER -// Functions not supported by MSVC -static char *dirname(char *path) { - static char dir[MAX_PATH]; - _splitpath(path, NULL, dir, NULL, NULL); - return dir; -} - -static char *basename(char *path) { - static char base[MAX_PATH]; - _splitpath(path, NULL, NULL, base, NULL); - return base; -} -#endif - -int main(int argc, const char **argv) { - options_t opts = { - .type = "r", .effects = "", .satnum = 19, .path = ".", .realtime = 0, .filename = "", .palette = "", .gamma = 1.0}; - - static const char *const usages[] = { - "aptdec [options] [[--] sources]", - "aptdec [sources]", - NULL, - }; - - struct argparse_option options[] = { - OPT_HELP(), - OPT_GROUP("Image options"), - OPT_STRING('i', "image", &opts.type, "set output image type (see the README for a list)", NULL, 0, 0), - OPT_STRING('e', "effect", &opts.effects, "add an effect (see the README for a list)", NULL, 0, 0), - OPT_FLOAT('g', "gamma", &opts.gamma, "gamma adjustment (1.0 = off)", NULL, 0, 0), - - OPT_GROUP("Satellite options"), - OPT_INTEGER('s', "satellite", &opts.satnum, "satellite ID, must be between 15 and 19", NULL, 0, 0), - - OPT_GROUP("Paths"), - OPT_STRING('p', "palette", &opts.palette, "path to a palette", NULL, 0, 0), - OPT_STRING('o', "filename", &opts.filename, "filename of the output image", NULL, 0, 0), - OPT_STRING('d', "output", &opts.path, "output directory (must exist first)", NULL, 0, 0), - - OPT_GROUP("Misc"), - OPT_BOOLEAN('r', "realtime", &opts.realtime, "decode in realtime", NULL, 0, 0), - OPT_END(), - }; - - struct argparse argparse; - argparse_init(&argparse, options, usages, 0); - argparse_describe( - &argparse, "\nA lightweight FOSS NOAA APT satellite imagery decoder.", - "\nSee `README.md` for a full description of command line arguments and `LICENSE` for licensing conditions."); - argc = argparse_parse(&argparse, argc, argv); - - if (argc == 0) { - argparse_usage(&argparse); - } - - // Actually decode the files - for (int i = 0; i < argc; i++) { - char *filename = strdup(argv[i]); - processAudio(filename, &opts); - } - - return 0; -} - -static int processAudio(char *filename, options_t *opts) { - // Image info struct - apt_image_t img; - - // 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", "near-infrared", "thermal-infrared", "thermal-infrared", "mid-infrared"}}; - - // 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), "%255[^.].%31s", img.name, extension); - - if (opts->realtime) { - // Set output filename to current time when in realtime mode - time_t t; - time(&t); - strncpy(img.name, ctime(&t), 24); - - // Init a row writer - initWriter(opts, &img, APT_IMG_WIDTH, APT_MAX_HEIGHT, "Unprocessed realtime image", "r"); - } - - if (strcmp(extension, "png") == 0) { - // Read PNG into image buffer - printf("Reading %s\n", filename); - if (readRawImage(filename, img.prow, &img.nrow) == 0) { - exit(EPERM); - } - } else { - // Attempt to open the audio file - if (initsnd(filename) == 0) exit(EPERM); - - // Build image - // TODO: multithreading, would require some sort of input buffer - for (img.nrow = 0; img.nrow < APT_MAX_HEIGHT; img.nrow++) { - // Allocate memory for this row - img.prow[img.nrow] = (float *)malloc(sizeof(float) * APT_PROW_WIDTH); - - // Write into memory and break the loop when there are no more samples to read - if (apt_getpixelrow(img.prow[img.nrow], img.nrow, &img.zenith, (img.nrow == 0), getsamples, NULL) == 0) break; - - if (opts->realtime) pushRow(img.prow[img.nrow], APT_IMG_WIDTH); - - fprintf(stderr, "Row: %d\r", img.nrow); - fflush(stderr); - } - - // Close stream - sf_close(audioFile); - } - - if (opts->realtime) closeWriter(); - - printf("Total rows: %d\n", img.nrow); - - // Calibrate - img.chA = apt_calibrate(img.prow, img.nrow, APT_CHA_OFFSET, APT_CH_WIDTH); - img.chB = apt_calibrate(img.prow, img.nrow, APT_CHB_OFFSET, APT_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]); - - // Crop noise from start and end of image - if (CONTAINS(opts->effects, Crop_Noise)) { - img.zenith -= apt_cropNoise(&img); - } - - // Denoise - if (CONTAINS(opts->effects, Denoise)) { - apt_denoise(img.prow, img.nrow, APT_CHA_OFFSET, APT_CH_WIDTH); - apt_denoise(img.prow, img.nrow, APT_CHB_OFFSET, APT_CH_WIDTH); - } - - // Flip, for northbound passes - if (CONTAINS(opts->effects, Flip_Image)) { - apt_flipImage(&img, APT_CH_WIDTH, APT_CHA_OFFSET); - apt_flipImage(&img, APT_CH_WIDTH, APT_CHB_OFFSET); - } - - // Temperature - if (CONTAINS(opts->type, Temperature) && img.chB >= 4) { - // Create another buffer as to not modify the orignal - apt_image_t tmpimg = img; - for (int i = 0; i < img.nrow; i++) { - tmpimg.prow[i] = (float *)malloc(sizeof(float) * APT_PROW_WIDTH); - memcpy(tmpimg.prow[i], img.prow[i], sizeof(float) * APT_PROW_WIDTH); - } - - // Perform temperature calibration - apt_calibrate_thermal(opts->satnum, &tmpimg, APT_CHB_OFFSET, APT_CH_WIDTH); - ImageOut(opts, &tmpimg, APT_CHB_OFFSET, APT_CH_WIDTH, "Temperature", Temperature, (char *)apt_TempPalette); - } - - // Visible - if (CONTAINS(opts->type, Visible) && img.chA <= 2) { - // Create another buffer as to not modify the orignal - apt_image_t tmpimg = img; - for (int i = 0; i < img.nrow; i++) { - tmpimg.prow[i] = (float *)malloc(sizeof(float) * APT_PROW_WIDTH); - memcpy(tmpimg.prow[i], img.prow[i], sizeof(float) * APT_PROW_WIDTH); - } - - // Perform visible calibration - apt_calibrate_visible(opts->satnum, &tmpimg, APT_CHA_OFFSET, APT_CH_WIDTH); - ImageOut(opts, &tmpimg, APT_CHA_OFFSET, APT_CH_WIDTH, "Visible", Visible, NULL); - } - - // Linear equalise - if (CONTAINS(opts->effects, Linear_Equalise)) { - apt_linearEnhance(img.prow, img.nrow, APT_CHA_OFFSET, APT_CH_WIDTH); - apt_linearEnhance(img.prow, img.nrow, APT_CHB_OFFSET, APT_CH_WIDTH); - } - - // Histogram equalise - if (CONTAINS(opts->effects, Histogram_Equalise)) { - apt_histogramEqualise(img.prow, img.nrow, APT_CHA_OFFSET, APT_CH_WIDTH); - apt_histogramEqualise(img.prow, img.nrow, APT_CHB_OFFSET, APT_CH_WIDTH); - } - - // Raw image - if (CONTAINS(opts->type, Raw_Image)) { - 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, APT_IMG_WIDTH, desc, Raw_Image, NULL); - } - - // Palette image - if (CONTAINS(opts->type, Palleted)) { - img.palette = opts->palette; - strcpy(desc, "Palette composite"); - ImageOut(opts, &img, APT_CHA_OFFSET, APT_CH_WIDTH, desc, Palleted, NULL); - } - - // Channel A - if (CONTAINS(opts->type, Channel_A)) { - sprintf(desc, "%s (%s)", ch.id[img.chA], ch.name[img.chA]); - ImageOut(opts, &img, APT_CHA_OFFSET, APT_CH_WIDTH, desc, Channel_A, NULL); - } - - // Channel B - if (CONTAINS(opts->type, Channel_B)) { - sprintf(desc, "%s (%s)", ch.id[img.chB], ch.name[img.chB]); - ImageOut(opts, &img, APT_CHB_OFFSET, APT_CH_WIDTH, desc, Channel_B, NULL); - } - - return 1; -} - -float *samplebuf; -static int initsnd(char *filename) { - SF_INFO infwav; - int res; - - // Open audio file - infwav.format = 0; - audioFile = sf_open(filename, SFM_READ, &infwav); - if (audioFile == NULL) { - error_noexit("Could not file"); - return 0; - } - - res = apt_init(infwav.samplerate); - printf("Input file: %s\n", filename); - if (res < 0) { - error_noexit("Input sample rate too low"); - return 0; - } else if (res > 0) { - error_noexit("Input sample rate too high"); - return 0; - } - printf("Input sample rate: %d\n", infwav.samplerate); - - channels = infwav.channels; - samplebuf = (float *)malloc(sizeof(float) * 32768 * channels); - - return 1; -} - -// Read samples from the audio file -int getsamples(void *context, float *samples, int nb) { - (void)context; - if (channels == 1) { - return (int)sf_read_float(audioFile, samples, nb); - } else if (channels == 2) { - // Stereo channels are interleaved - int samplesRead = (int)sf_read_float(audioFile, samplebuf, nb * channels); - for (int i = 0; i < nb; i++) { - samples[i] = samplebuf[i * channels]; - } - return samplesRead / channels; - } else { - printf("Only mono and stereo input files are supported\n"); - exit(1); - } -} diff --git a/src/pngio.c b/src/pngio.c deleted file mode 100644 index a061b0a..0000000 --- a/src/pngio.c +++ /dev/null @@ -1,411 +0,0 @@ -/* - * This file is part of Aptdec. - * Copyright (c) 2004-2009 Thierry Leconte (F4DWV), Xerbo (xerbo@protonmail.com) 2019-2022 - * - * 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 . - * - */ - -#include "pngio.h" - -#include -#include -#include -#include -#include -#include - -#include "util.h" - -int readRawImage(char *filename, float **prow, int *nrow) { - FILE *fp = fopen(filename, "rb"); - printf("%s", filename); - if (!fp) { - error_noexit("Cannot open image"); - return 0; - } - - // Create reader - png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (!png) { - fclose(fp); - return 0; - } - png_infop info = png_create_info_struct(png); - if (!info) { - fclose(fp); - 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 != APT_IMG_WIDTH) { - error_noexit("Raw image must be 2080px wide"); - return 0; - } else if (bit_depth != 8) { - error_noexit("Raw image must have 8 bit color"); - return 0; - } else if (color_type != PNG_COLOR_TYPE_GRAY) { - error_noexit("Raw image must be grayscale"); - 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 readPalette(char *filename, apt_rgb_t **pixels) { - FILE *fp = fopen(filename, "rb"); - if (!fp) { - error_noexit("Cannot open palette"); - return 0; - } - - // Create reader - png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (!png) { - fclose(fp); - return 0; - } - png_infop info = png_create_info_struct(png); - if (!info) { - fclose(fp); - 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 != 256 && height != 256) { - error_noexit("Palette must be 256x256"); - return 0; - } else if (bit_depth != 8) { - error_noexit("Palette must be 8 bit color"); - return 0; - } else if (color_type != PNG_COLOR_TYPE_RGB) { - error_noexit("Palette must be RGB"); - 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 crow - for (int y = 0; y < height; y++) { - pixels[y] = (apt_rgb_t *)malloc(sizeof(apt_rgb_t) * width); - - for (int x = 0; x < width; x++) - pixels[y][x] = (apt_rgb_t){PNGrows[y][x * 3], PNGrows[y][x * 3 + 1], PNGrows[y][x * 3 + 2]}; - } - - return 1; -} - -void prow2crow(float **prow, int nrow, char *palette, apt_rgb_t **crow) { - for (int y = 0; y < nrow; y++) { - crow[y] = (apt_rgb_t *)malloc(sizeof(apt_rgb_t) * APT_IMG_WIDTH); - - for (int x = 0; x < APT_IMG_WIDTH; x++) { - if (palette == NULL) - crow[y][x].r = crow[y][x].g = crow[y][x].b = prow[y][x]; - else - crow[y][x] = apt_applyPalette(palette, prow[y][x]); - } - } -} - -int applyUserPalette(float **prow, int nrow, char *filename, apt_rgb_t **crow) { - apt_rgb_t *pal_row[256]; - if (!readPalette(filename, pal_row)) { - error_noexit("Could not read palette"); - return 0; - } - - for (int y = 0; y < nrow; y++) { - for (int x = 0; x < APT_CH_WIDTH; x++) { - int cha = CLIP(prow[y][x + APT_CHA_OFFSET], 0, 255); - int chb = CLIP(prow[y][x + APT_CHB_OFFSET], 0, 255); - crow[y][x + APT_CHA_OFFSET] = pal_row[chb][cha]; - } - } - - return 1; -} - -int ImageOut(options_t *opts, apt_image_t *img, int offset, int width, char *desc, char chid, char *palette) { - char outName[512]; - if (opts->filename == NULL || opts->filename[0] == '\0') { - sprintf(outName, "%s/%s-%c.png", opts->path, img->name, chid); - } else { - sprintf(outName, "%s/%s", opts->path, opts->filename); - } - - png_text meta[] = {{PNG_TEXT_COMPRESSION_NONE, "Software", VERSION, sizeof(VERSION)}, - {PNG_TEXT_COMPRESSION_NONE, "Channel", desc, sizeof(desc)}, - {PNG_TEXT_COMPRESSION_NONE, "Description", "NOAA satellite image", 20}}; - - // Parse image type - int greyscale = 1; - switch (chid) { - case Palleted: - greyscale = 0; - break; - case Temperature: - greyscale = 0; - break; - case Raw_Image: - break; - case Channel_A: - break; - case Channel_B: - break; - } - - // Parse effects - int crop_telemetry = 0; - for (unsigned long int i = 0; i < strlen(opts->effects); i++) { - switch (opts->effects[i]) { - case Crop_Telemetry: - if (width == 2080) { - width -= APT_TOTAL_TELE; - offset += APT_SYNC_WIDTH + APT_SPC_WIDTH; - crop_telemetry = 1; - } - break; - case Precipitation_Overlay: - greyscale = 0; - break; - case Flip_Image: - break; - case Denoise: - break; - case Histogram_Equalise: - break; - case Linear_Equalise: - break; - case Crop_Noise: - break; - default: { - char text[100]; - sprintf(text, "Unrecognised effect, \"%c\"", opts->effects[i]); - warning(text); - break; - } - } - } - - FILE *pngfile; - - // 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); - error_noexit("Could not create a PNG writer"); - return 0; - } - png_infop info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) { - png_destroy_write_struct(&png_ptr, (png_infopp)NULL); - error_noexit("Could not create a PNG writer"); - return 0; - } - - if (greyscale) { - // Greyscale image - png_set_IHDR(png_ptr, info_ptr, width, img->nrow, 8, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - } else { - // 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, meta, 3); - png_set_pHYs(png_ptr, info_ptr, 3636, 3636, PNG_RESOLUTION_METER); - - // Init I/O - pngfile = fopen(outName, "wb"); - if (!pngfile) { - error_noexit("Could not open PNG for writing"); - return 1; - } - png_init_io(png_ptr, pngfile); - png_write_info(png_ptr, info_ptr); - - // Move prow into crow, crow ~ color rows, if required - apt_rgb_t *crow[APT_MAX_HEIGHT]; - if (!greyscale) { - prow2crow(img->prow, img->nrow, palette, crow); - } - - // Apply a user provided color palette - if (chid == Palleted) { - applyUserPalette(img->prow, img->nrow, opts->palette, crow); - } - - // Precipitation overlay - if (CONTAINS(opts->effects, Precipitation_Overlay)) { - for (int y = 0; y < img->nrow; y++) { - for (int x = 0; x < APT_CH_WIDTH; x++) { - if (img->prow[y][x + APT_CHB_OFFSET] >= 198) - crow[y][x + APT_CHB_OFFSET] = crow[y][x + APT_CHA_OFFSET] = - apt_applyPalette(apt_PrecipPalette, img->prow[y][x + APT_CHB_OFFSET] - 198); - } - } - } - - printf("Writing %s", outName); - -// Float power macro (for gamma adjustment) -#define POWF(a, b) (b == 1.0 ? a : exp(b * log(a))) - float a = POWF(255, opts->gamma) / 255; - - // Build image - for (int y = 0; y < img->nrow; y++) { - png_color pix[APT_IMG_WIDTH]; // Color - png_byte mpix[APT_IMG_WIDTH]; // Mono - - int skip = 0; - for (int x = 0; x < width; x++) { - if (crop_telemetry && x == APT_CH_WIDTH) skip += APT_TELE_WIDTH + APT_SYNC_WIDTH + APT_SPC_WIDTH; - - if (greyscale) { - mpix[x] = POWF(img->prow[y][x + skip + offset], opts->gamma) / a; - } else { - pix[x] = (png_color){POWF(crow[y][x + skip + offset].r, opts->gamma) / a, - POWF(crow[y][x + skip + offset].g, opts->gamma) / a, - POWF(crow[y][x + skip + offset].b, opts->gamma) / a}; - } - } - - if (greyscale) { - png_write_row(png_ptr, (png_bytep)mpix); - } else { - 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; -} - -// TODO: clean up everthing below this comment -png_structp rt_png_ptr; -png_infop rt_info_ptr; -FILE *rt_pngfile; - -int initWriter(options_t *opts, apt_image_t *img, int width, int height, char *desc, char *chid) { - char outName[384]; - sprintf(outName, "%s/%s-%s.png", opts->path, img->name, chid); - - png_text meta[] = {{PNG_TEXT_COMPRESSION_NONE, "Software", VERSION, sizeof(VERSION)}, - {PNG_TEXT_COMPRESSION_NONE, "Channel", desc, sizeof(desc)}, - {PNG_TEXT_COMPRESSION_NONE, "Description", "NOAA satellite image", 20}}; - - // Create writer - rt_png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (!rt_png_ptr) { - png_destroy_write_struct(&rt_png_ptr, (png_infopp)NULL); - error_noexit("Could not create a PNG writer"); - return 0; - } - rt_info_ptr = png_create_info_struct(rt_png_ptr); - if (!rt_info_ptr) { - png_destroy_write_struct(&rt_png_ptr, (png_infopp)NULL); - error_noexit("Could not create a PNG writer"); - return 0; - } - - // Greyscale image - png_set_IHDR(rt_png_ptr, rt_info_ptr, width, height, 8, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, - PNG_FILTER_TYPE_DEFAULT); - - png_set_text(rt_png_ptr, rt_info_ptr, meta, 3); - - // Channel = 25cm wide - png_set_pHYs(rt_png_ptr, rt_info_ptr, 3636, 3636, PNG_RESOLUTION_METER); - - // Init I/O - rt_pngfile = fopen(outName, "wb"); - if (!rt_pngfile) { - error_noexit("Could not open PNG for writing"); - return 0; - } - png_init_io(rt_png_ptr, rt_pngfile); - png_write_info(rt_png_ptr, rt_info_ptr); - - // Turn off compression - png_set_compression_level(rt_png_ptr, 0); - - return 1; -} - -void pushRow(float *row, int width) { - png_byte pix[APT_IMG_WIDTH]; - for (int i = 0; i < width; i++) pix[i] = row[i]; - - png_write_row(rt_png_ptr, (png_bytep)pix); -} - -void closeWriter() { - png_write_end(rt_png_ptr, rt_info_ptr); - fclose(rt_pngfile); - png_destroy_write_struct(&rt_png_ptr, &rt_info_ptr); -} diff --git a/src/pngio.h b/src/pngio.h deleted file mode 100644 index f812b3e..0000000 --- a/src/pngio.h +++ /dev/null @@ -1,11 +0,0 @@ -#include "apt.h" -#include "common.h" - -int readRawImage(char *filename, float **prow, int *nrow); -int readPalette(char *filename, apt_rgb_t **pixels); -void prow2crow(float **prow, int nrow, char *palette, apt_rgb_t **crow); -int applyUserPalette(float **prow, int nrow, char *filename, apt_rgb_t **crow); -int ImageOut(options_t *opts, apt_image_t *img, int offset, int width, char *desc, char chid, char *palette); -int initWriter(options_t *opts, apt_image_t *img, int width, int height, char *desc, char *chid); -void pushRow(float *row, int width); -void closeWriter(); diff --git a/src/taps.h b/src/taps.h deleted file mode 100644 index f33b892..0000000 --- a/src/taps.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * aptdec - A lightweight FOSS (NOAA) APT decoder - * Copyright (C) 2004-2009 Thierry Leconte (F4DWV) 2019-2022 Xerbo (xerbo@protonmail.com) - * - * This program 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 . - */ - -static const float hilbert_filter[] = {0.0205361, 0.0219524, 0.0235785, 0.0254648, 0.0276791, 0.0303152, 0.0335063, - 0.0374482, 0.0424413, 0.0489708, 0.0578745, 0.0707355, 0.0909457, 0.127324, - 0.212207, 0.63662, -0.63662, -0.212207, -0.127324, -0.0909457, -0.0707355, - -0.0578745, -0.0489708, -0.0424413, -0.0374482, -0.0335063, -0.0303152, -0.0276791, - -0.0254648, -0.0235785, -0.0219524, -0.0205361}; -#define HILBERT_FILTER_SIZE (sizeof(hilbert_filter) / sizeof(hilbert_filter[0])) - -static const float low_pass[] = { - -3.37279e-04, -8.80292e-06, -3.96418e-04, -1.78544e-04, -5.27511e-04, -3.75376e-04, -6.95337e-04, -5.93148e-04, -8.79730e-04, - -8.15327e-04, -1.05669e-03, -1.01377e-03, -1.19836e-03, -1.15443e-03, -1.26937e-03, -1.20955e-03, -1.23904e-03, -1.15302e-03, - -1.08660e-03, -9.64235e-04, -8.02450e-04, -6.46202e-04, -3.95376e-04, -2.18096e-04, 1.11906e-04, 2.89567e-04, 6.67167e-04, - 8.19039e-04, 1.21725e-03, 1.30556e-03, 1.69365e-03, 1.68588e-03, 2.03277e-03, 1.90159e-03, 2.18455e-03, 1.90833e-03, - 2.12100e-03, 1.69052e-03, 1.77484e-03, 1.42542e-03, 1.18292e-03, 8.66979e-04, 5.54161e-04, 2.15793e-04, -1.11623e-04, - -4.35173e-04, -7.27194e-04, -9.91551e-04, -1.20407e-03, -1.37032e-03, -1.46991e-03, -1.51120e-03, -1.48008e-03, -1.39047e-03, - -1.23115e-03, -1.02128e-03, -7.60099e-04, -4.68008e-04, -1.46339e-04, 1.80867e-04, 5.11244e-04, 8.19243e-04, 1.09739e-03, - 1.32668e-03, 1.50632e-03, 1.61522e-03, 1.66246e-03, 1.62390e-03, 1.52430e-03, 1.34273e-03, 1.10736e-03, 8.10335e-04, - 4.76814e-04, 1.13622e-04, -2.64150e-04, -6.26595e-04, -9.95436e-04, -1.27846e-03, -1.54080e-03, -1.74292e-03, -1.86141e-03, - -1.89318e-03, -1.83969e-03, -1.69770e-03, -1.47938e-03, -1.18696e-03, -8.37003e-04, -4.39507e-04, -1.56907e-05, 4.19904e-04, - 8.43172e-04, 1.23827e-03, 1.58411e-03, 1.86382e-03, 2.06312e-03, 2.17177e-03, 2.18121e-03, 2.08906e-03, 1.89772e-03, - 1.61153e-03, 1.24507e-03, 8.13976e-04, 3.29944e-04, -1.74591e-04, -6.83619e-04, -1.17826e-03, -1.61659e-03, -2.00403e-03, - -2.29070e-03, -2.49179e-03, -2.56546e-03, -2.53448e-03, -2.37032e-03, -2.10060e-03, -1.72140e-03, -1.24542e-03, -7.15425e-04, - -1.24964e-04, 4.83736e-04, 1.08328e-03, 1.64530e-03, 2.14503e-03, 2.55400e-03, 2.85589e-03, 3.02785e-03, 3.06271e-03, - 2.95067e-03, 2.69770e-03, 2.30599e-03, 1.79763e-03, 1.18587e-03, 5.04003e-04, -2.23591e-04, -9.57591e-04, -1.66939e-03, - -2.31717e-03, -2.87636e-03, -3.31209e-03, -3.60506e-03, -3.73609e-03, -3.69208e-03, -3.44913e-03, -3.06572e-03, -2.50229e-03, - -1.80630e-03, -1.00532e-03, -1.22305e-04, 7.83910e-04, 1.69402e-03, 2.53826e-03, 3.30312e-03, 3.91841e-03, 4.38017e-03, - 4.63546e-03, 4.68091e-03, 4.50037e-03, 4.09614e-03, 3.47811e-03, 2.67306e-03, 1.70418e-03, 6.20542e-04, -5.36994e-04, - -1.70981e-03, -2.84712e-03, -3.88827e-03, -4.78659e-03, -5.48593e-03, -5.95049e-03, -6.14483e-03, -6.05118e-03, -5.65829e-03, - -4.97525e-03, -4.01796e-03, -2.82224e-03, -1.43003e-03, 1.00410e-04, 1.71169e-03, 3.31983e-03, 4.87796e-03, 6.23237e-03, - 7.31013e-03, 8.20642e-03, 8.67374e-03, 8.77681e-03, 8.43444e-03, 7.66794e-03, 6.46827e-03, 4.87294e-03, 2.92923e-03, - 6.98913e-04, -1.72126e-03, -4.24785e-03, -6.75380e-03, -9.13309e-03, -1.12532e-02, -1.30038e-02, -1.42633e-02, -1.49338e-02, - -1.49145e-02, -1.41484e-02, -1.25761e-02, -1.01870e-02, -6.97432e-03, -2.97910e-03, 1.75386e-03, 7.11899e-03, 1.30225e-02, - 1.93173e-02, 2.58685e-02, 3.24965e-02, 3.90469e-02, 4.53316e-02, 5.11931e-02, 5.64604e-02, 6.09924e-02, 6.46584e-02, - 6.73547e-02, 6.90049e-02, 6.97096e-02, 6.90049e-02, 6.73547e-02, 6.46584e-02, 6.09924e-02, 5.64604e-02, 5.11931e-02, - 4.53316e-02, 3.90469e-02, 3.24965e-02, 2.58685e-02, 1.93173e-02, 1.30225e-02, 7.11899e-03, 1.75386e-03, -2.97910e-03, - -6.97432e-03, -1.01870e-02, -1.25761e-02, -1.41484e-02, -1.49145e-02, -1.49338e-02, -1.42633e-02, -1.30038e-02, -1.12532e-02, - -9.13309e-03, -6.75380e-03, -4.24785e-03, -1.72126e-03, 6.98913e-04, 2.92923e-03, 4.87294e-03, 6.46827e-03, 7.66794e-03, - 8.43444e-03, 8.77681e-03, 8.67374e-03, 8.20642e-03, 7.31013e-03, 6.23237e-03, 4.87796e-03, 3.31983e-03, 1.71169e-03, - 1.00410e-04, -1.43003e-03, -2.82224e-03, -4.01796e-03, -4.97525e-03, -5.65829e-03, -6.05118e-03, -6.14483e-03, -5.95049e-03, - -5.48593e-03, -4.78659e-03, -3.88827e-03, -2.84712e-03, -1.70981e-03, -5.36994e-04, 6.20542e-04, 1.70418e-03, 2.67306e-03, - 3.47811e-03, 4.09614e-03, 4.50037e-03, 4.68091e-03, 4.63546e-03, 4.38017e-03, 3.91841e-03, 3.30312e-03, 2.53826e-03, - 1.69402e-03, 7.83910e-04, -1.22305e-04, -1.00532e-03, -1.80630e-03, -2.50229e-03, -3.06572e-03, -3.44913e-03, -3.69208e-03, - -3.73609e-03, -3.60506e-03, -3.31209e-03, -2.87636e-03, -2.31717e-03, -1.66939e-03, -9.57591e-04, -2.23591e-04, 5.04003e-04, - 1.18587e-03, 1.79763e-03, 2.30599e-03, 2.69770e-03, 2.95067e-03, 3.06271e-03, 3.02785e-03, 2.85589e-03, 2.55400e-03, - 2.14503e-03, 1.64530e-03, 1.08328e-03, 4.83736e-04, -1.24964e-04, -7.15425e-04, -1.24542e-03, -1.72140e-03, -2.10060e-03, - -2.37032e-03, -2.53448e-03, -2.56546e-03, -2.49179e-03, -2.29070e-03, -2.00403e-03, -1.61659e-03, -1.17826e-03, -6.83619e-04, - -1.74591e-04, 3.29944e-04, 8.13976e-04, 1.24507e-03, 1.61153e-03, 1.89772e-03, 2.08906e-03, 2.18121e-03, 2.17177e-03, - 2.06312e-03, 1.86382e-03, 1.58411e-03, 1.23827e-03, 8.43172e-04, 4.19904e-04, -1.56907e-05, -4.39507e-04, -8.37003e-04, - -1.18696e-03, -1.47938e-03, -1.69770e-03, -1.83969e-03, -1.89318e-03, -1.86141e-03, -1.74292e-03, -1.54080e-03, -1.27846e-03, - -9.95436e-04, -6.26595e-04, -2.64150e-04, 1.13622e-04, 4.76814e-04, 8.10335e-04, 1.10736e-03, 1.34273e-03, 1.52430e-03, - 1.62390e-03, 1.66246e-03, 1.61522e-03, 1.50632e-03, 1.32668e-03, 1.09739e-03, 8.19243e-04, 5.11244e-04, 1.80867e-04, - -1.46339e-04, -4.68008e-04, -7.60099e-04, -1.02128e-03, -1.23115e-03, -1.39047e-03, -1.48008e-03, -1.51120e-03, -1.46991e-03, - -1.37032e-03, -1.20407e-03, -9.91551e-04, -7.27194e-04, -4.35173e-04, -1.11623e-04, 2.15793e-04, 5.54161e-04, 8.66979e-04, - 1.18292e-03, 1.42542e-03, 1.77484e-03, 1.69052e-03, 2.12100e-03, 1.90833e-03, 2.18455e-03, 1.90159e-03, 2.03277e-03, - 1.68588e-03, 1.69365e-03, 1.30556e-03, 1.21725e-03, 8.19039e-04, 6.67167e-04, 2.89567e-04, 1.11906e-04, -2.18096e-04, - -3.95376e-04, -6.46202e-04, -8.02450e-04, -9.64235e-04, -1.08660e-03, -1.15302e-03, -1.23904e-03, -1.20955e-03, -1.26937e-03, - -1.15443e-03, -1.19836e-03, -1.01377e-03, -1.05669e-03, -8.15327e-04, -8.79730e-04, -5.93148e-04, -6.95337e-04, -3.75376e-04, - -5.27511e-04, -1.78544e-04, -3.96418e-04, -8.80292e-06, -3.37279e-04}; -#define LOW_PASS_SIZE (sizeof(low_pass) / sizeof(low_pass[0])) - -static const float sync_pattern[] = {-14, -14, -14, 18, 18, -14, -14, 18, 18, -14, -14, 18, 18, -14, -14, 18, - 18, -14, -14, 18, 18, -14, -14, 18, 18, -14, -14, 18, 18, -14, -14, -14}; -#define SYNC_PATTERN_SIZE (sizeof(sync_pattern) / sizeof(sync_pattern[0])) diff --git a/util/PrecipitationPalette.png b/util/PrecipitationPalette.png index 2ac2db0..943868e 100644 Binary files a/util/PrecipitationPalette.png and b/util/PrecipitationPalette.png differ diff --git a/util/TempPalette.png b/util/TempPalette.png index 974c07d..2f1c7e2 100644 Binary files a/util/TempPalette.png and b/util/TempPalette.png differ diff --git a/util/Temperature.png b/util/Temperature.png index 012f57f..fa5bb0e 100644 Binary files a/util/Temperature.png and b/util/Temperature.png differ diff --git a/util/img2gradient.py b/util/img2gradient.py new file mode 100755 index 0000000..815cda7 --- /dev/null +++ b/util/img2gradient.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +import sys +from PIL import Image + +""" +Converts a PNG into a gradient compatible +with aptdec. Requires Pillow: + + pip3 install Pillow + +""" + +if len(sys.argv) == 1: + print("Usage: {} filename.png".format(sys.argv[0])) + exit() + +image = Image.open(sys.argv[1]) +pixels = image.load() + +if len(pixels[0, 0]) != 3: + print("Image must be RGB") + exit() + +if image.size[0] != 1: + print("Image must be 1px wide") + exit() + +print("uint32_t gradient[{}] = {{\n ".format(image.size[1]), end="") +for y in range(image.size[1]): + print("0x" + "".join("{:02X}".format(a) for a in pixels[0, y]), end="") + + if y != image.size[1] - 1: + print(", ", end="") + if y % 7 == 6: + print("\n ", end="") + +print("\n};") diff --git a/util/img2pal.py b/util/img2pal.py deleted file mode 100644 index f5c3753..0000000 --- a/util/img2pal.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/python3 -import sys -from PIL import Image - -''' -Converts a PNG into a palette compatible -with aptdec. Requires PIL: - - pip3 install Pillow - -''' - -if len(sys.argv) == 1: - print("Usage: python3 {} filename.png".format(sys.argv[0])) - exit() - -image = Image.open(sys.argv[1]) -pixels = image.load() - -sys.stdout.write("char palette[{}*3] = {{\n \"".format(image.size[1])) -for y in range(1, image.size[1]+1): - sys.stdout.write(''.join('\\x{:02x}'.format(a) for a in pixels[0, y-1])) - if(y % 7 == 0 and y != 0): - sys.stdout.write("\"\n \"") - -print("\"\n};")