Liam 1 год назад
committed by GitHub
Родитель
Сommit
bb175e914f
Не найден GPG ключ соответствующий данной подписи Идентификатор GPG ключа: 4AEE18F83AFDEB23
88 измененных файлов: 2585 добавлений и 2376 удалений
  1. +3
    -5
      .github/ISSUE_TEMPLATE/bug-report.md
  2. +67
    -38
      .github/workflows/build.yml
  3. +46
    -67
      .github/workflows/release.yml
  4. +44
    -10
      .gitignore
  5. +2
    -2
      .gitmodules
  6. +87
    -77
      CMakeLists.txt
  7. +15
    -16
      CONTRIBUTING.md
  8. +65
    -59
      README.md
  9. +34
    -0
      UPGRADE_GUIDE.md
  10. +1
    -0
      aptdec-cli/argparse
  11. +528
    -0
      aptdec-cli/main.c
  12. +153
    -0
      aptdec-cli/pngio.c
  13. +41
    -0
      aptdec-cli/pngio.h
  14. +8
    -3
      aptdec-cli/util.c
  15. +14
    -12
      aptdec-cli/util.h
  16. +18
    -0
      build_arm.sh
  17. +31
    -29
      build_windows.bat
  18. +45
    -43
      build_windows.sh
  19. +0
    -19
      cmake/FindLibSndFile.cmake
  20. +19
    -0
      cmake/toolchain-armhf.cmake
  21. +1
    -0
      cmake/toolchain-mingw32.cmake
  22. +62
    -3
      libaptdec/algebra.c
  23. +14
    -3
      libaptdec/algebra.h
  24. +121
    -0
      libaptdec/calibration.c
  25. +6
    -4
      libaptdec/calibration.h
  26. +94
    -0
      libaptdec/color.c
  27. +238
    -0
      libaptdec/dsp.c
  28. +177
    -0
      libaptdec/effects.c
  29. +113
    -0
      libaptdec/filter.c
  30. +14
    -2
      libaptdec/filter.h
  31. +246
    -0
      libaptdec/image.c
  32. +172
    -0
      libaptdec/include/aptdec.h
  33. +40
    -0
      libaptdec/util.c
  34. +29
    -0
      libaptdec/util.h
  35. Двоичные данные
      luts/N19-HRPT-Falsecolor.png
  36. Двоичные данные
      luts/WXtoImg-BD.png
  37. Двоичные данные
      luts/WXtoImg-CC.png
  38. Двоичные данные
      luts/WXtoImg-EC.png
  39. Двоичные данные
      luts/WXtoImg-HE.png
  40. Двоичные данные
      luts/WXtoImg-HF.png
  41. Двоичные данные
      luts/WXtoImg-JF.png
  42. Двоичные данные
      luts/WXtoImg-JJ.png
  43. Двоичные данные
      luts/WXtoImg-MB.png
  44. Двоичные данные
      luts/WXtoImg-MD.png
  45. Двоичные данные
      luts/WXtoImg-N15-HVC.png
  46. Двоичные данные
      luts/WXtoImg-N18-HVC.png
  47. Двоичные данные
      luts/WXtoImg-N19-HVC.png
  48. Двоичные данные
      luts/WXtoImg-NO.png
  49. Двоичные данные
      luts/WXtoImg-TA.png
  50. Двоичные данные
      luts/WXtoImg-ZA.png
  51. Двоичные данные
      luts/WXtoImg-class.png
  52. Двоичные данные
      luts/WXtoImg-fire.png
  53. Двоичные данные
      luts/WXtoImg-sea.png
  54. Двоичные данные
      palettes/N19-HRPT-Falsecolor.png
  55. Двоичные данные
      palettes/WXtoImg-BD.png
  56. Двоичные данные
      palettes/WXtoImg-CC.png
  57. Двоичные данные
      palettes/WXtoImg-EC.png
  58. Двоичные данные
      palettes/WXtoImg-HE.png
  59. Двоичные данные
      palettes/WXtoImg-HF.png
  60. Двоичные данные
      palettes/WXtoImg-JF.png
  61. Двоичные данные
      palettes/WXtoImg-JJ.png
  62. Двоичные данные
      palettes/WXtoImg-MB.png
  63. Двоичные данные
      palettes/WXtoImg-MD.png
  64. Двоичные данные
      palettes/WXtoImg-NO.png
  65. Двоичные данные
      palettes/WXtoImg-TA.png
  66. Двоичные данные
      palettes/WXtoImg-ZA.png
  67. Двоичные данные
      palettes/WXtoImg-fire.png
  68. Двоичные данные
      palettes/WXtoImg-sea.png
  69. +0
    -126
      src/apt.h
  70. +0
    -1
      src/argparse
  71. +0
    -101
      src/calibration.c
  72. +0
    -83
      src/color.c
  73. +0
    -3
      src/color.h
  74. +0
    -60
      src/common.h
  75. +0
    -258
      src/dsp.c
  76. +0
    -62
      src/filter.c
  77. +0
    -388
      src/image.c
  78. +0
    -2
      src/image.h
  79. +0
    -56
      src/libs/median.c
  80. +0
    -316
      src/main.c
  81. +0
    -411
      src/pngio.c
  82. +0
    -11
      src/pngio.h
  83. +0
    -80
      src/taps.h
  84. Двоичные данные
      util/PrecipitationPalette.png
  85. Двоичные данные
      util/TempPalette.png
  86. Двоичные данные
      util/Temperature.png
  87. +37
    -0
      util/img2gradient.py
  88. +0
    -26
      util/img2pal.py

+ 3
- 5
.github/ISSUE_TEMPLATE/bug-report.md Просмотреть файл

@@ -4,21 +4,19 @@ about: Create a bug report to improve aptdec
title: ''
labels: bug
assignees: ''

---

**Describe the bug**
A clear description of what the bug is.

**To Reproduce**
Steps to reproduce the behaviour.
Steps to reproduce the behavior.

**Expected behaviour**
A clear description of what you expected to happen.

**Link to audio**
A link to the audio that caused the problem.
A link to the audio file that caused the problem (if applicable).

**Build information**
The commit of aptdec you are running (check with `git rev-parse HEAD`), make sure it's up to date before opening this issue.
And whether you are using GNU automake or CMake.
The version of aptdec you are running (check with `git describe --tag`), make sure it's up to date before opening this issue.

+ 67
- 38
.github/workflows/build.yml Просмотреть файл

@@ -10,9 +10,6 @@ on:
paths-ignore:
- '**.md'

env:
BUILD_TYPE: Release

jobs:
build_linux:
runs-on: ubuntu-latest
@@ -21,37 +18,28 @@ jobs:
- uses: actions/checkout@v3
with:
submodules: 'recursive'
fetch-depth: 0
- name: Install dependencies
# The user does not run as root
run: sudo apt-get install cmake git gcc libsndfile-dev libpng-dev

- name: Create Build Environment
# Some projects don't allow in-source building, so create a separate build directory
# We'll use this as our working directory for all subsequent commands
run: cmake -E make_directory ${{runner.workspace}}/build

- name: Configure CMake
# Use a bash shell so we can use the same syntax for environment variable
# access regardless of the host operating system
shell: bash
working-directory: ${{runner.workspace}}/build
# Note the current convention is to use the -S and -B options here to specify source
# and build directories, but this is only available with CMake 3.13 and higher.
# The CMake binaries on the Github Actions machines are (as of this writing) 3.12
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE

- name: Build
working-directory: ${{runner.workspace}}/build
shell: bash
# Execute the build. You can specify a specific target with "--target <NAME>"
run: cmake --build . --config $BUILD_TYPE

- name: Upload compilied binary
uses: actions/upload-artifact@v2
- name: Configure cmake
run: cmake -B build -DCMAKE_BUILD_TYPE=Release

- name: Build and package
run: cmake --build build -j$(nproc) && cmake --build build --target package

- name: Upload TGZ archive
uses: actions/upload-artifact@v3
with:
name: aptdec_lin64
path: ${{runner.workspace}}/build/aptdec
name: TGZ archive (x86_64-gcc)
path: build/aptdec_*.tar.gz

- name: Upload Debian package
uses: actions/upload-artifact@v3
with:
name: Debian package (x86_64-gcc)
path: build/aptdec_*.deb

build_windows:
runs-on: ubuntu-latest
@@ -60,18 +48,59 @@ jobs:
- uses: actions/checkout@v3
with:
submodules: 'recursive'
fetch-depth: 0
- name: Install dependencies
# The user does not run as root
run: sudo apt install wget cmake make mingw-w64 git unzip libsndfile-dev
run: sudo apt install cmake git mingw-w64 unzip

- name: Run build script
run: ./build_windows.sh $BUILD_TYPE

- name: Upload ZIP archive
uses: actions/upload-artifact@v3
with:
name: ZIP archive (x86_64-MinGW)
path: winbuild/aptdec_*.zip
build_windows_msvc:
runs-on: windows-latest

steps:
- uses: actions/checkout@v3
with:
submodules: 'recursive'
fetch-depth: 0

- name: Run build script
shell: bash
working-directory: ${{runner.workspace}}
run: cd $GITHUB_WORKSPACE && ./build_windows.sh $BUILD_TYPE
shell: cmd
run: '"C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat" & build_windows.bat'

- name: Upload ZIP archive
uses: actions/upload-artifact@v3
with:
name: ZIP archive (x86_64-MSVC)
path: winbuild/aptdec_*.zip
build_linux_armhf:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
with:
submodules: 'recursive'
fetch-depth: 0

- name: Run build script
run: docker run -v $(pwd):/aptdec:z -w /aptdec debian:11 ./build_arm.sh

- name: Upload Debian package
uses: actions/upload-artifact@v3
with:
name: Debian package (armhf-gcc)
path: build/aptdec_*.deb

- name: Upload compilied binary
uses: actions/upload-artifact@v2
- name: Upload TGZ archive
uses: actions/upload-artifact@v3
with:
name: aptdec-win64.zip
path: ${{runner.workspace}}/aptdec/winbuild/aptdec-1.7.0.zip
name: TGZ archive (armhf-gcc)
path: build/aptdec_*.tar.gz

+ 46
- 67
.github/workflows/release.yml Просмотреть файл

@@ -1,110 +1,89 @@
name: Build
name: Build (release)

on:
push:
tags:
- 'v*'

env:
BUILD_TYPE: Release

jobs:
prepare_release:
create_release:
runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
needs: [ build_linux, build_windows ]

steps:
- name: Download TGZ package
uses: actions/download-artifact@v3
with:
name: TGZ package

- name: Download DEB package
uses: actions/download-artifact@v3
with:
name: DEB package

- name: Download ZIP package
uses: actions/download-artifact@v3
with:
name: ZIP package

- name: Test
run: ls -la

- name: Create Release
uses: actions/create-release@v1
id: create_release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body: A release wow
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
files: |
aptdec_$GITHUB_REF_NAME.x86_64.tar.gz
aptdec_$GITHUB_REF_NAME.x86_64.deb
aptdec_$GITHUB_REF_NAME.amd64.zip

build_linux:
runs-on: ubuntu-latest
needs: [prepare_release]

steps:
- uses: actions/checkout@v3
with:
submodules: 'recursive'
fetch-depth: 0

- name: Install dependencies
# The user does not run as root
run: sudo apt-get install cmake git gcc libsndfile-dev libpng-dev

- name: Create Build Environment
# Some projects don't allow in-source building, so create a separate build directory
# We'll use this as our working directory for all subsequent commands
run: cmake -E make_directory ${{runner.workspace}}/build

- name: Configure CMake
# Use a bash shell so we can use the same syntax for environment variable
# access regardless of the host operating system
shell: bash
working-directory: ${{runner.workspace}}/build
# Note the current convention is to use the -S and -B options here to specify source
# and build directories, but this is only available with CMake 3.13 and higher.
# The CMake binaries on the Github Actions machines are (as of this writing) 3.12
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE

- name: Build
working-directory: ${{runner.workspace}}/build
shell: bash
# Execute the build. You can specify a specific target with "--target <NAME>"
run: cmake --build . --config $BUILD_TYPE && cmake --build . --target package
- name: Configure cmake
run: cmake -B build -DCMAKE_BUILD_TYPE=Release

- name: Build and package
run: cmake --build build -j$(nproc) && cmake --build build --target package

- name: Upload TGZ package
uses: actions/upload-release-asset@v1
uses: actions/upload-artifact@v3
with:
upload_url: ${{ needs.prepare_release.outputs.upload_url }}
asset_path: ${{runner.workspace}}/build/aptdec-${{ github.ref_name }}.x86_64.tar.gz
asset_name: aptdec-${{ github.ref_name }}.x86_64.tar.gz
asset_content_type: application/gzip
env:
GITHUB_TOKEN: ${{ github.token }}
name: TGZ package
path: build/aptdec_*.tar.gz

- name: Upload DEB package
uses: actions/upload-release-asset@v1
uses: actions/upload-artifact@v3
with:
upload_url: ${{ needs.prepare_release.outputs.upload_url }}
asset_path: ${{runner.workspace}}/build/aptdec-${{ github.ref_name }}.x86_64.deb
asset_name: aptdec-${{ github.ref_name }}.x86_64.deb
asset_content_type: application/vnd.debian.binary-package
env:
GITHUB_TOKEN: ${{ github.token }}
name: Debian package
path: build/aptdec_*.deb

build_windows:
runs-on: ubuntu-latest
needs: [prepare_release]

steps:
- uses: actions/checkout@v3
with:
submodules: 'recursive'
fetch-depth: 0
- name: Install dependencies
# The user does not run as root
run: sudo apt install wget cmake make mingw-w64 git unzip libsndfile-dev
run: sudo apt install cmake git mingw-w64 unzip

- name: Run build script
shell: bash
working-directory: ${{runner.workspace}}
run: cd $GITHUB_WORKSPACE && ./build_windows.sh $BUILD_TYPE
run: ./build_windows.sh $BUILD_TYPE

- name: Upload zip
uses: actions/upload-release-asset@v1
- name: Upload ZIP package
uses: actions/upload-artifact@v3
with:
upload_url: ${{ needs.prepare_release.outputs.upload_url }}
asset_path: ${{runner.workspace}}/aptdec/winbuild/aptdec-${{ github.ref_name }}.zip
asset_name: aptdec-${{ github.ref_name }}.zip
asset_content_type: application/zip
env:
GITHUB_TOKEN: ${{ github.token }}
name: ZIP package
path: winbuild/aptdec_*.zip

+ 44
- 10
.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,47 @@
*.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/
zlib/
libpng/
libsndfile-*-win64.zip
libsndfile-*-win64/
root/

+ 2
- 2
.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

+ 87
- 77
CMakeLists.txt Просмотреть файл

@@ -1,99 +1,109 @@
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)

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()

if (USE_ADDRESS_SANITIZER)
target_compile_options(aptdec-cli PRIVATE -fsanitize=address)
target_link_options(aptdec-cli PRIVATE -fsanitize=address)
endif()

install(TARGETS aptdec-cli RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
file(GLOB LUTS luts/*.png)
install(FILES ${LUTS} DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/aptdec/luts)
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()
if (USE_ADDRESS_SANITIZER)
target_compile_options(aptdec PRIVATE -fsanitize=address)
target_link_options(aptdec PRIVATE -fsanitize=address)
endif()

# TODO: get this from git
set(PROJECT_VERSION "v1.8.0")
install(TARGETS aptdec PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})

# CPack
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
# Packaging
string(REPLACE v "" CPACK_PACKAGE_VERSION ${VERSION})
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
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_DEBIAN_PACKAGE_SHLIBDEPS ON)

IF(NOT WIN32)
set(CPACK_GENERATOR "DEB;TGZ")
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}.${CMAKE_SYSTEM_PROCESSOR}")
else()
#set(CPACK_GENERATOR "ZIP;NSIS")
set(CPACK_GENERATOR "ZIP")
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")
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_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}.${CMAKE_SYSTEM_PROCESSOR}")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libsndfile1, libpng16-16")

if (TARGET aptdec)
file(GLOB_RECURSE DLLS *.dll)
install(FILES ${DLLS} DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()
endif()
if(WIN32)
file(GLOB DLLS ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/*.dll)
install(FILES ${DLLS} DESTINATION ${CMAKE_INSTALL_BINDIR})

if (TARGET aptdec)
install(TARGETS aptdec RUNTIME DESTINATION bin)
install(DIRECTORY "${PROJECT_SOURCE_DIR}/palettes" DESTINATION ${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME})
set(CPACK_GENERATOR "ZIP")
else()
set(CPACK_GENERATOR "DEB;TGZ")
endif()

install(TARGETS apt PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/apt LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})

include(CPack)

+ 15
- 16
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

+ 65
- 59
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,73 @@ 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 <path> Output filename
-d <path> Destination directory
-s (15-19) Satellite number
-p <path> Path to palette
-r Realtime decode
-g Gamma adjustment (1.0 = off)
-h, --help show this help message and exit
-i, --image=<str> set output image type (see the README for a list)
-e, --effect=<str> add an effect (see the README for a list)
-s, --satellite=<int> satellite ID, must be either NORAD or 15/18/19
-l, --lut=<str> path to a LUT
-o, --output=<str> 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
./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



+ 34
- 0
UPGRADE_GUIDE.md Просмотреть файл

@@ -0,0 +1,34 @@
# Upgrading from aptdec 1.8.0 to 2.0.0

# `aptdec` (now `aptdec-cli`)

## Removed

- The `-d` (output directory) flag
- The `-g` (gamma) flag
- Map overlays/MCIR
- Reading raw PNG images

(Some of these features have been moved into `aptdec_post.py`)

## Changes

- "palettes" are now called "luts"
- Image type and effects are now full English words (separated by commas) instead of letters
- Output format of realtime images is now `CURRENT_TIME.png` instead of `CURRENT_TIME-r.png`
- Channel A/B images include telemetry

## Examples

| v1.8 | v2.0.0 |
|---------------------------------------|-------------------------------------------|
| `aptdec file.wav` | `aptdec-cli file.wav` |
| `aptdec -i rt file.wav` | `aptdec-cli -i raw,thermal file.wav` |
| `aptdec -i ab file.wav` | `aptdec-cli -i a,b -e strip file.wav` |
| `aptdec -i p -p palette.png file.wav` | `aptdec-cli -i lut -l lut.png file.wav` |
| `aptdec -d out *.wav` | `mkdir out; cd out; aptdec-cli ../*.wav` |


# `libapt` (now `libaptdec`)

To avoid confusion with `apt` (the Debian package manager) the name of the aptdec library has been changed to `libaptdec`.

+ 1
- 0
aptdec-cli/argparse

@@ -0,0 +1 @@
Subproject commit 15091715cc379bc647d5de6345185a288a35f999

+ 528
- 0
aptdec-cli/main.c Просмотреть файл

@@ -0,0 +1,528 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef _MSC_VER
#include <libgen.h>
#else
#include <windows.h>
#endif
#include <math.h>
#include <sndfile.h>
#include <time.h>
#include <signal.h>
#include <float.h>
#include <aptdec.h>

#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);
int array_contains(char **array, char *value, size_t n);

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] = { 0 };
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 15/18/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] = { 0 };
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] = { 0 };
sprintf(filename, "%s-decoding.png", name);
realtime_png = writer_init(filename, APTDEC_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 = calloc(APTDEC_IMG_WIDTH * (APTDEC_MAX_HEIGHT+1), sizeof(float));
size_t rows;
for (rows = 0; rows < APTDEC_MAX_HEIGHT; rows++) {
float *row = &data[rows * APTDEC_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) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
write_line(realtime_png, row);
#pragma GCC diagnostic pop
}

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] = { 0 };
sprintf(filename, "%s-decoding.png", name);
remove(filename);
}

// Normalize
int error;
aptdec_image_t img = aptdec_normalize(data, rows, opts->satellite, &error);
free(data);
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) {
aptdec_crop(&img);
} else if (strcmp(effects[i], "denoise") == 0) {
aptdec_denoise(&img, APTDEC_REGION_CHA);
aptdec_denoise(&img, APTDEC_REGION_CHB);
} else if (strcmp(effects[i], "flip") == 0) {
aptdec_flip(&img, APTDEC_REGION_CHA);
aptdec_flip(&img, APTDEC_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] = { 0 };
sprintf(filename, "%s-thermal.png", base);
char description[128] = { 0 };
sprintf(description, "Calibrated thermal image, channel %s - %s", channel_name[img.ch[1]], channel_desc[img.ch[1]]);

// Perform visible calibration
aptdec_image_t _img = aptdec_image_clone(img);
aptdec_calibrate_thermal(&_img, APTDEC_REGION_CHA);

writer_t *writer = writer_init(filename, APTDEC_REGION_CHB, img.rows, PNG_COLOR_TYPE_RGB, description);
writer_write_image_gradient(writer, &_img, aptdec_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] = { 0 };
sprintf(filename, "%s-visible.png", base);
char description[128] = { 0 };
sprintf(description, "Calibrated visible image, channel %s - %s", channel_name[img.ch[0]], channel_desc[img.ch[0]]);

// Perform visible calibration
aptdec_image_t _img = aptdec_image_clone(img);
aptdec_calibrate_visible(&_img, APTDEC_REGION_CHA);

writer_t *writer = writer_init(filename, APTDEC_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) {
aptdec_stretch(&img, APTDEC_REGION_CHA);
aptdec_stretch(&img, APTDEC_REGION_CHB);
} else if (strcmp(effects[i], "equalize") == 0) {
aptdec_equalize(&img, APTDEC_REGION_CHA);
aptdec_equalize(&img, APTDEC_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] = { 0 };
sprintf(filename, "%s-raw.png", base);
char description[128] = { 0 };
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;
if (array_contains(effects, "strip", effects_len)) {
writer = writer_init(filename, (aptdec_region_t){0, APTDEC_CH_WIDTH*2}, img.rows, PNG_COLOR_TYPE_GRAY, description);
aptdec_image_t _img = aptdec_image_clone(img);
aptdec_strip(&_img);
writer_write_image(writer, &_img);
free(_img.data);
} else {
writer = writer_init(filename, APTDEC_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] = { 0 };
sprintf(filename, "%s-lut.png", base);
char description[128] = { 0 };
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 = calloc(256*256, sizeof(png_color));
if (read_lut(opts->lut, lut)) {
writer_t *writer = writer_init(filename, APTDEC_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] = { 0 };
sprintf(filename, "%s-a.png", base);
char description[128] = { 0 };
sprintf(description, "Channel A: %s - %s", channel_name[img.ch[0]], channel_desc[img.ch[0]]);

writer_t *writer;
if (array_contains(effects, "strip", effects_len)) {
writer = writer_init(filename, (aptdec_region_t){0, APTDEC_CH_WIDTH}, img.rows, PNG_COLOR_TYPE_GRAY, description);
aptdec_image_t _img = aptdec_image_clone(img);
aptdec_strip(&_img);
writer_write_image(writer, &_img);
free(_img.data);
} else {
writer = writer_init(filename, APTDEC_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] = { 0 };
sprintf(filename, "%s-b.png", base);
char description[128] = { 0 };
sprintf(description, "Channel B: %s - %s", channel_name[img.ch[1]], channel_desc[img.ch[1]]);

writer_t *writer;
if (array_contains(effects, "strip", effects_len)) {
writer = writer_init(filename, (aptdec_region_t){APTDEC_CH_WIDTH, APTDEC_CH_WIDTH}, img.rows, PNG_COLOR_TYPE_GRAY, description);
aptdec_image_t _img = aptdec_image_clone(img);
aptdec_strip(&_img);
writer_write_image(writer, &_img);
free(_img.data);
} else {
writer = writer_init(filename, APTDEC_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[APTDEC_BUFFER_SIZE * 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 < APTDEC_IMG_WIDTH; i++) {
if (row[i] < min) min = row[i];
if (row[i] > max) max = row[i];
}

png_byte pixels[APTDEC_IMG_WIDTH];
for (int i = 0; i < APTDEC_IMG_WIDTH; i++) {
pixels[i] = clamp_int(roundf((row[i]-min) / (max-min) * 255.0f), 0, 255);
}

png_write_row(png->png, pixels);
}

int array_contains(char **array, char *value, size_t n) {
for (size_t i = 0; i < n; i++) {
if (strcmp(array[i], value) == 0) {
return 1;
}
}
return 0;
}

+ 153
- 0
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 <https://www.gnu.org/licenses/>.
*/

#include "pngio.h"

#include <png.h>
#include <stdlib.h>
#include <string.h>

#include "util.h"

writer_t *writer_init(const char *filename, aptdec_region_t region, uint32_t height, int color, char *channel) {
writer_t *png = calloc(1, 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] = { 0 };
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 aptdec_image_t *img) {
for (size_t y = 0; y < img->rows; y++) {
png_write_row(png->png, &img->data[y*APTDEC_IMG_WIDTH + png->region.offset]);
}
}

void writer_write_image_gradient(writer_t *png, const aptdec_image_t *img, const uint32_t *gradient) {
for (size_t y = 0; y < img->rows; y++) {
png_color pixels[APTDEC_IMG_WIDTH];
for (size_t x = 0; x < APTDEC_IMG_WIDTH; x++) {
aptdec_rgb_t pixel = aptdec_gradient(gradient, img->data[y*APTDEC_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 aptdec_image_t *img, const png_colorp lut) {
for (size_t y = 0; y < img->rows; y++) {
png_color pixels[APTDEC_CH_WIDTH];
for (size_t x = 0; x < APTDEC_CH_WIDTH; x++) {
uint8_t a = img->data[y*APTDEC_IMG_WIDTH + x + APTDEC_CHA_OFFSET];
uint8_t b = img->data[y*APTDEC_IMG_WIDTH + x + APTDEC_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;
}

+ 41
- 0
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 <https://www.gnu.org/licenses/>.
*/

#ifndef APTDEC_CLI_PNGIO_H_
#define APTDEC_CLI_PNGIO_H_

#include <aptdec.h>
#include <png.h>

typedef struct {
png_structp png;
png_infop info;
FILE *file;
aptdec_region_t region;
} writer_t;

writer_t *writer_init(const char *filename, aptdec_region_t region, uint32_t height, int color, char *channel);
void writer_free(writer_t *png);

void writer_write_image(writer_t *png, const aptdec_image_t *img);
void writer_write_image_gradient(writer_t *png, const aptdec_image_t *img, const uint32_t *gradient);
void writer_write_image_lut(writer_t *png, const aptdec_image_t *img, const png_colorp lut);

int read_lut(const char *filename, png_colorp out);

#endif

src/util.c → 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 <aptdec.h>
#include <stdio.h>
#include <stdlib.h>

@@ -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());
}

src/util.h → 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,21 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

#include <stdio.h>
#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 <stddef.h>

#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);
#ifdef _MSC_VER
void error(const char *text);
#else
__attribute__((noreturn)) void error(const char *text);
#endif
void warning(const char *text);

int clamp_int(int x, int lo, int hi);

int get_version(char *str);

#endif

+ 18
- 0
build_arm.sh Просмотреть файл

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# This script only works on Debian Bullseye
# If you have docker this is trivially easy:
# docker run -v $(pwd):/aptdec:z -w /aptdec debian:11 ./build_arm.sh

apt-get update
apt-get install -y debootstrap cmake gcc-arm-linux-gnueabihf git

# Prepare armhf root environment
if [ ! -d root ]; then
debootstrap --keyring=/usr/share/keyrings/debian-archive-keyring.gpg --arch=armhf --include=libsndfile1-dev,libpng-dev --download-only bullseye root http://deb.debian.org/debian/
for i in root/var/cache/apt/archives/*.deb; do dpkg -x "$i" root; done
fi

# Build aptdec
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-armhf.cmake -DCMAKE_INSTALL_PREFIX=/aptdec/root
cmake --build build -j$(nproc)
cmake --build build --target package

+ 31
- 29
build_windows.bat Просмотреть файл

@@ -1,36 +1,38 @@
REM Build using Visual Studio 2019 on Windows
REM Additional tools needed: git, cmake and ninja
REM Build using MSVC on Windows
REM Requires: git, cmake and ninja
REM You need to run vcvars before running this
REM Build zlib
git clone https://github.com/madler/zlib
cd zlib
mkdir build
cd build
cmake -G Ninja -DCMAKE_C_COMPILER="cl.exe" -DMSVC_TOOLSET_VERSION=190 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../../winpath ..
ninja install
cd ../../
IF NOT EXIST zlib (
git clone --depth 1 -b v1.2.13 https://github.com/madler/zlib
cd zlib
cmake -B build -G Ninja -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../winpath
cmake --build build -j%NUMBER_OF_PROCESSORS%
cmake --build build --target install
cd ..
)
REM Build libpng
git clone https://github.com/glennrp/libpng
cd libpng
mkdir build
cd build
cmake -G Ninja -DCMAKE_C_COMPILER="cl.exe" -DMSVC_TOOLSET_VERSION=190 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../../winpath ..
ninja install
cd ../..
IF NOT EXIST libpng (
git clone --depth 1 -b v1.6.39 https://github.com/glennrp/libpng
cd libpng
cmake -B build -G Ninja -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../winpath -DPNG_STATIC=OFF -DPNG_EXECUTABLES=OFF -DPNG_TESTS=OFF
cmake --build build -j%NUMBER_OF_PROCESSORS%
cmake --build build --target install
cd ..
)
REM Build libsndfile - Could build Vorbis, FLAC and Opus first for extra support
git clone https://github.com/libsndfile/libsndfile
cd libsndfile
mkdir build
cd build
cmake -G Ninja -DCMAKE_C_COMPILER="cl.exe" -DMSVC_TOOLSET_VERSION=190 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../../winpath ..
ninja install
cd ../..
REM Build libsndfile, only with WAV support
IF NOT EXIST libsndfile (
git clone --depth 1 -b 1.2.0 https://github.com/libsndfile/libsndfile
cd libsndfile
cmake -B build -G Ninja -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../winpath -DBUILD_SHARED_LIBS=ON -DBUILD_EXAMPLES=OFF -DBUILD_PROGRAMS=OFF
cmake --build build -j%NUMBER_OF_PROCESSORS%
cmake --build build --target install
cd ..
)
REM Build aptdec
mkdir winbuild
cd winbuild
cmake -G Ninja -DCMAKE_C_COMPILER="cl.exe" -DMSVC_TOOLSET_VERSION=190 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../winpath ..
ninja install
cd ..
cmake -B winbuild -G Ninja -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../winpath
cmake --build winbuild -j%NUMBER_OF_PROCESSORS%
cmake --build winbuild --target package

+ 45
- 43
build_windows.sh Просмотреть файл

@@ -1,55 +1,57 @@
#!/bin/bash
#!/usr/bin/env bash
# Cross compile for Windows from Linux

TEMP_PATH="$(pwd)/winpath"
set -e

# Compile and build zlib
if [ -d "zlib" ]; then
cd zlib && git pull
else
git clone https://github.com/madler/zlib && cd zlib
TEMP_PATH="$(pwd)/winpath"
BUILD_DIR="winbuild"

# Build zlib from source
if [ ! -d "zlib" ]; then
git clone --depth 1 -b v1.2.13 https://github.com/madler/zlib && cd zlib
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchain-mingw32.cmake -DCMAKE_INSTALL_PREFIX=$TEMP_PATH
cmake --build build -j$(nproc)
cmake --build build --target install
cd ..
fi

mkdir -p build && cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=../../cmake/toolchain-mingw32.cmake -DCMAKE_INSTALL_PREFIX=$TEMP_PATH ..
make -j4
make install
cd ../..

# Clone and build ligpng
if [ -d "libpng" ]; then
cd libpng && git pull
else
git clone https://github.com/glennrp/libpng && cd libpng
# Build libpng from source
if [ ! -d "libpng" ]; then
git clone --depth 1 -b v1.6.39 https://github.com/glennrp/libpng && cd libpng
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchain-mingw32.cmake -DCMAKE_INSTALL_PREFIX=$TEMP_PATH -DPNG_STATIC=OFF -DPNG_EXECUTABLES=OFF -DPNG_TESTS=OFF
cmake --build build -j$(nproc)
cmake --build build --target install
cd ..
fi

mkdir -p build && cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=../../cmake/toolchain-mingw32.cmake -DCMAKE_INSTALL_PREFIX=$TEMP_PATH ..
make -j4
make install
cd ../..

# Download libsndfile
if [ ! -d "libsndfile-1.0.29-win64" ]; then
wget https://github.com/erikd/libsndfile/releases/download/v1.0.29/libsndfile-1.0.29-win64.zip
unzip libsndfile-1.0.29-win64.zip
if [ ! -d libsndfile-1.2.0-win64 ]; then
wget https://github.com/libsndfile/libsndfile/releases/download/1.2.0/libsndfile-1.2.0-win64.zip
unzip libsndfile-1.2.0-win64.zip
cp "libsndfile-1.2.0-win64/bin/sndfile.dll" $TEMP_PATH/bin
cp "libsndfile-1.2.0-win64/include/sndfile.h" $TEMP_PATH/include
cp "libsndfile-1.2.0-win64/lib/sndfile.lib" $TEMP_PATH/lib
fi
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 ../

buildtype="Debug"
if [[ "$1" == "Release" ]]; then
buildtype="Release"
fi
find_dll() {
filename=$(x86_64-w64-mingw32-gcc -print-file-name=$1)
if [ -f $filename ]; then
echo $filename
else
filename=$(x86_64-w64-mingw32-gcc -print-sysroot)/mingw/bin/$1
if [ -f $filename ]; then
echo $filename
else
echo "Could not find $1" >&2
return 1
fi
fi
}

# Copy required GCC libs
cp $(find_dll libgcc_s_seh-1.dll) $TEMP_PATH/bin
cp $(find_dll libwinpthread-1.dll) $TEMP_PATH/bin

# Build aptdec
mkdir -p winbuild && cd winbuild
cmake -DCMAKE_BUILD_TYPE=$buildtype -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchain-mingw32.cmake -DCMAKE_INSTALL_PREFIX=$TEMP_PATH ..
make -j 4
make package
cmake -B $BUILD_DIR -DCMAKE_BUILD_TYPE=$1 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-mingw32.cmake -DCMAKE_INSTALL_PREFIX=$TEMP_PATH
cmake --build $BUILD_DIR -j$(nproc)
cmake --build $BUILD_DIR --target package

+ 0
- 19
cmake/FindLibSndFile.cmake Просмотреть файл

@@ -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)

+ 19
- 0
cmake/toolchain-armhf.cmake Просмотреть файл

@@ -0,0 +1,19 @@
# the name of the target operating system
SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_SYSTEM_PROCESSOR armhf)

# which compilers to use for C and C++
SET(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
SET(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)

# here is the target environment located
SET(CMAKE_FIND_ROOT_PATH /usr/arm-linux-gnueabihf/ ${CMAKE_INSTALL_PREFIX})
# Hack
link_directories(${CMAKE_INSTALL_PREFIX}/lib/arm-linux-gnueabihf)

# adjust the default behaviour of the FIND_XXX() commands:
# search headers and libraries in the target environment, search
# programs in the host environment
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

+ 1
- 0
cmake/toolchain-mingw32.cmake Просмотреть файл

@@ -1,5 +1,6 @@
# the name of the target operating system
SET(CMAKE_SYSTEM_NAME Windows)
SET(CMAKE_SYSTEM_PROCESSOR x86_64)

# which compilers to use for C and C++
SET(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)


src/algebra.c → 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 <stdlib.h>
#include <math.h>

// Find the best linear equation to estimate the value of the
@@ -51,6 +52,64 @@ 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; }
// "Sample" standard deviation
float standard_deviation(const float *data, size_t len) {
float mean = meanf(data, len);

float quadratic_calc(float x, quadratic_t quadratic) { return x * x * quadratic.a + x * quadratic.b + quadratic.c; }
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-1));
}

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);

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);
}

src/algebra.h → 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 <https://www.gnu.org/licenses/>.
*/

#ifndef APTDEC_ALGEBRA_H
#define APTDEC_ALGEBRA_H
#ifndef LIBAPTDEC_ALGEBRA_H_
#define LIBAPTDEC_ALGEBRA_H_
#include <stddef.h>

#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

+ 121
- 0
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 <https://www.gnu.org/licenses/>.
*/

#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(aptdec_satellite_t satid) {
switch (satid) {
case NOAA15: return calibration[0];
case NOAA18: return calibration[1];
default: return calibration[2];
}
}

src/calibration.h → 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 <https://www.gnu.org/licenses/>.
*/

#ifndef APTDEC_CALIBRATION_H
#define APTDEC_CALIBRATION_H
#ifndef LIBAPTDEC_CALIBRATION_H_
#define LIBAPTDEC_CALIBRATION_H_

#include "algebra.h"
#include <aptdec.h>

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(aptdec_satellite_t satid);

#endif

+ 94
- 0
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 <https://www.gnu.org/licenses/>.
*/

#include <aptdec.h>
#include "util.h"

#define MCOMPOSITE(m1, a1, m2, a2) (m1 * a1 + m2 * a2 * (1 - a1))

// clang-format off

aptdec_rgb_t aptdec_gradient(const uint32_t *gradient, uint8_t val) {
return (aptdec_rgb_t) {
(gradient[val] & 0x00FF0000) >> 16,
(gradient[val] & 0x0000FF00) >> 8,
(gradient[val] & 0x000000FF)
};
}

aptdec_rgb_t aptdec_composite_rgb(aptdec_rgb_t top, float top_alpha, aptdec_rgb_t bottom, float bottom_alpha) {
return (aptdec_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 aptdec_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 aptdec_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
};

+ 238
- 0
libaptdec/dsp.c Просмотреть файл

@@ -0,0 +1,238 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/

#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <aptdec.h>

#include "filter.h"
#include "util.h"
#include "algebra.h"

#define LOW_PASS_SIZE 101

#define CARRIER_FREQ 2400.0f
#define MAX_CARRIER_OFFSET 10.0f

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]))

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 {
float sample_rate;
float sync_frequency;

pll_t *pll;
fir_t *hilbert;

float low_pass[LOW_PASS_SIZE];
float row_buffer[APTDEC_IMG_WIDTH + SYNC_SIZE + 2];

float interpolator_buffer[APTDEC_BUFFER_SIZE];
size_t interpolator_n;
float interpolator_offset;
};

char *aptdec_get_version(void) {
return VERSION;
}

fir_t *fir_init(size_t max_size, size_t ntaps) {
fir_t *fir = calloc(1, sizeof(fir_t));
fir->ntaps = ntaps;
fir->ring_size = max_size + ntaps;
fir->taps = calloc(ntaps, sizeof(float));
fir->ring_buffer = calloc(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 = calloc(1, 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 + APTDEC_IMG_WIDTH) * 2.0f) {
return NULL;
}

aptdec_t *aptdec = calloc(1, sizeof(aptdec_t));
aptdec->sample_rate = sample_rate;
aptdec->sync_frequency = 1.0f;
aptdec->interpolator_n = APTDEC_BUFFER_SIZE;
aptdec->interpolator_offset = 0.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);
aptdec->pll = pll_init(alpha, beta, CARRIER_FREQ-MAX_CARRIER_OFFSET, CARRIER_FREQ+MAX_CARRIER_OFFSET, sample_rate);
if (aptdec->pll == NULL) {
free(aptdec);
return NULL;
}

// Hilbert transform
aptdec->hilbert = fir_init(APTDEC_BUFFER_SIZE, 31);
if (aptdec->hilbert == NULL) {
free(aptdec->pll);
free(aptdec);
return NULL;
}
design_hilbert(aptdec->hilbert->taps, aptdec->hilbert->ntaps);

design_low_pass(aptdec->low_pass, aptdec->sample_rate, (2080.0f + CARRIER_FREQ) / 2.0f, LOW_PASS_SIZE);

return aptdec;
}

void aptdec_free(aptdec_t *aptdec) {
fir_free(aptdec->hilbert);
free(aptdec->pll);
free(aptdec);
}

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 *aptdec, float *out, size_t count, aptdec_callback_t callback, void *context) {
size_t read = callback(&aptdec->hilbert->ring_buffer[aptdec->hilbert->ntaps], count, context);

for (size_t i = 0; i < read; i++) {
complexf_t sample = hilbert_transform(&aptdec->hilbert->ring_buffer[i], aptdec->hilbert->taps, aptdec->hilbert->ntaps);
out[i] = crealf(pll_work(aptdec->pll, sample));
}

memcpy(aptdec->hilbert->ring_buffer, &aptdec->hilbert->ring_buffer[read], aptdec->hilbert->ntaps*sizeof(float));

return read;
}

static int get_pixels(aptdec_t *aptdec, float *out, size_t count, aptdec_callback_t callback, void *context) {
float ratio = aptdec->sample_rate / (4160.0f * aptdec->sync_frequency);

for (size_t i = 0; i < count; i++) {
// Get more samples if there are less than `LOW_PASS_SIZE` available
if (aptdec->interpolator_n + LOW_PASS_SIZE > APTDEC_BUFFER_SIZE) {
memcpy(aptdec->interpolator_buffer, &aptdec->interpolator_buffer[aptdec->interpolator_n], (APTDEC_BUFFER_SIZE-aptdec->interpolator_n) * sizeof(float));

size_t read = am_demod(aptdec, &aptdec->interpolator_buffer[APTDEC_BUFFER_SIZE-aptdec->interpolator_n], aptdec->interpolator_n, callback, context);
if (read != aptdec->interpolator_n) {
return i;
}
aptdec->interpolator_n = 0;
}

out[i] = interpolating_convolve(&aptdec->interpolator_buffer[aptdec->interpolator_n], aptdec->low_pass, LOW_PASS_SIZE, aptdec->interpolator_offset);

// Do not question the sacred code
int shift = ceilf(ratio - aptdec->interpolator_offset);
aptdec->interpolator_offset = shift + aptdec->interpolator_offset - ratio;
aptdec->interpolator_n += shift;
}

return count;
}

// Get an entire row of pixels, aligned with sync markers
int aptdec_getrow(aptdec_t *aptdec, float *row, aptdec_callback_t callback, void *context) {
// Wrap the circular buffer
memcpy(aptdec->row_buffer, &aptdec->row_buffer[APTDEC_IMG_WIDTH], (SYNC_SIZE + 2) * sizeof(float));

// Get a lines worth (APTDEC_IMG_WIDTH) of samples
if (get_pixels(aptdec, &aptdec->row_buffer[SYNC_SIZE + 2], APTDEC_IMG_WIDTH, callback, context) != APTDEC_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 < APTDEC_IMG_WIDTH; i++) {
float _left = convolve(&aptdec->row_buffer[i + 0], sync_pattern, SYNC_SIZE);
float _middle = convolve(&aptdec->row_buffer[i + 1], sync_pattern, SYNC_SIZE);
float _right = convolve(&aptdec->row_buffer[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);
aptdec->sync_frequency = 1.0f + bias / APTDEC_IMG_WIDTH / 4.0f;

// Phase
memcpy(&row[APTDEC_IMG_WIDTH], &aptdec->row_buffer[phase], (APTDEC_IMG_WIDTH - phase) * sizeof(float));
memcpy(&row[APTDEC_IMG_WIDTH - phase], aptdec->row_buffer, phase * sizeof(float));

return 1;
}

+ 177
- 0
libaptdec/effects.c Просмотреть файл

@@ -0,0 +1,177 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/

#include <aptdec.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>

#include "algebra.h"
#include "util.h"
#include "filter.h"

void aptdec_equalize(aptdec_image_t *img, aptdec_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 * APTDEC_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 * APTDEC_IMG_WIDTH + x + region.offset];
img->data[y * APTDEC_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 * APTDEC_IMG_WIDTH + x + offset], regr);
data[y * APTDEC_IMG_WIDTH + x + offset] = clamp_int(roundf(pv), 0, 255);
}
}
}

void aptdec_stretch(aptdec_image_t *img, aptdec_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*APTDEC_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 aptdec_denoise(aptdec_image_t *img, aptdec_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] = { 0.0f };
int pixeln = 0;
for (int y2 = -1; y2 < 2; y2++) {
for (int x2 = -1; x2 < 2; x2++) {
pixels[pixeln++] = img->data[(y + y2) * APTDEC_IMG_WIDTH + (x + region.offset) + x2];
}
}

if (standard_deviation(pixels, 9) > 15) {
img->data[y * APTDEC_IMG_WIDTH + x + region.offset] = medianf(pixels, 9);
}
}
}
}

// Flips a channel, for northbound passes
void aptdec_flip(aptdec_image_t *img, aptdec_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) * APTDEC_IMG_WIDTH + region.offset + x],
&img->data[y * APTDEC_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 aptdec_crop(aptdec_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 = calloc(img->rows, sizeof(float));
int startCrop = 0;
int endCrop = img->rows;

for (size_t y = 0; y < img->rows; y++) {
float temp[39] = { 0.0f };
for (size_t i = 0; i < 39; i++) {
temp[i] = img->data[y * APTDEC_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 * APTDEC_IMG_WIDTH], img->rows * APTDEC_IMG_WIDTH * sizeof(float));

free(spc_rows);
return startCrop;
}

+ 113
- 0
libaptdec/filter.c Просмотреть файл

@@ -0,0 +1,113 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/

#include "filter.h"

#include <math.h>
// SSE2 intrinsics
#ifdef __x86_64__
#include <emmintrin.h>
#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) {
#ifdef _MSC_VER
float *_taps = (float *)_alloca(len * sizeof(float));
#else
float _taps[len];
#endif

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);
}

src/filter.h → 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 <https://www.gnu.org/licenses/>.
*/
#ifndef LIBAPTDEC_FILTER_H_
#define LIBAPTDEC_FILTER_H_
#include <complex.h>
#include <stddef.h>
#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

+ 246
- 0
libaptdec/image.c Просмотреть файл

@@ -0,0 +1,246 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <float.h>
#include <aptdec.h>

#include "algebra.h"
#include "util.h"
#include "calibration.h"

#define APTDEC_COUNT_RATIO (1023.0f/255.0f)

aptdec_image_t aptdec_image_clone(aptdec_image_t img) {
aptdec_image_t _img = img;
_img.data = calloc(APTDEC_IMG_WIDTH * img.rows, sizeof(uint8_t));
memcpy(_img.data, img.data, APTDEC_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 = calloc(rows, sizeof(float));
for (size_t y = 0; y < rows; y++) {
telemetry_rows[y] = meanf(&data[y*APTDEC_IMG_WIDTH + offset + APTDEC_CH_WIDTH], APTDEC_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 = APTDEC_WEDGE_HEIGHT; y <= rows - APTDEC_WEDGE_HEIGHT; y++) {
float difference = sumf(&telemetry_rows[y - APTDEC_WEDGE_HEIGHT], APTDEC_WEDGE_HEIGHT) - sumf(&telemetry_rows[y], APTDEC_WEDGE_HEIGHT);

// Find the maximum difference
if (difference > max_difference) {
max_difference = difference;
telemetry_offset = (y + 64) % APTDEC_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-APTDEC_FRAME_LEN; y += APTDEC_FRAME_LEN) {
float noise = 0.0f;
for (size_t i = 0; i < APTDEC_FRAME_WEDGES; i++) {
noise += standard_deviation(&telemetry_rows[y + i*APTDEC_WEDGE_HEIGHT], APTDEC_WEDGE_HEIGHT);
}

if (noise < best_noise) {
best_noise = noise;
best_frame = y;
}
}

for (size_t i = 0; i < APTDEC_FRAME_WEDGES; i++) {
wedges[i] = meanf(&telemetry_rows[best_frame + i*APTDEC_WEDGE_HEIGHT], APTDEC_WEDGE_HEIGHT);
}

free(telemetry_rows);
}

static float average_spc(aptdec_image_t *img, size_t offset) {
float *rows = calloc(img->rows, sizeof(float));
float average = 0.0f;
for (size_t y = 0; y < img->rows; y++) {
float row_average = 0.0f;
for (size_t x = 0; x < APTDEC_SPC_WIDTH; x++) {
row_average += img->data[y*APTDEC_IMG_WIDTH + offset - APTDEC_SPC_WIDTH + x];
}
row_average /= (float)APTDEC_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++;
}
}

free(rows);
return weighted_average / (float)n;
}

aptdec_image_t aptdec_normalize(const float *data, size_t rows, aptdec_satellite_t satellite, int *error) {
aptdec_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[APTDEC_FRAME_WEDGES];
float wedges_cha[APTDEC_FRAME_WEDGES];
float wedges_chb[APTDEC_FRAME_WEDGES];
decode_telemetry(data, rows, APTDEC_CHA_OFFSET, wedges_cha);
decode_telemetry(data, rows, APTDEC_CHB_OFFSET, wedges_chb);
for (size_t i = 0; i < APTDEC_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 < APTDEC_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 * APTDEC_IMG_WIDTH);
for (size_t i = 0; i < rows * APTDEC_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, APTDEC_CHA_OFFSET);
img.space_view[1] = average_spc(&img, APTDEC_CHB_OFFSET);

return img;
}

static void make_thermal_lut(aptdec_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] = { 0.0f };
for (size_t n = 0; n < 4; n++) {
T[n] = quadratic_calc(img->telemetry[1][n + 9] * APTDEC_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] * APTDEC_COUNT_RATIO;
float Cb = img->telemetry[1][14] * APTDEC_COUNT_RATIO;
for (size_t i = 0; i < 256; i++) {
float Nl = Ns + (Nbb - Ns) * (Cs - i * APTDEC_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 aptdec_calibrate_thermal(aptdec_image_t *img, aptdec_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] = { 0.0f };
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 * APTDEC_IMG_WIDTH + region.offset + x]];
img->data[y * APTDEC_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 * APTDEC_COUNT_RATIO, cal.visible[channel].high) / 100.0f * 255.0f;
} else {
return linear_calc(value * APTDEC_COUNT_RATIO, cal.visible[channel].low) / 100.0f * 255.0f;
}
}

int aptdec_calibrate_visible(aptdec_image_t *img, aptdec_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 * APTDEC_IMG_WIDTH + region.offset + x], img->ch[0]-1, calibration);
img->data[y * APTDEC_IMG_WIDTH + region.offset + x] = clamp_int(roundf(albedo), 0, 255);
}
}

return 0;
}

void aptdec_strip(aptdec_image_t* img) {
for (size_t y = 0; y < img->rows; y++) {
memcpy(&img->data[y*APTDEC_IMG_WIDTH], &img->data[y*APTDEC_IMG_WIDTH + APTDEC_CHA_OFFSET], APTDEC_CH_WIDTH);
memcpy(&img->data[y*APTDEC_IMG_WIDTH + APTDEC_CH_WIDTH], &img->data[y*APTDEC_IMG_WIDTH + APTDEC_CHB_OFFSET], APTDEC_CH_WIDTH);
}
}

+ 172
- 0
libaptdec/include/aptdec.h Просмотреть файл

@@ -0,0 +1,172 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/

#ifndef APTDEC_H_
#define APTDEC_H_

#include <stddef.h>
#include <stdint.h>

#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 APTDEC_WEDGE_HEIGHT 8
// Numbers of wedges in a frame
#define APTDEC_FRAME_WEDGES 16
// Height of a telemetry frame
#define APTDEC_FRAME_LEN (APTDEC_WEDGE_HEIGHT * APTDEC_FRAME_WEDGES)

// Width of the overall image
#define APTDEC_IMG_WIDTH 2080
// Width of sync marker
#define APTDEC_SYNC_WIDTH 39
// Width of space view
#define APTDEC_SPC_WIDTH 47
// Width of telemetry
#define APTDEC_TELEMETRY_WIDTH 45
// Width of a single video channel
#define APTDEC_CH_WIDTH 909

// Offset to channel A video data
#define APTDEC_CHA_OFFSET (APTDEC_SYNC_WIDTH + APTDEC_SPC_WIDTH)
// Offset to channel B video data
#define APTDEC_CHB_OFFSET (APTDEC_SYNC_WIDTH + APTDEC_SPC_WIDTH + APTDEC_CH_WIDTH + APTDEC_TELEMETRY_WIDTH + APTDEC_SYNC_WIDTH + APTDEC_SPC_WIDTH)

// Number of rows needed for aptdec_normalize to (reliably) work
#define APTDEC_NORMALIZE_ROWS (APTDEC_FRAME_LEN * 2)
// Maximum amount of samples that will be requested from aptdec_callback_t
#define APTDEC_BUFFER_SIZE 16384

// 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 aptdec_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 aptdec_satellite {
NOAA15,
NOAA18,
NOAA19
} aptdec_satellite_t;

typedef struct aptdec_image {
uint8_t *data; // Image data
size_t rows; // Number of rows

// Telemetry
aptdec_satellite_t satellite;
avhrr_channel_t ch[2];
float space_view[2];
float telemetry[2][16];
} aptdec_image_t;

typedef struct aptdec_rgb {
uint8_t r, g, b;
} aptdec_rgb_t;

typedef struct aptdec_region {
size_t offset;
size_t width;
} aptdec_region_t;

typedef struct aptdec 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 aptdec_image_t struct
// Useful for calibration
APTDEC_API aptdec_image_t aptdec_image_clone(aptdec_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 *aptdec);

// Normalize and quantize raw image data
// Data is arranged so that each row starts at APTDEC_IMG_WIDTH*y
APTDEC_API aptdec_image_t aptdec_normalize(const float *data, size_t rows, aptdec_satellite_t satellite, int *error);

// Get an entire row of pixels
// Requires that `row` has enough space to store APTDEC_IMG_WIDTH*2
// Returns 0 when `callback` return value != count
APTDEC_API int aptdec_getrow(aptdec_t *aptdec, float *row, aptdec_callback_t callback, void *context);

// Calibrate channels
APTDEC_API int aptdec_calibrate_thermal(aptdec_image_t *img, aptdec_region_t region);
APTDEC_API int aptdec_calibrate_visible(aptdec_image_t *img, aptdec_region_t region);

APTDEC_API void aptdec_denoise (aptdec_image_t *img, aptdec_region_t region);
APTDEC_API void aptdec_flip (aptdec_image_t *img, aptdec_region_t region);
APTDEC_API void aptdec_stretch (aptdec_image_t *img, aptdec_region_t region);
APTDEC_API void aptdec_equalize(aptdec_image_t *img, aptdec_region_t region);
APTDEC_API int aptdec_crop (aptdec_image_t *img);
APTDEC_API void aptdec_strip (aptdec_image_t* img);

// Composite two RGB values as layers, in most cases bottom_a will be 1.0f
APTDEC_API aptdec_rgb_t aptdec_composite_rgb(aptdec_rgb_t top, float top_a, aptdec_rgb_t bottom, float bottom_a);

// Apply a gradient such as aptdec_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 aptdec_rgb_t aptdec_gradient(const uint32_t *gradient, uint8_t val);

static const aptdec_region_t APTDEC_REGION_CHA = { APTDEC_CHA_OFFSET, APTDEC_CH_WIDTH };
static const aptdec_region_t APTDEC_REGION_CHB = { APTDEC_CHB_OFFSET, APTDEC_CH_WIDTH };
static const aptdec_region_t APTDEC_REGION_CHA_FULL = { 0, APTDEC_IMG_WIDTH/2 };
static const aptdec_region_t APTDEC_REGION_CHB_FULL = { APTDEC_IMG_WIDTH/2, APTDEC_IMG_WIDTH/2 };
static const aptdec_region_t APTDEC_REGION_FULL = { 0, APTDEC_IMG_WIDTH };

APTDEC_API extern const uint32_t aptdec_temperature_gradient[256];
APTDEC_API extern const uint32_t aptdec_precipitation_gradient[58];

#ifdef __cplusplus
}
#endif

#endif

+ 40
- 0
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 <https://www.gnu.org/licenses/>.
*/

#include "util.h"

#include <stdlib.h>
#include <math.h>

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

+ 29
- 0
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 <https://www.gnu.org/licenses/>.
*/

#ifndef LIBAPTDEC_UTIL_H_
#define LIBAPTDEC_UTIL_H_

#include <stdint.h>

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

Двоичные данные
luts/N19-HRPT-Falsecolor.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 16 KiB

Двоичные данные
luts/WXtoImg-BD.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 1.6 KiB

Двоичные данные
luts/WXtoImg-CC.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 1.6 KiB

Двоичные данные
luts/WXtoImg-EC.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 1.6 KiB

Двоичные данные
luts/WXtoImg-HE.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 1.7 KiB

Двоичные данные
luts/WXtoImg-HF.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 1.6 KiB

Двоичные данные
luts/WXtoImg-JF.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 1.6 KiB

Двоичные данные
luts/WXtoImg-JJ.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 1.8 KiB

Двоичные данные
luts/WXtoImg-MB.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 1.6 KiB

Двоичные данные
luts/WXtoImg-MD.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 1.6 KiB

Двоичные данные
palettes/WXtoImg-N15-HVC.png → luts/WXtoImg-N15-HVC.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 15 KiB Ширина: 256  |  Высота: 256  |  Размер: 12 KiB

Двоичные данные
palettes/WXtoImg-N18-HVC.png → luts/WXtoImg-N18-HVC.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 15 KiB Ширина: 256  |  Высота: 256  |  Размер: 11 KiB

Двоичные данные
palettes/WXtoImg-N19-HVC.png → luts/WXtoImg-N19-HVC.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 15 KiB Ширина: 256  |  Высота: 256  |  Размер: 12 KiB

Двоичные данные
luts/WXtoImg-NO.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 2.0 KiB

Двоичные данные
luts/WXtoImg-TA.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 1.5 KiB

Двоичные данные
luts/WXtoImg-ZA.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 1.5 KiB

Двоичные данные
palettes/WXtoImg-class.png → luts/WXtoImg-class.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 13 KiB Ширина: 256  |  Высота: 256  |  Размер: 10 KiB

Двоичные данные
luts/WXtoImg-fire.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 1.5 KiB

Двоичные данные
luts/WXtoImg-sea.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 1.5 KiB

Двоичные данные
palettes/N19-HRPT-Falsecolor.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 16 KiB

Двоичные данные
palettes/WXtoImg-BD.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 4.9 KiB

Двоичные данные
palettes/WXtoImg-CC.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 4.9 KiB

Двоичные данные
palettes/WXtoImg-EC.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 4.9 KiB

Двоичные данные
palettes/WXtoImg-HE.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 5.0 KiB

Двоичные данные
palettes/WXtoImg-HF.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 4.9 KiB

Двоичные данные
palettes/WXtoImg-JF.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 4.9 KiB

Двоичные данные
palettes/WXtoImg-JJ.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 5.0 KiB

Двоичные данные
palettes/WXtoImg-MB.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 4.9 KiB

Двоичные данные
palettes/WXtoImg-MD.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 4.9 KiB

Двоичные данные
palettes/WXtoImg-NO.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 5.3 KiB

Двоичные данные
palettes/WXtoImg-TA.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 4.8 KiB

Двоичные данные
palettes/WXtoImg-ZA.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 4.8 KiB

Двоичные данные
palettes/WXtoImg-fire.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 4.8 KiB

Двоичные данные
palettes/WXtoImg-sea.png Просмотреть файл

До После
Ширина: 256  |  Высота: 256  |  Размер: 4.8 KiB

+ 0
- 126
src/apt.h Просмотреть файл

@@ -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 <https://www.gnu.org/licenses/>.
*
*/

#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

+ 0
- 1
src/argparse

@@ -1 +0,0 @@
Subproject commit c612dc03958cdbd538ca306d61853b643a435933

+ 0
- 101
src/calibration.c Просмотреть файл

@@ -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 <https://www.gnu.org/licenses/>.
*/

#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];
}
}

+ 0
- 83
src/color.c Просмотреть файл

@@ -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 <https://www.gnu.org/licenses/>.
*
*/

#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"};

+ 0
- 3
src/color.h Просмотреть файл

@@ -1,3 +0,0 @@
#include "common.h"

#define MCOMPOSITE(m1, a1, m2, a2) (m1 * a1 + m2 * a2 * (1 - a1))

+ 0
- 60
src/common.h Просмотреть файл

@@ -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 <https://www.gnu.org/licenses/>.
*
*/

// 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

+ 0
- 258
src/dsp.c Просмотреть файл

@@ -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 <https://www.gnu.org/licenses/>.
*/

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#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;
}

+ 0
- 62
src/filter.c Просмотреть файл

@@ -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 <https://www.gnu.org/licenses/>.
*/

#include "filter.h"

#include <math.h>

#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;
}

+ 0
- 388
src/image.c Просмотреть файл

@@ -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 <https://www.gnu.org/licenses/>.
*
*/

#include "image.h"

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#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);
}
}
}

+ 0
- 2
src/image.h Просмотреть файл

@@ -1,2 +0,0 @@
#include "apt.h"
#include "common.h"

+ 0
- 56
src/libs/median.c Просмотреть файл

@@ -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

+ 0
- 316
src/main.c Просмотреть файл

@@ -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 <https://www.gnu.org/licenses/>.
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef _MSC_VER
#include <libgen.h>
#else
#include <windows.h>
#endif
#include <errno.h>
#include <math.h>
#include <sndfile.h>
#include <time.h>

#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);
}
}

+ 0
- 411
src/pngio.c Просмотреть файл

@@ -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 <https://www.gnu.org/licenses/>.
*
*/

#include "pngio.h"

#include <math.h>
#include <png.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#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);
}

+ 0
- 11
src/pngio.h Просмотреть файл

@@ -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();

+ 0
- 80
src/taps.h Просмотреть файл

@@ -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 <https://www.gnu.org/licenses/>.
*/

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]))

Двоичные данные
util/PrecipitationPalette.png Просмотреть файл

До После
Ширина: 1  |  Высота: 58  |  Размер: 1.4 KiB Ширина: 1  |  Высота: 58  |  Размер: 199 B

Двоичные данные
util/TempPalette.png Просмотреть файл

До После
Ширина: 1  |  Высота: 256  |  Размер: 901 B Ширина: 1  |  Высота: 256  |  Размер: 454 B

Двоичные данные
util/Temperature.png Просмотреть файл

До После
Ширина: 298  |  Высота: 68  |  Размер: 2.0 KiB Ширина: 298  |  Высота: 68  |  Размер: 1.6 KiB

+ 37
- 0
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};")

+ 0
- 26
util/img2pal.py Просмотреть файл

@@ -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};")

Загрузка…
Отмена
Сохранить