diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..5bf5b856 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit = src/python/librir/tools/_thermavip.py \ No newline at end of file diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 705032b7..84651cbb 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -4,7 +4,7 @@ on: push: branches: [ "main" ] pull_request: - branches: [ "main" ] + branches: [ "main", "releases/**" ] workflow_dispatch: inputs: compiler: diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 553be955..1da60990 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -4,7 +4,7 @@ on: push: branches: [ "main" ] pull_request: - branches: [ "main" ] + branches: [ "main", "releases/**" ] workflow_dispatch: inputs: compiler: diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 68fa25eb..867e0390 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -4,7 +4,7 @@ on: push: branches: [ "main" ] pull_request: - branches: [ "main" ] + branches: [ "main", "releases/**" ] workflow_dispatch: workflow_call: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 376f7b85..08ce4139 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,16 +39,18 @@ jobs: run: | python -m pip install --upgrade pip pip install ruff pytest pytest-cov - pip install -e build/install + pip install -e build/install/python - name: Test with pytest continue-on-error: false run: | python -m pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=librir tests/python -m "not thermavip" | tee pytest-coverage.txt + - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + # - name: Pytest coverage comment # uses: MishaKav/pytest-coverage-comment@main # with: diff --git a/.gitignore b/.gitignore index 9fd1dc77..07912e77 100644 --- a/.gitignore +++ b/.gitignore @@ -183,3 +183,7 @@ plugins/* .vscode/* +src/python/setup.py +src/python/pyproject.toml +src/python/README.md +src/python/librir/libs diff --git a/3rd_64/build-gcc.sh b/3rd_64/build-gcc.sh index 82cc2924..d3aed833 100755 --- a/3rd_64/build-gcc.sh +++ b/3rd_64/build-gcc.sh @@ -165,6 +165,9 @@ else echo "Dir $FILE does not exist." git clone https://github.com/ultravideo/kvazaar.git fi +cd kvazaar +git checkout v2.2.0 +cd .. FILE=kvazaar/install/lib/pkgconfig/kvazaar.pc if [ -f $FILE ]; then diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b34d258..4a2d8a3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16) project(librir - VERSION 4.2.0 + VERSION 5.0.0 DESCRIPTION "librir" HOMEPAGE_URL "https://github.com/IRFM/librir" LANGUAGES CXX C @@ -12,50 +12,36 @@ project(librir set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) -include(GNUInstallDirs) option(LOCAL_INSTALL "Install locally" ON) -option(WITH_WEST "Fetch WEST plugin" OFF) IF(LOCAL_INSTALL) - SET(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install) + SET(CMAKE_INSTALL_PREFIX ${PROJECT_BINARY_DIR}/install) MESSAGE(STATUS ${CMAKE_INSTALL_PREFIX}) set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out) set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out) - + IF(NOT MSVC) SET(CMAKE_INSTALL_RPATH $ORIGIN) SET(CMAKE_SKIP_BUILD_RPATH FALSE) SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) - #~ SET(CMAKE_INSTALL_RPATH_USE_ORIGIN TRUE) SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) ENDIF() ENDIF() - - -IF(WITH_WEST) - IF(EXISTS ${PROJECT_SOURCE_DIR}/plugins/west) - message(STATUS "Pulling WEST repository...") - execute_process(COMMAND git pull WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/plugins/west) - ELSE() - message(STATUS "Cloning WEST repository...") - execute_process(COMMAND git clone http://irfm-gitlab.intra.cea.fr/data-analysis/infrared/west.git ${PROJECT_SOURCE_DIR}/plugins/west) - ENDIF() -endif() - +include(GNUInstallDirs) IF (UNIX) execute_process(COMMAND "chmod 100755 configure_ffmpeg") ENDIF() IF (NOT MSVC) -# Create a dummy target to build ffmpeg as a prebuild step -add_custom_target(ffmpeg BYPRODUCTS "${PROJECT_SOURCE_DIR}/build_ffmpeg.h" - COMMAND ${PROJECT_SOURCE_DIR}/configure_ffmpeg) -add_executable(dummy ${PROJECT_SOURCE_DIR}/dummy.c "${PROJECT_SOURCE_DIR}/build_ffmpeg.h") -target_include_directories(dummy PRIVATE "${PROJECT_SOURCE_DIR}") - + # Create a dummy target to build ffmpeg as a prebuild step + add_custom_target(ffmpeg BYPRODUCTS "${PROJECT_SOURCE_DIR}/build_ffmpeg.h" + COMMAND ${PROJECT_SOURCE_DIR}/configure_ffmpeg) + add_executable(dummy ${PROJECT_SOURCE_DIR}/dummy.c "${PROJECT_SOURCE_DIR}/build_ffmpeg.h") + target_include_directories(dummy PRIVATE "${PROJECT_SOURCE_DIR}") + ENDIF() @@ -72,49 +58,18 @@ IF (WIN32) add_definitions("-D_WINDOWS") ENDIF() + + configure_file(rir_config.h.in rir_config.h) -install(FILES ${CMAKE_BINARY_DIR}/rir_config.h DESTINATION include) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/rir_config.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include) option(BUILD_SHARED_LIBS "Build using shared libraries" ON) -# set(THREADS_PREFER_PTHREAD_FLAG ON) -add_subdirectory(extra) -INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR}) - - -# # DOCTEST -# set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${CMAKE_BINARY_DIR}/doctest/doctest.h) -# set(doctest_FOUND FALSE) -# # Expose required variable (DOCTEST_INCLUDE_DIR) to parent scope -# set(DOCTEST_INCLUDE_DIR ${CMAKE_BINARY_DIR}/doctest/src/doctest CACHE INTERNAL "Path to include folder for doctest") -# IF(EXISTS ${DOCTEST_INCLUDE_DIR}/doctest/doctest.h) - # set(doctest_FOUND TRUE) -# endif() -# MESSAGE(STATUS ${doctest_FOUND}) -# if(NOT doctest_FOUND) - # include(ExternalProject) - # find_package(Git) - # if(Git_FOUND) - # ExternalProject_Add( - # doctest - # PREFIX ${CMAKE_BINARY_DIR}/doctest - # GIT_REPOSITORY https://github.com/onqtam/doctest.git - # TIMEOUT 10 - # UPDATE_COMMAND ${GIT_EXECUTABLE} pull - # CONFIGURE_COMMAND "" - # BUILD_COMMAND "" - # INSTALL_COMMAND "" - # LOG_DOWNLOAD ON - # ) - # endif() -# endif() -# include_directories(${DOCTEST_INCLUDE_DIR}) - -# list(APPEND TARGETS ${PRESENT_PLUGINS}) -# message(STATUS "PRESENT_PLUGINS : " ${PRESENT_PLUGINS}) -# message(STATUS "TARGETS : " ${TARGETS}) -# add it globally +# TODO: move all elements in extra folder to dedicated FetchContent/find_package modules +add_subdirectory(extra) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) # Create global variable used in librir.pc.in # Plugins can add libraries to link with for generated librir.pc @@ -122,49 +77,38 @@ set(PLUGINS_LIBS "" CACHE INTERNAL "") set(PLUGINS_PYTHON_REQUIRE "" CACHE INTERNAL "") +# Pusue compilation add_subdirectory(src) -add_subdirectory(plugins) add_subdirectory(tests) -# enable_testing() -# TOREMOVE - -# find_package(Python COMPONENTS Interpreter) -# IF(NOT Python_FOUND) - -# MESSAGE(STATUS "PYTHON NOT FOUND") - -# endif() - -# MESSAGE(STATUS "found python") - - -# Perform this action once more (already done in src/CMakeLists.txt) to copy plugins binaries -install(DIRECTORY ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/ - DESTINATION ${CMAKE_INSTALL_PREFIX}/librir/libs -) - -# # Perform this action to copy shared objects into python source folder (for devlopment purposes) -# install(DIRECTORY ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/ -# DESTINATION ${CMAKE_SOURCE_DIR}/src/python/librir/libs -# ) - # Generate requirements.txt configure_file(requirements.txt.in requirements.txt @ONLY) -install(FILES ${CMAKE_BINARY_DIR}/requirements.txt DESTINATION ${CMAKE_INSTALL_PREFIX}) - - -#set(INCLUDE_DIR "include" CACHE PATH "Location of header files" ) -#set(LIB_DIR "lib lib64" CACHE PATH "Location of libraries" ) -#set(LIBRARIES "tools geometry signal_processing video_io" CACHE PATH "Libraries" ) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/requirements.txt DESTINATION ${CMAKE_INSTALL_PREFIX}) + + +install( + TARGETS ${LIBRIR_TARGETS} + PUBLIC_HEADER + ARCHIVE + RUNTIME + ) +install( + TARGETS ${LIBRIR_TARGETS} + EXPORT LibrirTargets + ) +install(EXPORT LibrirTargets + FILE LibrirTargets.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/librir + NAMESPACE librir:: +) + +# Configure and install PackageConfig files include(CMakePackageConfigHelpers) configure_package_config_file(cmake/librirConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/librirConfig.cmake - INSTALL_DESTINATION ${CMAKE_INSTALL_PREFIX}/cmake - PATH_VARS - PLUGINS_LIBS) - #INCLUDE_DIR LIB_DIR LIBRARIES PLUGINS_LIBS) + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake +) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/librirConfigVersion.cmake @@ -173,7 +117,7 @@ write_basic_package_version_file( install(FILES ${CMAKE_CURRENT_BINARY_DIR}/librirConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/librirConfigVersion.cmake - DESTINATION ${CMAKE_INSTALL_PREFIX}/cmake/librir ) + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/librir ) @@ -182,5 +126,5 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/librirConfig.cmake list(TRANSFORM PLUGINS_LIBS PREPEND "-l") configure_file(librir.pc.in librir.pc @ONLY) configure_file(librir.pc.in ${CMAKE_INSTALL_LIBDIR}/pkgconfig/librir.pc @ONLY) -install(FILES ${CMAKE_BINARY_DIR}/librir.pc DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/librir.pc DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) diff --git a/README.md b/README.md index b542e78f..b003e353 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![Build on Linux](https://github.com/IRFM/librir/actions/workflows/build-linux.yml/badge.svg)](https://github.com/IRFM/librir/actions/workflows/build-linux.yml) [![Build on Windows](https://github.com/IRFM/librir/actions/workflows/build-windows.yml/badge.svg?branch=main)](https://github.com/IRFM/librir/actions/workflows/build-windows.yml) +[![codecov](https://codecov.io/gh/IRFM/librir/graph/badge.svg?token=33OAGARS5K)](https://codecov.io/gh/IRFM/librir) # Librir diff --git a/build.bat b/build.bat index bf859fba..e9c6893f 100644 --- a/build.bat +++ b/build.bat @@ -1,6 +1,5 @@ @echo off -SET USE_WEST=0 SET CMAKE_OPTIONS=-DLOCAL_INSTALL=ON SET CMAKE_BUILD=Release SET WHEEL=0 @@ -16,12 +15,6 @@ IF NOT "%1"=="" ( SET CMAKE_BUILD=Debug ) ELSE IF "%1"=="-d" ( SET CMAKE_BUILD=Debug - ) ELSE IF "%1"=="--with" ( - SET URL=%2 - SHIFT - cd plugins - CALL :clone_pull %URL% - cd .. ) ELSE IF "%1"=="--build-wheel" ( SET WHEEL=1 ) ELSE IF "%1"=="--global" ( @@ -47,7 +40,7 @@ REM Build wheel if necessary IF "%WHEEL%" == "1" ( cd install python -c "import librir;print('librir is importable !')" -python setup.py bdist_wheel +pip wheel --no-deps . cd .. ) cd .. @@ -59,7 +52,6 @@ echo "Build script for librir" echo echo "Usage:" echo "--help display help" -echo "--with download and compile the given plugin using its git url" echo "--debug debug build only (default is release only)" echo "--build-wheel build Python wheel package" echo "--global global installation instead of local one" diff --git a/build.sh b/build.sh index db01d6d2..b1e1d447 100755 --- a/build.sh +++ b/build.sh @@ -105,9 +105,9 @@ make install # Build wheel if necessary if [ "$WHEEL" -eq "1" ]; then echo "Build wheel" - cd install + cd install/python python -c "import librir;print('librir is importable !')" - python setup.py bdist_wheel + pip wheel --no-deps . cd .. fi diff --git a/extra/CMakeLists.txt b/extra/CMakeLists.txt index 0ed99b2f..360ce33b 100644 --- a/extra/CMakeLists.txt +++ b/extra/CMakeLists.txt @@ -1,4 +1,4 @@ - +# TODO: get blosc, minizip, jpeg-9b, charls from official repositories with FetchContent/find_package IF(MSVC) SET(MINIZIP_SRC @@ -84,10 +84,6 @@ SET(MINIZIP_SRC ) ENDIF() -# SET(ZSTD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/zstd PARENT_SCOPE) - -# add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/zstd) - add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/blosc) SET(BLOSC_SRC ${BLOSC_SRC} PARENT_SCOPE) @@ -102,13 +98,4 @@ SET(JPEG9B_SRC ${JPEG9B_SRC} PARENT_SCOPE) SET(CHARLS_SRC ${CHARLS_SRC} PARENT_SCOPE) -SET(EXTRAS_INCLUDE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE) - -# SET(ZSTD_INCLUDE_DIR -# ${ZSTD_INCLUDE_DIR} -# PARENT_SCOPE -# ) -# SET(ZSTD_SRC -# ${ZSTD_SRC} -# PARENT_SCOPE -# ) \ No newline at end of file +SET(EXTRAS_INCLUDE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE) \ No newline at end of file diff --git a/librir.pc.in b/librir.pc.in index 2311da65..042dd7e7 100644 --- a/librir.pc.in +++ b/librir.pc.in @@ -8,9 +8,9 @@ libdir="${prefix}/lib" includedir="${prefix}/include" Name: @PROJECT_NAME@ -Description: @CMAKE_PROJECT_DESCRIPTION@ -URL: @CMAKE_PROJECT_HOMEPAGE_URL@ +Description: @PROJECT_DESCRIPTION@ +URL: @PROJECT_HOMEPAGE_URL@ Version: @PROJECT_VERSION@ Cflags: -I"${includedir}" -Libs: -L"${libdir}" -ltools -lgeometry -lsignal_processing -lvideo_io @PLUGINS_LIBS@ -Libs.private: -L"${libdir}" -ltools -lgeometry -lsignal_processing -lvideo_io @PLUGINS_LIBS@ \ No newline at end of file +Libs: -L"${libdir}" -ltools -lgeometry -lsignal_processing -lvideo_io +Libs.private: -L"${libdir}" -ltools -lgeometry -lsignal_processing -lvideo_io \ No newline at end of file diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt deleted file mode 100644 index 1d8194a3..00000000 --- a/plugins/CMakeLists.txt +++ /dev/null @@ -1,56 +0,0 @@ - -MACRO(SUBDIRLIST result curdir) - FILE(GLOB children ${curdir}/*) # This was changed - SET(dirlist "") - FOREACH(child ${children}) - IF(IS_DIRECTORY ${child}) # This was changed - get_filename_component(dirname ${child} NAME ) - LIST(APPEND dirlist ${dirname}) - ENDIF() - ENDFOREACH() - SET(${result} ${dirlist}) -ENDMACRO() - -SUBDIRLIST(_PRESENT_PLUGINS ${CMAKE_CURRENT_SOURCE_DIR}) -MESSAGE(STATUS "_PRESENT_PLUGINS : " ${_PRESENT_PLUGINS}) -set(EXCLUDED_PATTERNS) - -function(cat IN_FILE OUT_FILE) - file(READ ${IN_FILE} CONTENTS) - file(APPEND ${OUT_FILE} "${CONTENTS}") -endfunction() - -set(INIT_PY_FILES) -file(REMOVE __init__.py.in) -file(WRITE __init__.py.in "") -cat(${CMAKE_SOURCE_DIR}/src/python/librir/__init__.py __init__.py.in ) - -foreach(plugin ${_PRESENT_PLUGINS}) - string(TOUPPER ${plugin} UPPERCASE_PLUGIN) - if(DISABLE_${UPPERCASE_PLUGIN}) - continue() - endif() - - - add_subdirectory(${plugin}) - MESSAGE(STATUS "Include '${plugin}' plugin") - list(APPEND PRESENT_PLUGINS ${plugin}) -endforeach() - -# # Call the "cat" function for each input file -foreach(INIT_PY_FILE ${INIT_PY_FILES}) - cat(${INIT_PY_FILE} __init__.py.in) -endforeach() - - -# Copy the temporary file to the final location -configure_file(__init__.py.in __init__.py @ONLY) - - -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/__init__.py - DESTINATION ${CMAKE_INSTALL_PREFIX}/librir) - - -SET(EXCLUDED_PATTERNS ${EXCLUDED_PATTERNS} PARENT_SCOPE) -SET(PRESENT_PLUGINS ${PRESENT_PLUGINS} PARENT_SCOPE) -# add_subdirectory(test) \ No newline at end of file diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..1cc78f45 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,398 @@ +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" + +[[package]] +name = "coverage" +version = "7.3.3" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "exceptiongroup" +version = "1.2.0" +description = "Backport of PEP 654 (exception groups)" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "joblib" +version = "1.3.2" +description = "Lightweight pipelining with Python functions" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "numpy" +version = "1.26.2" +description = "Fundamental package for array computing in Python" +category = "main" +optional = false +python-versions = ">=3.9" + +[[package]] +name = "opencv-python" +version = "4.5.5.64" +description = "Wrapper package for OpenCV python bindings." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +numpy = [ + {version = ">=1.21.2", markers = "python_version >= \"3.10\" or python_version >= \"3.6\" and platform_system == \"Darwin\" and platform_machine == \"arm64\""}, + {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, + {version = ">=1.14.5", markers = "python_version >= \"3.7\""}, + {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, +] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "pandas" +version = "2.1.4" +description = "Powerful data structures for data analysis, time series, and statistics" +category = "main" +optional = false +python-versions = ">=3.9" + +[package.dependencies] +numpy = [ + {version = ">=1.22.4,<2", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.1" + +[package.extras] +all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"] +aws = ["s3fs (>=2022.05.0)"] +clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"] +compression = ["zstandard (>=0.17.0)"] +computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"] +feather = ["pyarrow (>=7.0.0)"] +fss = ["fsspec (>=2022.05.0)"] +gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"] +hdf5 = ["tables (>=3.7.0)"] +html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"] +mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"] +parquet = ["pyarrow (>=7.0.0)"] +performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"] +plot = ["matplotlib (>=3.6.1)"] +postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"] +spss = ["pyreadstat (>=1.1.5)"] +sql-other = ["SQLAlchemy (>=1.4.36)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.8.0)"] + +[[package]] +name = "pluggy" +version = "1.2.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "7.4.3" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2023.3.post1" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "tzdata" +version = "2023.3" +description = "Provider of IANA time zone data" +category = "main" +optional = false +python-versions = ">=2" + +[metadata] +lock-version = "1.1" +python-versions = ">=3.9" +content-hash = "e937138e61e5488d3fdbb8c9a30292aef43719cd5e3975955046cec8c9d7e377" + +[metadata.files] +colorama = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] +coverage = [ + {file = "coverage-7.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d874434e0cb7b90f7af2b6e3309b0733cde8ec1476eb47db148ed7deeb2a9494"}, + {file = "coverage-7.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee6621dccce8af666b8c4651f9f43467bfbf409607c604b840b78f4ff3619aeb"}, + {file = "coverage-7.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1367aa411afb4431ab58fd7ee102adb2665894d047c490649e86219327183134"}, + {file = "coverage-7.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f0f8f0c497eb9c9f18f21de0750c8d8b4b9c7000b43996a094290b59d0e7523"}, + {file = "coverage-7.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db0338c4b0951d93d547e0ff8d8ea340fecf5885f5b00b23be5aa99549e14cfd"}, + {file = "coverage-7.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d31650d313bd90d027f4be7663dfa2241079edd780b56ac416b56eebe0a21aab"}, + {file = "coverage-7.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9437a4074b43c177c92c96d051957592afd85ba00d3e92002c8ef45ee75df438"}, + {file = "coverage-7.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9e17d9cb06c13b4f2ef570355fa45797d10f19ca71395910b249e3f77942a837"}, + {file = "coverage-7.3.3-cp310-cp310-win32.whl", hash = "sha256:eee5e741b43ea1b49d98ab6e40f7e299e97715af2488d1c77a90de4a663a86e2"}, + {file = "coverage-7.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:593efa42160c15c59ee9b66c5f27a453ed3968718e6e58431cdfb2d50d5ad284"}, + {file = "coverage-7.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c944cf1775235c0857829c275c777a2c3e33032e544bcef614036f337ac37bb"}, + {file = "coverage-7.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eda7f6e92358ac9e1717ce1f0377ed2b9320cea070906ece4e5c11d172a45a39"}, + {file = "coverage-7.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c854c1d2c7d3e47f7120b560d1a30c1ca221e207439608d27bc4d08fd4aeae8"}, + {file = "coverage-7.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:222b038f08a7ebed1e4e78ccf3c09a1ca4ac3da16de983e66520973443b546bc"}, + {file = "coverage-7.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff4800783d85bff132f2cc7d007426ec698cdce08c3062c8d501ad3f4ea3d16c"}, + {file = "coverage-7.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fc200cec654311ca2c3f5ab3ce2220521b3d4732f68e1b1e79bef8fcfc1f2b97"}, + {file = "coverage-7.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:307aecb65bb77cbfebf2eb6e12009e9034d050c6c69d8a5f3f737b329f4f15fb"}, + {file = "coverage-7.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ffb0eacbadb705c0a6969b0adf468f126b064f3362411df95f6d4f31c40d31c1"}, + {file = "coverage-7.3.3-cp311-cp311-win32.whl", hash = "sha256:79c32f875fd7c0ed8d642b221cf81feba98183d2ff14d1f37a1bbce6b0347d9f"}, + {file = "coverage-7.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:243576944f7c1a1205e5cd658533a50eba662c74f9be4c050d51c69bd4532936"}, + {file = "coverage-7.3.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a2ac4245f18057dfec3b0074c4eb366953bca6787f1ec397c004c78176a23d56"}, + {file = "coverage-7.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f9191be7af41f0b54324ded600e8ddbcabea23e1e8ba419d9a53b241dece821d"}, + {file = "coverage-7.3.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31c0b1b8b5a4aebf8fcd227237fc4263aa7fa0ddcd4d288d42f50eff18b0bac4"}, + {file = "coverage-7.3.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee453085279df1bac0996bc97004771a4a052b1f1e23f6101213e3796ff3cb85"}, + {file = "coverage-7.3.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1191270b06ecd68b1d00897b2daddb98e1719f63750969614ceb3438228c088e"}, + {file = "coverage-7.3.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:007a7e49831cfe387473e92e9ff07377f6121120669ddc39674e7244350a6a29"}, + {file = "coverage-7.3.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:af75cf83c2d57717a8493ed2246d34b1f3398cb8a92b10fd7a1858cad8e78f59"}, + {file = "coverage-7.3.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:811ca7373da32f1ccee2927dc27dc523462fd30674a80102f86c6753d6681bc6"}, + {file = "coverage-7.3.3-cp312-cp312-win32.whl", hash = "sha256:733537a182b5d62184f2a72796eb6901299898231a8e4f84c858c68684b25a70"}, + {file = "coverage-7.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:e995efb191f04b01ced307dbd7407ebf6e6dc209b528d75583277b10fd1800ee"}, + {file = "coverage-7.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbd8a5fe6c893de21a3c6835071ec116d79334fbdf641743332e442a3466f7ea"}, + {file = "coverage-7.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:50c472c1916540f8b2deef10cdc736cd2b3d1464d3945e4da0333862270dcb15"}, + {file = "coverage-7.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e9223a18f51d00d3ce239c39fc41410489ec7a248a84fab443fbb39c943616c"}, + {file = "coverage-7.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f501e36ac428c1b334c41e196ff6bd550c0353c7314716e80055b1f0a32ba394"}, + {file = "coverage-7.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:475de8213ed95a6b6283056d180b2442eee38d5948d735cd3d3b52b86dd65b92"}, + {file = "coverage-7.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:afdcc10c01d0db217fc0a64f58c7edd635b8f27787fea0a3054b856a6dff8717"}, + {file = "coverage-7.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fff0b2f249ac642fd735f009b8363c2b46cf406d3caec00e4deeb79b5ff39b40"}, + {file = "coverage-7.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a1f76cfc122c9e0f62dbe0460ec9cc7696fc9a0293931a33b8870f78cf83a327"}, + {file = "coverage-7.3.3-cp38-cp38-win32.whl", hash = "sha256:757453848c18d7ab5d5b5f1827293d580f156f1c2c8cef45bfc21f37d8681069"}, + {file = "coverage-7.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:ad2453b852a1316c8a103c9c970db8fbc262f4f6b930aa6c606df9b2766eee06"}, + {file = "coverage-7.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b15e03b8ee6a908db48eccf4e4e42397f146ab1e91c6324da44197a45cb9132"}, + {file = "coverage-7.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:89400aa1752e09f666cc48708eaa171eef0ebe3d5f74044b614729231763ae69"}, + {file = "coverage-7.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c59a3e59fb95e6d72e71dc915e6d7fa568863fad0a80b33bc7b82d6e9f844973"}, + {file = "coverage-7.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ede881c7618f9cf93e2df0421ee127afdfd267d1b5d0c59bcea771cf160ea4a"}, + {file = "coverage-7.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3bfd2c2f0e5384276e12b14882bf2c7621f97c35320c3e7132c156ce18436a1"}, + {file = "coverage-7.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7f3bad1a9313401ff2964e411ab7d57fb700a2d5478b727e13f156c8f89774a0"}, + {file = "coverage-7.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:65d716b736f16e250435473c5ca01285d73c29f20097decdbb12571d5dfb2c94"}, + {file = "coverage-7.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a702e66483b1fe602717020a0e90506e759c84a71dbc1616dd55d29d86a9b91f"}, + {file = "coverage-7.3.3-cp39-cp39-win32.whl", hash = "sha256:7fbf3f5756e7955174a31fb579307d69ffca91ad163467ed123858ce0f3fd4aa"}, + {file = "coverage-7.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cad9afc1644b979211989ec3ff7d82110b2ed52995c2f7263e7841c846a75348"}, + {file = "coverage-7.3.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:d299d379b676812e142fb57662a8d0d810b859421412b4d7af996154c00c31bb"}, + {file = "coverage-7.3.3.tar.gz", hash = "sha256:df04c64e58df96b4427db8d0559e95e2df3138c9916c96f9f6a4dd220db2fdb7"}, +] +exceptiongroup = [ + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, +] +iniconfig = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] +joblib = [ + {file = "joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9"}, + {file = "joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1"}, +] +numpy = [ + {file = "numpy-1.26.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3703fc9258a4a122d17043e57b35e5ef1c5a5837c3db8be396c82e04c1cf9b0f"}, + {file = "numpy-1.26.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cc392fdcbd21d4be6ae1bb4475a03ce3b025cd49a9be5345d76d7585aea69440"}, + {file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36340109af8da8805d8851ef1d74761b3b88e81a9bd80b290bbfed61bd2b4f75"}, + {file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc008217145b3d77abd3e4d5ef586e3bdfba8fe17940769f8aa09b99e856c00"}, + {file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ced40d4e9e18242f70dd02d739e44698df3dcb010d31f495ff00a31ef6014fe"}, + {file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b272d4cecc32c9e19911891446b72e986157e6a1809b7b56518b4f3755267523"}, + {file = "numpy-1.26.2-cp310-cp310-win32.whl", hash = "sha256:22f8fc02fdbc829e7a8c578dd8d2e15a9074b630d4da29cda483337e300e3ee9"}, + {file = "numpy-1.26.2-cp310-cp310-win_amd64.whl", hash = "sha256:26c9d33f8e8b846d5a65dd068c14e04018d05533b348d9eaeef6c1bd787f9919"}, + {file = "numpy-1.26.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b96e7b9c624ef3ae2ae0e04fa9b460f6b9f17ad8b4bec6d7756510f1f6c0c841"}, + {file = "numpy-1.26.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aa18428111fb9a591d7a9cc1b48150097ba6a7e8299fb56bdf574df650e7d1f1"}, + {file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06fa1ed84aa60ea6ef9f91ba57b5ed963c3729534e6e54055fc151fad0423f0a"}, + {file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96ca5482c3dbdd051bcd1fce8034603d6ebfc125a7bd59f55b40d8f5d246832b"}, + {file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:854ab91a2906ef29dc3925a064fcd365c7b4da743f84b123002f6139bcb3f8a7"}, + {file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f43740ab089277d403aa07567be138fc2a89d4d9892d113b76153e0e412409f8"}, + {file = "numpy-1.26.2-cp311-cp311-win32.whl", hash = "sha256:a2bbc29fcb1771cd7b7425f98b05307776a6baf43035d3b80c4b0f29e9545186"}, + {file = "numpy-1.26.2-cp311-cp311-win_amd64.whl", hash = "sha256:2b3fca8a5b00184828d12b073af4d0fc5fdd94b1632c2477526f6bd7842d700d"}, + {file = "numpy-1.26.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a4cd6ed4a339c21f1d1b0fdf13426cb3b284555c27ac2f156dfdaaa7e16bfab0"}, + {file = "numpy-1.26.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d5244aabd6ed7f312268b9247be47343a654ebea52a60f002dc70c769048e75"}, + {file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a3cdb4d9c70e6b8c0814239ead47da00934666f668426fc6e94cce869e13fd7"}, + {file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa317b2325f7aa0a9471663e6093c210cb2ae9c0ad824732b307d2c51983d5b6"}, + {file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:174a8880739c16c925799c018f3f55b8130c1f7c8e75ab0a6fa9d41cab092fd6"}, + {file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f79b231bf5c16b1f39c7f4875e1ded36abee1591e98742b05d8a0fb55d8a3eec"}, + {file = "numpy-1.26.2-cp312-cp312-win32.whl", hash = "sha256:4a06263321dfd3598cacb252f51e521a8cb4b6df471bb12a7ee5cbab20ea9167"}, + {file = "numpy-1.26.2-cp312-cp312-win_amd64.whl", hash = "sha256:b04f5dc6b3efdaab541f7857351aac359e6ae3c126e2edb376929bd3b7f92d7e"}, + {file = "numpy-1.26.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4eb8df4bf8d3d90d091e0146f6c28492b0be84da3e409ebef54349f71ed271ef"}, + {file = "numpy-1.26.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a13860fdcd95de7cf58bd6f8bc5a5ef81c0b0625eb2c9a783948847abbef2c2"}, + {file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64308ebc366a8ed63fd0bf426b6a9468060962f1a4339ab1074c228fa6ade8e3"}, + {file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baf8aab04a2c0e859da118f0b38617e5ee65d75b83795055fb66c0d5e9e9b818"}, + {file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d73a3abcac238250091b11caef9ad12413dab01669511779bc9b29261dd50210"}, + {file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b361d369fc7e5e1714cf827b731ca32bff8d411212fccd29ad98ad622449cc36"}, + {file = "numpy-1.26.2-cp39-cp39-win32.whl", hash = "sha256:bd3f0091e845164a20bd5a326860c840fe2af79fa12e0469a12768a3ec578d80"}, + {file = "numpy-1.26.2-cp39-cp39-win_amd64.whl", hash = "sha256:2beef57fb031dcc0dc8fa4fe297a742027b954949cabb52a2a376c144e5e6060"}, + {file = "numpy-1.26.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1cc3d5029a30fb5f06704ad6b23b35e11309491c999838c31f124fee32107c79"}, + {file = "numpy-1.26.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94cc3c222bb9fb5a12e334d0479b97bb2df446fbe622b470928f5284ffca3f8d"}, + {file = "numpy-1.26.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe6b44fb8fcdf7eda4ef4461b97b3f63c466b27ab151bec2366db8b197387841"}, + {file = "numpy-1.26.2.tar.gz", hash = "sha256:f65738447676ab5777f11e6bbbdb8ce11b785e105f690bc45966574816b6d3ea"}, +] +opencv-python = [ + {file = "opencv-python-4.5.5.64.tar.gz", hash = "sha256:f65de0446a330c3b773cd04ba10345d8ce1b15dcac3f49770204e37602d0b3f7"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-macosx_10_15_x86_64.whl", hash = "sha256:a512a0c59b6fec0fac3844b2f47d6ecb1a9d18d235e6c5491ce8dbbe0663eae8"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca6138b6903910e384067d001763d40f97656875487381aed32993b076f44375"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b293ced62f4360d9f11cf72ae7e9df95320ff7bf5b834d87546f844e838c0c35"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-win32.whl", hash = "sha256:6247e584813c00c3b9ed69a795da40d2c153dc923d0182e957e1c2f00a554ac2"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-win_amd64.whl", hash = "sha256:408d5332550287aa797fd06bef47b2dfed163c6787668cc82ef9123a9484b56a"}, + {file = "opencv_python-4.5.5.64-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:7787bb017ae93d5f9bb1b817ac8e13e45dd193743cb648498fcab21d00cf20a3"}, +] +packaging = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] +pandas = [ + {file = "pandas-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bdec823dc6ec53f7a6339a0e34c68b144a7a1fd28d80c260534c39c62c5bf8c9"}, + {file = "pandas-2.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:294d96cfaf28d688f30c918a765ea2ae2e0e71d3536754f4b6de0ea4a496d034"}, + {file = "pandas-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b728fb8deba8905b319f96447a27033969f3ea1fea09d07d296c9030ab2ed1d"}, + {file = "pandas-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00028e6737c594feac3c2df15636d73ace46b8314d236100b57ed7e4b9ebe8d9"}, + {file = "pandas-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:426dc0f1b187523c4db06f96fb5c8d1a845e259c99bda74f7de97bd8a3bb3139"}, + {file = "pandas-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:f237e6ca6421265643608813ce9793610ad09b40154a3344a088159590469e46"}, + {file = "pandas-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b7d852d16c270e4331f6f59b3e9aa23f935f5c4b0ed2d0bc77637a8890a5d092"}, + {file = "pandas-2.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7d5f2f54f78164b3d7a40f33bf79a74cdee72c31affec86bfcabe7e0789821"}, + {file = "pandas-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0aa6e92e639da0d6e2017d9ccff563222f4eb31e4b2c3cf32a2a392fc3103c0d"}, + {file = "pandas-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d797591b6846b9db79e65dc2d0d48e61f7db8d10b2a9480b4e3faaddc421a171"}, + {file = "pandas-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2d3e7b00f703aea3945995ee63375c61b2e6aa5aa7871c5d622870e5e137623"}, + {file = "pandas-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:dc9bf7ade01143cddc0074aa6995edd05323974e6e40d9dbde081021ded8510e"}, + {file = "pandas-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:482d5076e1791777e1571f2e2d789e940dedd927325cc3cb6d0800c6304082f6"}, + {file = "pandas-2.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8a706cfe7955c4ca59af8c7a0517370eafbd98593155b48f10f9811da440248b"}, + {file = "pandas-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0513a132a15977b4a5b89aabd304647919bc2169eac4c8536afb29c07c23540"}, + {file = "pandas-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9f17f2b6fc076b2a0078862547595d66244db0f41bf79fc5f64a5c4d635bead"}, + {file = "pandas-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:45d63d2a9b1b37fa6c84a68ba2422dc9ed018bdaa668c7f47566a01188ceeec1"}, + {file = "pandas-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:f69b0c9bb174a2342818d3e2778584e18c740d56857fc5cdb944ec8bbe4082cf"}, + {file = "pandas-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3f06bda01a143020bad20f7a85dd5f4a1600112145f126bc9e3e42077c24ef34"}, + {file = "pandas-2.1.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab5796839eb1fd62a39eec2916d3e979ec3130509930fea17fe6f81e18108f6a"}, + {file = "pandas-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edbaf9e8d3a63a9276d707b4d25930a262341bca9874fcb22eff5e3da5394732"}, + {file = "pandas-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ebfd771110b50055712b3b711b51bee5d50135429364d0498e1213a7adc2be8"}, + {file = "pandas-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8ea107e0be2aba1da619cc6ba3f999b2bfc9669a83554b1904ce3dd9507f0860"}, + {file = "pandas-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:d65148b14788b3758daf57bf42725caa536575da2b64df9964c563b015230984"}, + {file = "pandas-2.1.4.tar.gz", hash = "sha256:fcb68203c833cc735321512e13861358079a96c174a61f5116a1de89c58c0ef7"}, +] +pluggy = [ + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, +] +pytest = [ + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, +] +pytest-cov = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] +pytz = [ + {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, + {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +tzdata = [ + {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, + {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, +] diff --git a/pytest.ini b/pytest.ini index f4e142ff..6968cf24 100644 --- a/pytest.ini +++ b/pytest.ini @@ -6,4 +6,5 @@ markers = thermavip io needs_improvement - instantiation \ No newline at end of file + instantiation + accessors \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 251c01ff..69e7d850 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,31 +1,5 @@ add_subdirectory(cpp) - - - -install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/python/librir - DESTINATION ${CMAKE_INSTALL_PREFIX} - FILES_MATCHING PATTERN "*.py" -) - -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/python/setup.py ${CMAKE_SOURCE_DIR}/CMakeLists.txt ${CMAKE_SOURCE_DIR}/README.md - DESTINATION ${CMAKE_INSTALL_PREFIX} -) - -install(DIRECTORY $${CMAKE_CURRENT_SOURCE_DIR}/python/librir/LICENSES - DESTINATION ${CMAKE_INSTALL_PREFIX}/librir - FILES_MATCHING PATTERN "*.txt" -) - -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/python/librir/LICENSE - DESTINATION ${CMAKE_INSTALL_PREFIX}/librir -) - -install(DIRECTORY ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/ - DESTINATION ${CMAKE_INSTALL_PREFIX}/librir/libs -) - - -install(DIRECTORY ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/ - DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/labview/libs -) \ No newline at end of file +add_subdirectory(python) +add_subdirectory(labview) +set(LIBRIR_TARGETS ${LIBRIR_TARGETS} PARENT_SCOPE) diff --git a/src/cpp/CMakeLists.txt b/src/cpp/CMakeLists.txt index c1f9313b..7324557a 100644 --- a/src/cpp/CMakeLists.txt +++ b/src/cpp/CMakeLists.txt @@ -1,5 +1,5 @@ SET(TARGETS tools geometry signal_processing video_io) -# MESSAGE(STATUS "TARGETS : " ${TARGETS}) + add_subdirectory(tools) add_subdirectory(geometry) add_subdirectory(signal_processing) @@ -10,16 +10,7 @@ IF (NOT MSVC) add_dependencies(video_io dummy) ENDIF() -install (TARGETS ${TARGETS} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) -install (TARGETS ${TARGETS} LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR} ) -install( - TARGETS ${TARGETS} - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -) - -# message(STATUS ${EXCLUDED_TARGETS}) +set(LIBRIR_TARGETS ${TARGETS} CACHE INTERNAL "") diff --git a/src/cpp/geometry/CMakeLists.txt b/src/cpp/geometry/CMakeLists.txt index 71dcea73..59c6c126 100644 --- a/src/cpp/geometry/CMakeLists.txt +++ b/src/cpp/geometry/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DBUILD_GEOMETRY_LIB") - +# get previous target include folders get_target_property(TOOLS_HEADERS tools PUBLIC_HEADER) get_target_property(TOOLS_HEADERS_DIR tools INCLUDE_DIRECTORIES) @@ -10,9 +10,6 @@ SET(LIBRIR_GEOMETRY_SRC ${CMAKE_CURRENT_SOURCE_DIR}/DrawPolygon.cpp ) -#~ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -#~ include_directories(${TOOLS_HEADERS_DIR}) - SET(LIBRIR_GEOMETRY_SRC ${LIBRIR_GEOMETRY_SRC} PARENT_SCOPE) @@ -25,19 +22,15 @@ set(GEOMETRY_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/geometry.h ) -target_include_directories(geometry PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(geometry tools) +set(GEOMETRY_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}) -# install(TARGETS geometry - # ARCHIVE DESTINATION lib - # LIBRARY DESTINATION lib - # RUNTIME DESTINATION bin - # ) +target_include_directories( geometry + INTERFACE $ + $ +) +target_link_libraries(geometry tools) set_target_properties(geometry PROPERTIES PUBLIC_HEADER "${GEOMETRY_HEADERS}" - #~ LIBRARY_OUTPUT_DIRECTORY ${LIB_DESTINATION_DIR} - #~ RUNTIME_OUTPUT_DIRECTORY ${BIN_DESTINATION_DIR} ) -# set(GEOMETRY_HEADERS ${GEOMETRY_HEADERS} PARENT_SCOPE) diff --git a/src/cpp/signal_processing/CMakeLists.txt b/src/cpp/signal_processing/CMakeLists.txt index 1528cbae..02ff0d2e 100644 --- a/src/cpp/signal_processing/CMakeLists.txt +++ b/src/cpp/signal_processing/CMakeLists.txt @@ -1,13 +1,13 @@ add_definitions("-DBUILD_SIGNAL_PROCESSING_LIB") - +# get previous targets include folders get_target_property(TOOLS_HEADERS tools PUBLIC_HEADER) get_target_property(TOOLS_HEADERS_DIR tools INCLUDE_DIRECTORIES) get_target_property(GEOMETRY_HEADERS geometry PUBLIC_HEADER) get_target_property(GEOMETRY_HEADERS_DIR geometry INCLUDE_DIRECTORIES) -# MESSAGE(STATUS ${GEOMETRY_HEADERS}) + SET(LIBRIR_SIGNAL_PROCESSING_SRC ${CMAKE_CURRENT_SOURCE_DIR}/BadPixels.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Filters.cpp @@ -16,15 +16,6 @@ SET(LIBRIR_SIGNAL_PROCESSING_SRC ${JPEG9B_SRC} ) -#~ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -#~ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/charls) -#~ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/jpeg-9b) - -#~ include_directories(${GEOMETRY_HEADERS_DIR}) -#~ include_directories(${TOOLS_HEADERS_DIR}) -#~ include_directories(${TOOLS_HEADERS_DIR}) -# include_directories(JPEG9B_DIR CHARLS_DIR) - set(SIGNAL_PROCESSING_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/BadPixels.h ${CMAKE_CURRENT_SOURCE_DIR}/Filters.h @@ -35,14 +26,13 @@ SET(LIBRIR_SIGNAL_PROCESSING_SRC ${LIBRIR_SIGNAL_PROCESSING_SRC} PARENT_SCOPE) add_library(signal_processing ${LIBRIR_SIGNAL_PROCESSING_SRC}) target_link_libraries(signal_processing geometry tools) -target_include_directories(geometry PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} - ${JPEG9B_DIR} - ${CHARLS_DIR} - ) +target_include_directories( signal_processing + INTERFACE $ + $ +) +target_include_directories(signal_processing PRIVATE ${JPEG9B_DIR} ${CHARLS_DIR}) + set_target_properties(signal_processing PROPERTIES PUBLIC_HEADER "${SIGNAL_PROCESSING_HEADERS}" - #~ LIBRARY_OUTPUT_DIRECTORY ${DESTINATION_DIR} - #~ RUNTIME_OUTPUT_DIRECTORY ${DESTINATION_DIR} ) diff --git a/src/cpp/signal_processing/Filters.h b/src/cpp/signal_processing/Filters.h index 1457b236..abf4ff4f 100644 --- a/src/cpp/signal_processing/Filters.h +++ b/src/cpp/signal_processing/Filters.h @@ -229,7 +229,7 @@ namespace rir void translate(const T *src, U *dst, U background, size_t w, size_t h, float dx, float dy, TranslateBorder strategy) { #pragma omp parallel for - for (size_t y = 0; y < h; ++y) + for (int y = 0; y < (int)h; ++y) { for (size_t x = 0; x < w; ++x) { diff --git a/src/cpp/tools/CMakeLists.txt b/src/cpp/tools/CMakeLists.txt index 4860aa72..ab5a942c 100644 --- a/src/cpp/tools/CMakeLists.txt +++ b/src/cpp/tools/CMakeLists.txt @@ -1,4 +1,4 @@ - +# Find or fetch ZSTD include(FetchContent) set(TOOLS_DEP_LIBS) @@ -9,11 +9,10 @@ FetchContent_Declare( GIT_TAG v1.5.5 # URL "https://github.com/facebook/zstd/releases/download/v1.5.5/zstd-1.5.5.tar.gz" SOURCE_SUBDIR build/cmake - # OVERRIDE_FIND_PACKAGE + OVERRIDE_FIND_PACKAGE ) -FetchContent_Populate(zstd) -add_subdirectory(${zstd_SOURCE_DIR}/build/cmake ${zstd_BINARY_DIR} EXCLUDE_FROM_ALL) +find_package(zstd REQUIRED) add_library(zstd::libzstd_shared ALIAS libzstd_shared) add_library(zstd::libzstd_static ALIAS libzstd_static) @@ -30,6 +29,7 @@ SET(LIBRIR_TOOLS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/ReadFileChunk.cpp ${CMAKE_CURRENT_SOURCE_DIR}/SIMD.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tools.cpp + # ${ZSTD_SRC} ${BLOSC_SRC} ${MINIZIP_SRC} @@ -57,8 +57,13 @@ target_link_libraries(tools PRIVATE ${TOOLS_DEP_LIBS}) target_include_directories(tools PRIVATE ${ZSTD_INCLUDE_DIR}) +# target_include_directories(tools PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories( tools + INTERFACE $ + $ +) + -target_include_directories(tools PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(tools PRIVATE ${EXTRAS_INCLUDE_DIRECTORY}) target_include_directories(tools PRIVATE ${EXTRAS_INCLUDE_DIRECTORY}/blosc) target_include_directories(tools PRIVATE ${EXTRAS_INCLUDE_DIRECTORY}/minizip) diff --git a/src/cpp/tools/Misc.h b/src/cpp/tools/Misc.h index fad5c588..0678f8bd 100644 --- a/src/cpp/tools/Misc.h +++ b/src/cpp/tools/Misc.h @@ -16,451 +16,408 @@ truncate function already available on all unix systems */ extern "C" { - #include +#include } #endif #undef close - /** @file +/** @file - Defines utility functions used within librir - */ +Defines utility functions used within librir +*/ - namespace rir - { +namespace rir +{ - /**Vector of strings*/ - typedef std::vector StringList; - /**Vector of timestamps, usually in nanoseconds*/ - typedef std::vector TimestampVector; - /**String to wide string conversion*/ - std::wstring s2ws(const std::string &str); - /**Wide string to string conversion*/ - std::string ws2s(const std::wstring &wstr); - /** - Retrieve any kind of object from a string. - Returns T(0) on error. - If ok is not NULL, it will be set to true on success, false otherwise. - */ - template - T fromString(const std::string &str, bool *ok = NULL) + /**Vector of strings*/ + typedef std::vector StringList; + /**Vector of timestamps, usually in nanoseconds*/ + typedef std::vector TimestampVector; + /**String to wide string conversion*/ + std::wstring s2ws(const std::string &str); + /**Wide string to string conversion*/ + std::string ws2s(const std::wstring &wstr); + /** + Retrieve any kind of object from a string. + Returns T(0) on error. + If ok is not NULL, it will be set to true on success, false otherwise. + */ + template + T fromString(const std::string &str, bool *ok = NULL) + { + std::istringstream iss(str); + T res; + if (iss >> res) { - std::istringstream iss(str); - T res; - if (iss >> res) - { - if (ok) - *ok = true; - return res; - } if (ok) - *ok = false; - return T(0); + *ok = true; + return res; } + if (ok) + *ok = false; + return T(0); + } - /** - Convert any kind of object to its string representation. - */ - template - std::string toString(const T &value) + /** + Convert any kind of object to its string representation. + */ + template + std::string toString(const T &value) + { + std::ostringstream oss; + oss << value; + return oss.str(); + } + /**Returns number of non-overlapping occurrences of \a sub in \a str.*/ + TOOLS_EXPORT int count_substring(const std::string &str, const std::string &sub); + /**Replace all occurrences of \a str in \a in by \a replace*/ + TOOLS_EXPORT int replace(std::string &in, const char *str, const char *replace); + /**Split \a in based on \a match separator.*/ + TOOLS_EXPORT StringList split(const std::string &in, const char *match, bool keep_empty_strings = false); + /**Split \a in based on \a match separator.*/ + TOOLS_EXPORT StringList split(const char *in, const char *match, bool keep_empty_strings = false); + /**Join list of string with \a str separator*/ + TOOLS_EXPORT std::string join(const StringList &lst, const char *str); + /** Find substring using case insensitive comparison. Returns std::string::npos if not found. */ + TOOLS_EXPORT size_t find_case_insensitive(size_t start, const char *str, size_t size, const char *sub_str, size_t sub_size); + + /**Returns file size (0 if it does not exist)*/ + TOOLS_EXPORT size_t file_size(const char *filename); + /**Returns full file content in binary format.*/ + TOOLS_EXPORT std::string read_file(const char *filename, bool *ok = NULL); + /**Returns true if given file exists*/ + TOOLS_EXPORT bool file_exists(const char *filename); + /**Returns true if given directory exists*/ + TOOLS_EXPORT bool dir_exists(const char *dirname); + /**Remove given file or directory (recursively)*/ + TOOLS_EXPORT bool rm_file_or_dir(const char *filename); + /**Attempt to create directory, and create all parents if missing*/ + TOOLS_EXPORT bool make_path(const char *path); + /**Rename file, returns 0 on success */ + TOOLS_EXPORT int rename_file(const char *old, const char *_new); + + /** + Format given file path by: + 1 - replacing '\' by '/', + 2 - removing any trailing slash. + */ + TOOLS_EXPORT std::string format_file_path(const char *fname); + /** + Format given directory path by: + 1 - replacing '\' by '/', + 2 - adding a trailing slash if necessary. + */ + TOOLS_EXPORT std::string format_dir_path(const char *dname); + /**Returns the temporay directory for west data*/ + // TOOLS_EXPORT std::string get_temp_dir(); + + /**Sleep \a msecs milliseconds*/ + TOOLS_EXPORT void msleep(int msecs); + + /**Return the number of milliseconds elapsed since Epoch.*/ + TOOLS_EXPORT long long msecs_since_epoch(); + + /**Check endianness*/ + inline unsigned is_little_endian() + { + const union + { + unsigned i; + unsigned char c[4]; + } one = {1}; /* don't use static : performance detrimental */ + return one.c[0]; + } + /** Byte swap unsigned short*/ + inline uint16_t swap_uint16(uint16_t val) + { + return (val << 8) | (val >> 8); + } + /** Byte swap short*/ + inline int16_t swap_int16(int16_t val) + { + return (val << 8) | ((val >> 8) & 0xFF); + } + /** Byte swap unsigned int*/ + inline uint32_t swap_uint32(uint32_t val) + { + val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF); + return (val << 16) | (val >> 16); + } + /** Byte swap int*/ + inline int32_t swap_int32(int32_t val) + { + val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF); + return (val << 16) | ((val >> 16) & 0xFFFF); + } + /** Byte swap int 64 bits*/ + inline int64_t swap_int64(int64_t val) + { + val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL); + val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL); + return (val << 32) | ((val >> 32) & 0xFFFFFFFFULL); + } + /** Byte swap unsigned int 64 bits*/ + inline uint64_t swap_uint64(uint64_t val) + { + val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL); + val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL); + return (val << 32) | (val >> 32); + } + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + inline double swap_double(double X) + { + int64_t x = reinterpret_cast(X); //*(int64_t*)(&X); + x = (x & 0x00000000FFFFFFFF) << 32 | (x & 0xFFFFFFFF00000000) >> 32; + x = (x & 0x0000FFFF0000FFFF) << 16 | (x & 0xFFFF0000FFFF0000) >> 16; + x = (x & 0x00FF00FF00FF00FF) << 8 | (x & 0xFF00FF00FF00FF00) >> 8; + return reinterpret_cast(x); //*(double*)(&x); + } +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + /** Check machine endianness */ + inline unsigned isLittleEndian() + { + const union + { + uint32_t i; + uint8_t c[4]; + } one = {1}; /* don't use static : performance detrimental */ + return one.c[0]; + } + +#ifdef _MSC_VER + /**Truncate given file*/ + TOOLS_EXPORT int truncate(const char *fname, size_t off); +#else + /** + truncate function already available on all unix systems + */ + +#endif + + /** + Very basic 2D array class. + Values are stored in Row major order. + */ + template + struct Array2D + { + std::vector values; + size_t width; + size_t height; + + Array2D() noexcept + : width(0), height(0) { - std::ostringstream oss; - oss << value; - return oss.str(); } - /**Returns number of non-overlapping occurrences of \a sub in \a str.*/ - TOOLS_EXPORT int count_substring(const std::string &str, const std::string &sub); - /**Replace all occurrences of \a str in \a in by \a replace*/ - TOOLS_EXPORT int replace(std::string &in, const char *str, const char *replace); - /**Split \a in based on \a match separator.*/ - TOOLS_EXPORT StringList split(const std::string &in, const char *match, bool keep_empty_strings = false); - /**Split \a in based on \a match separator.*/ - TOOLS_EXPORT StringList split(const char *in, const char *match, bool keep_empty_strings = false); - /**Join list of string with \a str separator*/ - TOOLS_EXPORT std::string join(const StringList &lst, const char *str); - /** Find substring using case insensitive comparison. Returns std::string::npos if not found. */ - TOOLS_EXPORT size_t find_case_insensitive(size_t start, const char *str, size_t size, const char *sub_str, size_t sub_size); - - /**Returns file size (0 if it does not exist)*/ - TOOLS_EXPORT size_t file_size(const char *filename); - /**Returns full file content in binary format.*/ - TOOLS_EXPORT std::string read_file(const char *filename, bool *ok = NULL); - /**Returns true if given file exists*/ - TOOLS_EXPORT bool file_exists(const char *filename); - /**Returns true if given directory exists*/ - TOOLS_EXPORT bool dir_exists(const char *dirname); - /**Remove given file or directory (recursively)*/ - TOOLS_EXPORT bool rm_file_or_dir(const char *filename); - /**Attempt to create directory, and create all parents if missing*/ - TOOLS_EXPORT bool make_path(const char *path); - /**Rename file, returns 0 on success */ - TOOLS_EXPORT int rename_file(const char *old, const char *_new); - - /** - Format given file path by: - 1 - replacing '\' by '/', - 2 - removing any trailing slash. - */ - TOOLS_EXPORT std::string format_file_path(const char *fname); - /** - Format given directory path by: - 1 - replacing '\' by '/', - 2 - adding a trailing slash if necessary. - */ - TOOLS_EXPORT std::string format_dir_path(const char *dname); - /**Returns the temporay directory for west data*/ - // TOOLS_EXPORT std::string get_temp_dir(); - - /**Sleep \a msecs milliseconds*/ - TOOLS_EXPORT void msleep(int msecs); - - /**Return the number of milliseconds elapsed since Epoch.*/ - TOOLS_EXPORT long long msecs_since_epoch(); - - /**Check endianness*/ - inline unsigned is_little_endian() + Array2D(Array2D &&other) noexcept + : values(std::move(other.values)), width(0), height(0) { - const union - { - unsigned i; - unsigned char c[4]; - } one = {1}; /* don't use static : performance detrimental */ - return one.c[0]; + std::swap(width, other.width); + std::swap(height, other.height); } - /** Byte swap unsigned short*/ - inline uint16_t swap_uint16(uint16_t val) + Array2D(const Array2D &other) + : values(other.values()), width(other.width), height(other.height) { - return (val << 8) | (val >> 8); } - /** Byte swap short*/ - inline int16_t swap_int16(int16_t val) + ~Array2D() noexcept { - return (val << 8) | ((val >> 8) & 0xFF); } - /** Byte swap unsigned int*/ - inline uint32_t swap_uint32(uint32_t val) + Array2D &operator=(Array2D &&other) noexcept { - val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF); - return (val << 16) | (val >> 16); + values = std::move(other.values); + std::swap(width, other.width); + std::swap(height, other.height); + return *this; } - /** Byte swap int*/ - inline int32_t swap_int32(int32_t val) + Array2D &operator=(const Array2D &other) noexcept { - val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF); - return (val << 16) | ((val >> 16) & 0xFFFF); + values = other.values; + width = other.width; + height = other.height; + return *this; } - /** Byte swap int 64 bits*/ - inline int64_t swap_int64(int64_t val) + + size_t size() const noexcept { return values.size(); } + + T &operator()(size_t y, size_t x) noexcept { - val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL); - val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL); - return (val << 32) | ((val >> 32) & 0xFFFFFFFFULL); + return values[x + y * width]; } - /** Byte swap unsigned int 64 bits*/ - inline uint64_t swap_uint64(uint64_t val) + const T &operator()(size_t y, size_t x) const noexcept { - val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL); - val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL); - return (val << 32) | (val >> 32); + return values[x + y * width]; } -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wstrict-aliasing" -#endif - inline double swap_double(double X) + void resize(size_t new_size, const T *data = NULL) { - int64_t x = reinterpret_cast(X); //*(int64_t*)(&X); - x = (x & 0x00000000FFFFFFFF) << 32 | (x & 0xFFFFFFFF00000000) >> 32; - x = (x & 0x0000FFFF0000FFFF) << 16 | (x & 0xFFFF0000FFFF0000) >> 16; - x = (x & 0x00FF00FF00FF00FF) << 8 | (x & 0xFF00FF00FF00FF00) >> 8; - return reinterpret_cast(x); //*(double*)(&x); + values.resize(new_size); + if (data) + std::copy(data, data + new_size, values.begin()); } -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - /** Check machine endianness */ - inline unsigned isLittleEndian() + void resize(size_t new_size, const T val) { - const union - { - uint32_t i; - uint8_t c[4]; - } one = {1}; /* don't use static : performance detrimental */ - return one.c[0]; + values.resize(new_size, val); } + }; -#ifdef _MSC_VER - /**Truncate given file*/ - TOOLS_EXPORT int truncate(const char *fname, size_t off); -#else /** - truncate function already available on all unix systems + Straightforward view on a 2D array. + Values are considered to be stored in Row major order. */ + template + struct Array2DView + { + T *values; + size_t width; + size_t height; -#endif - - /** - Very basic 2D array class. - Values are stored in Row major order. - */ - template - struct Array2D + Array2DView() : values(NULL), width(0), height(0) {} + Array2DView(T *ptr, size_t w, size_t h) + : values(ptr), width(w), height(h) { - std::vector values; - size_t width; - size_t height; - - Array2D() noexcept - : width(0), height(0) - { - } - Array2D(Array2D &&other) noexcept - : values(std::move(other.values)), width(0), height(0) - { - std::swap(width, other.width); - std::swap(height, other.height); - } - Array2D(const Array2D &other) - : values(other.values()), width(other.width), height(other.height) - { - } - ~Array2D() noexcept - { - } - Array2D &operator=(Array2D &&other) noexcept - { - values = std::move(other.values); - std::swap(width, other.width); - std::swap(height, other.height); - return *this; - } - Array2D &operator=(const Array2D &other) noexcept - { - values = other.values; - width = other.width; - height = other.height; - return *this; - } - - size_t size() const noexcept { return values.size(); } - - T &operator()(size_t y, size_t x) noexcept - { - return values[x + y * width]; - } - const T &operator()(size_t y, size_t x) const noexcept - { - return values[x + y * width]; - } - - void resize(size_t new_size, const T *data = NULL) - { - values.resize(new_size); - if (data) - std::copy(data, data + new_size, values.begin()); - } + } + Array2DView(Array2D &ar) + : values(ar.values.data()), width(ar.width), height(ar.height) + { + } + size_t size() const noexcept { return width * height; } + const T &operator()(size_t y, size_t x) const noexcept + { + return values[x + y * width]; + } + T &operator()(size_t y, size_t x) noexcept + { + return values[x + y * width]; + } + }; + /** + Straightforward view on a 2D array. + Values are considered to be stored in Row major order. + */ + template + struct ConstArray2DView + { + const T *values; + size_t width; + size_t height; - void resize(size_t new_size, const T val) - { - values.resize(new_size, val); - } - }; + ConstArray2DView() : values(NULL), width(0), height(0) {} + ConstArray2DView(const T *ptr, size_t w, size_t h) + : values(ptr), width(w), height(h) + { + } + ConstArray2DView(const Array2D &ar) + : values(ar.values.data()), width(ar.width), height(ar.height) + { + } + ConstArray2DView(const Array2DView &ar) + : values(ar.values), width(ar.width), height(ar.height) + { + } - /** - Straightforward view on a 2D array. - Values are considered to be stored in Row major order. - */ - template - struct Array2DView + size_t size() const noexcept { return width * height; } + const T &operator()(size_t y, size_t x) const noexcept { - T *values; - size_t width; - size_t height; + return values[x + y * width]; + } + }; - Array2DView() : values(NULL), width(0), height(0) {} - Array2DView(T *ptr, size_t w, size_t h) - : values(ptr), width(w), height(h) - { - } - Array2DView(Array2D &ar) - : values(ar.values.data()), width(ar.width), height(ar.height) - { - } - size_t size() const noexcept { return width * height; } - const T &operator()(size_t y, size_t x) const noexcept - { - return values[x + y * width]; - } - T &operator()(size_t y, size_t x) noexcept - { - return values[x + y * width]; - } - }; - /** - Straightforward view on a 2D array. - Values are considered to be stored in Row major order. - */ + /** + Simple class to read file/string containing ascii float/double values. + This class is much faster than standard stl streams, which are painfully slow when reading huge ascii matrices. + */ + class TOOLS_EXPORT FileFloatStream + { + FILE *file; + const char *string; + size_t len; + size_t pos; + char buff[100]; + + public: + /**Construct from file*/ + FileFloatStream(const char *filename, const char *format = "r"); + /**Construct from string*/ + FileFloatStream(const char *string, size_t len); + ~FileFloatStream(); + + /**Open given file*/ + bool open(const char *filename, const char *format = "r"); + /**Open given string*/ + bool open(const char *string, size_t len); + /**Close stream. Automatically called in the destructor, and before any call to #Open().*/ + void close(); + + /**Returns true if the stream is open on a file or string*/ + bool isOpen() const; + /**Returns true if we reach the end of file/string*/ + bool atEnd() const { return pos >= len; } + /**Returns the file/string total size*/ + size_t size() const; + /**Returns the current read position*/ + size_t tell() const; + /**Seek to given position from the beginning of file/string*/ + bool seek(size_t p); + + /**Read all remaining data from current position*/ + std::string readAll(); + /**Read next line*/ + std::string readLine(); + + /**Reads the next float or double value and returns it. + If not NULL, ok is set to true on success, false otherwise.*/ template - struct ConstArray2DView + T readFloat(bool *ok = NULL) { - const T *values; - size_t width; - size_t height; - - ConstArray2DView() : values(NULL), width(0), height(0) {} - ConstArray2DView(const T *ptr, size_t w, size_t h) - : values(ptr), width(w), height(h) + if (!isOpen() || pos >= len) { + if (ok) + *ok = false; + return (T)0; } - ConstArray2DView(const Array2D &ar) - : values(ar.values.data()), width(ar.width), height(ar.height) - { - } - ConstArray2DView(const Array2DView &ar) - : values(ar.values), width(ar.width), height(ar.height) - { - } - - size_t size() const noexcept { return width * height; } - const T &operator()(size_t y, size_t x) const noexcept - { - return values[x + y * width]; - } - }; - /** - Simple class to read file/string containing ascii float/double values. - This class is much faster than standard stl streams, which are painfully slow when reading huge ascii matrices. - */ - class TOOLS_EXPORT FileFloatStream - { - FILE *file; - const char *string; - size_t len; - size_t pos; - char buff[100]; - - public: - /**Construct from file*/ - FileFloatStream(const char *filename, const char *format = "r"); - /**Construct from string*/ - FileFloatStream(const char *string, size_t len); - ~FileFloatStream(); - - /**Open given file*/ - bool open(const char *filename, const char *format = "r"); - /**Open given string*/ - bool open(const char *string, size_t len); - /**Close stream. Automatically called in the destructor, and before any call to #Open().*/ - void close(); - - /**Returns true if the stream is open on a file or string*/ - bool isOpen() const; - /**Returns true if we reach the end of file/string*/ - bool atEnd() const { return pos >= len; } - /**Returns the file/string total size*/ - size_t size() const; - /**Returns the current read position*/ - size_t tell() const; - /**Seek to given position from the beginning of file/string*/ - bool seek(size_t p); - - /**Read all remaining data from current position*/ - std::string readAll(); - /**Read next line*/ - std::string readLine(); - - /**Reads the next float or double value and returns it. - If not NULL, ok is set to true on success, false otherwise.*/ - template - T readFloat(bool *ok = NULL) - { - if (!isOpen() || pos >= len) - { - if (ok) - *ok = false; - return (T)0; - } + size_t saved_pos = pos; - size_t saved_pos = pos; - - // seek to actual position - if (file) - fseek(file, (long)pos, SEEK_SET); + // seek to actual position + if (file) + fseek(file, (long)pos, SEEK_SET); - // skip empty characters - char first; - while (true) + // skip empty characters + char first; + while (true) + { + first = file ? getc(file) : string[pos]; + ++pos; + if ((!(first == ' ' || first == '\t' || first == '\n' || first == '\r')) || pos == len) { - first = file ? getc(file) : string[pos]; - ++pos; - if ((!(first == ' ' || first == '\t' || first == '\n' || first == '\r')) || pos == len) - { - break; - } - } - buff[0] = first; - - { - // read next 32 bytes - size_t max_read = len - pos; - if (max_read > 32) - max_read = 32; - if (file) - fread(buff + 1, max_read, 1, file); - else - memcpy(buff + 1, string + pos, max_read); - char *start = buff; - char *end = buff + 1 + max_read; - *end = 0; - T res = (T)strtod(start, &end); - if (start == end) - { - if (ok) - *ok = false; - pos = saved_pos; - return (T)0; - } - pos += (end - start) - 1; - if (ok) - *ok = true; - return res; + break; } + } + buff[0] = first; - // read file until word break + { + // read next 32 bytes size_t max_read = len - pos; - if (max_read > 98) - max_read = 98; - size_t i = 1; - for (; i <= max_read; ++i) - { - char c = file ? getc(file) : string[pos + i - 1]; - if (c == ' ' || c == '\t' || c == '\n' || c == '\r') - { - break; - } - buff[i] = c; - } - buff[i] = 0; - - char *start = buff; - char *end = buff + i; - T res; - if (std::is_same::value) - res = (T)strtod(start, &end); - else if (std::is_same::value) - res = (T)strtof(start, &end); + if (max_read > 32) + max_read = 32; + if (file) + fread(buff + 1, max_read, 1, file); else - { - if (ok) - *ok = false; - pos = saved_pos; - return (T)0; - } - if (end == start) + memcpy(buff + 1, string + pos, max_read); + char *start = buff; + char *end = buff + 1 + max_read; + *end = 0; + T res = (T)strtod(start, &end); + if (start == end) { if (ok) *ok = false; @@ -472,174 +429,217 @@ extern "C" *ok = true; return res; } - }; - /** - Read ascii matrix from a FileFloatStream object - */ - template - Array2D readFileFast(FileFloatStream &fin, std::string *error = NULL) - { - static_assert(std::is_same::value || std::is_same::value, "wrong data type (should be float or double)"); - if (!fin.isOpen()) + // read file until word break + size_t max_read = len - pos; + if (max_read > 98) + max_read = 98; + size_t i = 1; + for (; i <= max_read; ++i) { - if (error) - *error = "Cannot read stream: not open"; - return Array2D(); + char c = file ? getc(file) : string[pos + i - 1]; + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') + { + break; + } + buff[i] = c; } - bool ok; + buff[i] = 0; - std::int64_t start = fin.tell(); - - // read first value - bool is_ascii = true; - /*double val =*/fin.readFloat(&ok); - - if (!ok) + char *start = buff; + char *end = buff + i; + T res; + if (std::is_same::value) + res = (T)strtod(start, &end); + else if (std::is_same::value) + res = (T)strtof(start, &end); + else + { + if (ok) + *ok = false; + pos = saved_pos; + return (T)0; + } + if (end == start) { - is_ascii = false; + if (ok) + *ok = false; + pos = saved_pos; + return (T)0; } + pos += (end - start) - 1; + if (ok) + *ok = true; + return res; + } + }; - if (!is_ascii) + /** + Read ascii matrix from a FileFloatStream object + */ + template + Array2D readFileFast(FileFloatStream &fin, std::string *error = NULL) + { + static_assert(std::is_same::value || std::is_same::value, "wrong data type (should be float or double)"); + if (!fin.isOpen()) + { + if (error) + *error = "Cannot read stream: not open"; + return Array2D(); + } + bool ok; + + std::int64_t start = fin.tell(); + + // read first value + bool is_ascii = true; + /*double val =*/fin.readFloat(&ok); + + if (!ok) + { + is_ascii = false; + } + + if (!is_ascii) + { + start = fin.tell() + 256; + // the file probably has a WEST header + // fin.open(filename,"rb"); + fin.seek(start); + /*double val =*/fin.readFloat(&ok); + if (!ok) { - start = fin.tell() + 256; - // the file probably has a WEST header - // fin.open(filename,"rb"); + // sometimes, the header seems to have 256 + 5 bytes + start += 5; + // fin.open(filename, "rb"); fin.seek(start); - /*double val =*/fin.readFloat(&ok); + /*val =*/fin.readFloat(&ok); if (!ok) { - // sometimes, the header seems to have 256 + 5 bytes - start += 5; - // fin.open(filename, "rb"); - fin.seek(start); - /*val =*/fin.readFloat(&ok); - if (!ok) - { - if (error) - *error = "Cannot interpret file format"; - return Array2D(); - } + if (error) + *error = "Cannot interpret file format"; + return Array2D(); } } + } - fin.seek(start); + fin.seek(start); - // read full file - std::string file = fin.readAll(); + // read full file + std::string file = fin.readAll(); - // count line ending - std::string eol = "\n"; - if (file.find("\r\n") != std::string::npos) - eol = "\r\n"; - int eol_count = count_substring(file, eol); + // count line ending + std::string eol = "\n"; + if (file.find("\r\n") != std::string::npos) + eol = "\r\n"; + int eol_count = count_substring(file, eol); - Array2D res; - res.width = 0; - res.height = 0; + Array2D res; + res.width = 0; + res.height = 0; - // check if we need to skip comment line - int skip_line = 0; + // check if we need to skip comment line + int skip_line = 0; + { + FileFloatStream tmp(file.c_str(), file.size()); + while (true) { - FileFloatStream tmp(file.c_str(), file.size()); - while (true) - { - std::string line = tmp.readLine(); - if (line[0] == ('C') || line[0] == ('#') || line[0] == ('%') || line.substr(0, 2) == ("//")) - ++skip_line; - else - break; - } + std::string line = tmp.readLine(); + if (line[0] == ('C') || line[0] == ('#') || line[0] == ('%') || line.substr(0, 2) == ("//")) + ++skip_line; + else + break; } + } + { + FileFloatStream s(file.c_str(), file.size()); + // skip comments + for (int i = 0; i < skip_line; ++i) { - FileFloatStream s(file.c_str(), file.size()); - // skip comments - for (int i = 0; i < skip_line; ++i) - { - std::string line = s.readLine(); - } - // skip empty lines - size_t pos = s.tell(); - while (true) + std::string line = s.readLine(); + } + // skip empty lines + size_t pos = s.tell(); + while (true) + { + std::string line = s.readLine(); + if (line.empty()) { - std::string line = s.readLine(); - if (line.empty()) - { - pos = s.tell(); - if (s.atEnd()) - break; - } - else + pos = s.tell(); + if (s.atEnd()) break; } - s.seek(pos); + else + break; + } + s.seek(pos); - std::vector tmp; - // read values - while (true) - { - // read a full line - std::string line = s.readLine(); + std::vector tmp; + // read values + while (true) + { + // read a full line + std::string line = s.readLine(); - // empy line: finished - if (line.empty()) - break; + // empy line: finished + if (line.empty()) + break; - // read all values in the line - int width = 0; - T value; + // read all values in the line + int width = 0; + T value; - FileFloatStream line_s(line.c_str(), line.size()); - while (true) + FileFloatStream line_s(line.c_str(), line.size()); + while (true) + { + value = line_s.readFloat(&ok); + if (ok) { - value = line_s.readFloat(&ok); - if (ok) - { - tmp.push_back(value); - ++width; - } - else - break; + tmp.push_back(value); + ++width; } - - // empy line: finished - if (width == 0) + else break; + } - // line with a different width - if (res.width && (int)res.width != width) - { - if (error) - *error = "The file does not contain a matrix "; - return Array2D(); - } + // empy line: finished + if (width == 0) + break; - res.width = width; - res.height++; + // line with a different width + if (res.width && (int)res.width != width) + { + if (error) + *error = "The file does not contain a matrix "; + return Array2D(); } - res.values = tmp; + res.width = width; + res.height++; } - return res; + res.values = tmp; } - /** - Read ascii matrix from file - */ - template - Array2D readFileFast(const char *filename, std::string *error = NULL) + return res; + } + + /** + Read ascii matrix from file + */ + template + Array2D readFileFast(const char *filename, std::string *error = NULL) + { + FileFloatStream fin(filename, "rb"); + // open file + if (!fin.isOpen()) { - FileFloatStream fin(filename, "rb"); - // open file - if (!fin.isOpen()) - { - if (error) - *error = "Cannot read file " + std::string(filename); - return Array2D(); - } - return readFileFast(fin, error); + if (error) + *error = "Cannot read file " + std::string(filename); + return Array2D(); } - + return readFileFast(fin, error); } + +} diff --git a/src/cpp/video_io/BaseCalibration.h b/src/cpp/video_io/BaseCalibration.h index d4e5e30b..5e882c88 100644 --- a/src/cpp/video_io/BaseCalibration.h +++ b/src/cpp/video_io/BaseCalibration.h @@ -23,8 +23,6 @@ namespace rir { SupportGlobalEmissivity = 0x01, SupportPixelEmissivity = 0x02, - SupportOpticalTemperature = 0x04, - SupportSTEFITemperature = 0x08 // Only for WEST cameras }; virtual ~BaseCalibration() {} @@ -52,24 +50,14 @@ namespace rir /**Returns the list of calibration files required for this calibration*/ virtual StringList calibrationFiles() const = 0; - /**Convert Digital Level value and Integration time to temperature (�C) for given integration time*/ + /**Convert Digital Level value and Integration time to temperature (°C) for given integration time*/ virtual unsigned rawDLToTemp(unsigned DL, int ti) const = 0; - /**Convert Digital Level value and Integration time to temperature (�C) for given integration time*/ + /**Convert Digital Level value and Integration time to temperature (°C) for given integration time*/ virtual float rawDLToTempF(unsigned DL, int ti) const = 0; - /**Convert temperature (�C) to Digital Level for given Integration time*/ + /**Convert temperature (°C) to Digital Level for given Integration time*/ virtual unsigned tempToRawDL(unsigned temp, int ti) const = 0; virtual unsigned tempToRawDLF(float temp, int ti) const = 0; - /**Returns optics temperature*/ - virtual unsigned short opticalTemperature() const = 0; - /**Set optics temperature (B30 temperature)*/ - virtual void setOpticalTemperature(unsigned short) = 0; - - /**Returns STEFI temperature*/ - virtual unsigned short STEFITemperature() const = 0; - /**Set STEFI temperature */ - virtual void setSTEFITemperature(unsigned short) = 0; - /** * Returns the names of internal used tables (floating point matrices) used for calibration */ @@ -77,7 +65,7 @@ namespace rir virtual std::pair getTable(const char *name) const { return {nullptr, 0}; } /** - Apply inverted calibration (from T�C to DL). + Apply inverted calibration (from T°C to DL). IT is the image of integration time (can be NULL for some implementations) */ virtual bool applyInvert(const unsigned short *T, const unsigned char *IT, unsigned int size, unsigned short *out) const = 0; @@ -102,7 +90,7 @@ namespace rir @param saturate if not NULL, set to true if the calibration saturated, false otherwise Returns true on success, false otherwise. - This function performs a floating point calibration (precision < 1�C) and is slightly slower than #apply() function. + This function performs a floating point calibration (precision < 1°C) and is slightly slower than #apply() function. */ virtual bool applyF(const unsigned short *DL, const std::vector &inv_emissivities, unsigned int size, float *out, bool *saturate = NULL) const = 0; }; diff --git a/src/cpp/video_io/CMakeLists.txt b/src/cpp/video_io/CMakeLists.txt index d663f35c..2e9b2254 100644 --- a/src/cpp/video_io/CMakeLists.txt +++ b/src/cpp/video_io/CMakeLists.txt @@ -1,7 +1,7 @@ add_definitions("-DBUILD_IO_LIB") - +# get previous targets include folders get_target_property(TOOLS_HEADERS tools PUBLIC_HEADER) get_target_property(TOOLS_HEADERS_DIR tools INCLUDE_DIRECTORIES) @@ -11,8 +11,8 @@ get_target_property(GEOMETRY_HEADERS_DIR geometry INCLUDE_DIRECTORIES) get_target_property(SIGNAL_PROCESSING_HEADERS signal_processing PUBLIC_HEADER) get_target_property(SIGNAL_PROCESSING_HEADERS_DIR signal_processing INCLUDE_DIRECTORIES) -# find_package(FFmpeg COMPONENTS AVDEVICE REQUIRED) +# Set sources SET(LIBRIR_IO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/BaseCalibration.cpp @@ -21,97 +21,138 @@ SET(LIBRIR_IO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/IRVideoLoader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/HCCLoader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/video_io.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ZFile.cpp + # ${CMAKE_CURRENT_SOURCE_DIR}/ZFile.cpp ) +SET(LIBRIR_IO_SRC ${LIBRIR_IO_SRC} PARENT_SCOPE) + +set(IO_HEADERS +${CMAKE_CURRENT_SOURCE_DIR}/IRVideoLoader.h +${CMAKE_CURRENT_SOURCE_DIR}/IRFileLoader.h +${CMAKE_CURRENT_SOURCE_DIR}/BaseCalibration.h +${CMAKE_CURRENT_SOURCE_DIR}/HCCLoader.h +${CMAKE_CURRENT_SOURCE_DIR}/video_io.h +${CMAKE_CURRENT_SOURCE_DIR}/h264.h +# ${CMAKE_CURRENT_SOURCE_DIR}/ZFile.h +) + +# set library video_io + +add_library(video_io ${LIBRIR_IO_SRC}) + +# retrieve ffmpeg dependency manually since there is no easy way +# to fetch and compile sources for windows and linux +# FIXME: FetchContent/find_package ffmpeg IF (MSVC) - SET(FFMPEG_LIB_DIR ${CMAKE_SOURCE_DIR}/3rd_64/ffmpeg-4.3-msvc/lib) - SET(FFMPEG_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/3rd_64/ffmpeg-4.3-msvc) + SET(FFMPEG_LIB_DIR ${PROJECT_SOURCE_DIR}/3rd_64/ffmpeg-4.3-msvc/lib) + SET(FFMPEG_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/3rd_64/ffmpeg-4.3-msvc) SET(FFMPEG_LIBS avdevice avformat avcodec swscale avutil swresample avfilter ) - SET(ADDITIONAL_FFMPEG_LIBS libx264 libx265 kvazaar postproc vcruntime140_1 Vfw32 shlwapi strmbase) + SET(ADDITIONAL_FFMPEG_LIBS libx264 libx265 kvazaar postproc vcruntime140_1 ) + SET(FFMPEG_SYSTEM_DEPENDENCIES Vfw32 shlwapi strmbase) SET(FFMPEG_PREFIX ) SET(FFMPEG_SUFFIX .dll) + SET(FFMPEG_IMPLIB .lib) ELSE() - IF (WIN32) # mingw - SET(FFMPEG_LIB_DIR ${CMAKE_SOURCE_DIR}/3rd_64/ffmpeg/install/lib) - SET(FFMPEG_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/3rd_64/ffmpeg/install/include) + SET(FFMPEG_LIB_DIR ${PROJECT_SOURCE_DIR}/3rd_64/ffmpeg/install/lib) + SET(FFMPEG_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/3rd_64/ffmpeg/install/include) SET(FFMPEG_LIBS avdevice avformat avcodec swscale avutil swresample avfilter) SET(ADDITIONAL_FFMPEG_LIBS postproc) + SET(FFMPEG_SYSTEM_DEPENDENCIES ) + SET(FFMPEG_PREFIX ) SET(FFMPEG_SUFFIX .dll) + SET(FFMPEG_IMPLIB .lib) + ELSE() - SET(FFMPEG_LIB_DIR ${CMAKE_SOURCE_DIR}/3rd_64/ffmpeg/install/lib) - SET(FFMPEG_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/3rd_64/ffmpeg/install/include) + SET(FFMPEG_LIB_DIR ${PROJECT_SOURCE_DIR}/3rd_64/ffmpeg/install/lib) + SET(FFMPEG_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/3rd_64/ffmpeg/install/include) SET(FFMPEG_LIBS avdevice avformat avcodec swscale avutil swresample avfilter) SET(ADDITIONAL_FFMPEG_LIBS postproc) + SET(FFMPEG_SYSTEM_DEPENDENCIES ) SET(FFMPEG_PREFIX lib) SET(FFMPEG_SUFFIX .so) + SET(FFMPEG_IMPLIB .a) + ENDIF() ENDIF() -link_directories(${FFMPEG_LIB_DIR}) -MESSAGE(STATUS "Using ${FFMPEG_LIB_DIR} for ffmpeg library files") - - -SET(LIBRIR_IO_SRC ${LIBRIR_IO_SRC} PARENT_SCOPE) -set(IO_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/IRVideoLoader.h - ${CMAKE_CURRENT_SOURCE_DIR}/IRFileLoader.h - ${CMAKE_CURRENT_SOURCE_DIR}/BaseCalibration.h - ${CMAKE_CURRENT_SOURCE_DIR}/HCCLoader.h - ${CMAKE_CURRENT_SOURCE_DIR}/video_io.h - ${CMAKE_CURRENT_SOURCE_DIR}/h264.h - ${CMAKE_CURRENT_SOURCE_DIR}/ZFile.h +MESSAGE(STATUS "Using ${FFMPEG_LIB_DIR} for ffmpeg library files") +set(COMPLETE_FFMPEG_LIBS ${FFMPEG_LIBS} ${ADDITIONAL_FFMPEG_LIBS}) +set(FFMPEG_DLLS_DEPEDENCIES "" CACHE INTERNAL "") + +if (WIN32 OR MSVC) + foreach(ffmpeg_lib ${FFMPEG_LIBS}) + file(GLOB CORRECT_FFMPEG_SHARED_LIB_PATH_LIST "${FFMPEG_LIB_DIR}/*${ffmpeg_lib}*${FFMPEG_SUFFIX}*") + list(GET CORRECT_FFMPEG_SHARED_LIB_PATH_LIST 0 CORRECT_FFMPEG_SHARED_LIB_PATH) + file(GLOB CORRECT_FFMPEG_IMPLIB_PATH_LIST "${FFMPEG_LIB_DIR}/*${ffmpeg_lib}*${FFMPEG_IMPLIB}*") + list(GET CORRECT_FFMPEG_IMPLIB_PATH_LIST 0 CORRECT_FFMPEG_IMPLIB_PATH) + add_library(${ffmpeg_lib} SHARED IMPORTED) + set_target_properties(${ffmpeg_lib} PROPERTIES + IMPORTED_LOCATION ${CORRECT_FFMPEG_SHARED_LIB_PATH} + + ) + + get_filename_component(__dll_name ${CORRECT_FFMPEG_SHARED_LIB_PATH} NAME) + set_target_properties(${ffmpeg_lib} PROPERTIES + RUNTIME_OUTPUT_NAME ${__dll_name} + ) + + set_target_properties(${ffmpeg_lib} PROPERTIES + IMPORTED_IMPLIB ${CORRECT_FFMPEG_IMPLIB_PATH} + ) + set_target_properties(${ffmpeg_lib} PROPERTIES + INCLUDE_DIRECTORIES ${FFMPEG_INCLUDE_DIR}/lib${ffmpeg_lib} + ) + list(APPEND FFMPEG_DLLS_DEPEDENCIES ${CORRECT_FFMPEG_SHARED_LIB_PATH}) + + endforeach() + foreach(_additional_ffmpeg_lib ${ADDITIONAL_FFMPEG_LIBS}) + file(GLOB CORRECT_FFMPEG_SHARED_LIB_PATH "${FFMPEG_LIB_DIR}/*${_additional_ffmpeg_lib}*${FFMPEG_SUFFIX}*") + list(APPEND FFMPEG_DLLS_DEPEDENCIES ${CORRECT_FFMPEG_SHARED_LIB_PATH}) + endforeach() +endif() + +set(FFMPEG_DLLS_DEPEDENCIES ${FFMPEG_DLLS_DEPEDENCIES} CACHE INTERNAL "") +set(FFMPEG_LIB_DIR ${FFMPEG_LIB_DIR} CACHE INTERNAL "") +set(FFMPEG_INCLUDE_DIR ${FFMPEG_INCLUDE_DIR} CACHE INTERNAL "") +set(FFMPEG_LIBS ${FFMPEG_LIBS} CACHE INTERNAL "") +set(ADDITIONAL_FFMPEG_LIBS ${ADDITIONAL_FFMPEG_LIBS} CACHE INTERNAL "") +set(FFMPEG_PREFIX ${FFMPEG_PREFIX} CACHE INTERNAL "") +set(FFMPEG_SUFFIX ${FFMPEG_SUFFIX} CACHE INTERNAL "") +set(COMPLETE_FFMPEG_LIBS ${COMPLETE_FFMPEG_LIBS} CACHE INTERNAL "") + +# include and linking +target_link_libraries(video_io PUBLIC tools geometry signal_processing) + +target_include_directories( video_io + INTERFACE $ + $ + PUBLIC $ + $ ) -add_library(video_io ${LIBRIR_IO_SRC}) -add_dependencies(video_io tools geometry signal_processing) -target_link_libraries(video_io PRIVATE tools geometry signal_processing ${FFMPEG_LIBS}) -target_include_directories(video_io PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) -target_include_directories(video_io PRIVATE ${FFMPEG_INCLUDE_DIR}) +target_link_libraries( video_io + PRIVATE ${FFMPEG_LIBS} + +) +target_link_directories(video_io + PRIVATE $ + $ +) +target_include_directories(video_io PRIVATE ${FFMPEG_INCLUDE_DIR}) set_target_properties(video_io PROPERTIES PUBLIC_HEADER "${IO_HEADERS}" - #~ LIBRARY_OUTPUT_DIRECTORY ${DESTINATION_DIR} - #~ RUNTIME_OUTPUT_DIRECTORY ${DESTINATION_DIR} ) get_target_property(IO_RUNTIME_OUTPUT_DIRECTORY video_io RUNTIME_OUTPUT_DIRECTORY) -MESSAGE(STATUS "${IO_RUNTIME_OUTPUT_DIRECTORY}") -SET(COMPLETE_FFMPEG_LIBS ${FFMPEG_LIBS} ${ADDITIONAL_FFMPEG_LIBS}) - -file(MAKE_DIRECTORY ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) - -# Add any variables you need during post install. -install(CODE "set(FFMPEG_LIB_DIR \"${FFMPEG_LIB_DIR}\")") -install(CODE "set(FFMPEG_INCLUDE_DIR \"${FFMPEG_INCLUDE_DIR}\")") -install(CODE "set(FFMPEG_LIBS \"${FFMPEG_LIBS}\")") -install(CODE "set(ADDITIONAL_FFMPEG_LIBS \"${ADDITIONAL_FFMPEG_LIBS}\")") -install(CODE "set(FFMPEG_PREFIX \"${FFMPEG_PREFIX}\")") -install(CODE "set(FFMPEG_SUFFIX \"${FFMPEG_SUFFIX}\")") -install(CODE "set(COMPLETE_FFMPEG_LIBS \"${COMPLETE_FFMPEG_LIBS}\")") -install(CODE "set(CMAKE_INSTALL_BINDIR \"${CMAKE_INSTALL_BINDIR}\")") -install(CODE "set(CMAKE_SOURCE_DIR \"${CMAKE_SOURCE_DIR}\")") +MESSAGE(STATUS "IO_RUNTIME_OUTPUT_DIRECTORY : ${IO_RUNTIME_OUTPUT_DIRECTORY}") + +# installing FFMPEG dependencies +install(CODE "set(FFMPEG_INSTALL_DIR \"${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}\")") install(SCRIPT InstallFfmpeg.cmake) -# foreach(ffmpeg_lib IN ITEMS ${COMPLETE_FFMPEG_LIBS} ) - # file(GLOB CORRECT_FFMPEG_LIB_PATH "${FFMPEG_LIB_DIR}/*${ffmpeg_lib}*${FFMPEG_SUFFIX}*") - # foreach(ffmpeg_lib_path ${CORRECT_FFMPEG_LIB_PATH}) - - # IF(ffmpeg_lib_path) - # install(FILES ${ffmpeg_lib_path} DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}) - # endif() - # endforeach() -# endforeach() - -# IF (WIN32) - # IF (NOT MSVC) - # # mingw/msys2 - # file(GLOB ffmpeg_dlls "${CMAKE_SOURCE_DIR}/3rd_64/ffmpeg/install/bin/*.dll") - # install(FILES ${ffmpeg_dlls} DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}) - # ENDIF() -# ENDIF() diff --git a/src/cpp/video_io/HCCLoader.h b/src/cpp/video_io/HCCLoader.h index abf56725..96a3c607 100644 --- a/src/cpp/video_io/HCCLoader.h +++ b/src/cpp/video_io/HCCLoader.h @@ -132,6 +132,7 @@ namespace rir virtual bool calibrate(unsigned short *img, float *out, int size, int calibration); virtual bool calibrateInplace(unsigned short *img, int size, int calibration); virtual BaseCalibration *calibration() const { return NULL; } + virtual bool setCalibration(BaseCalibration *calibration) { return false; } virtual const std::map &globalAttributes() const; virtual bool extractAttributes(std::map &) const; virtual void close(); diff --git a/src/cpp/video_io/IRFileLoader.cpp b/src/cpp/video_io/IRFileLoader.cpp index 4552f919..1292a488 100644 --- a/src/cpp/video_io/IRFileLoader.cpp +++ b/src/cpp/video_io/IRFileLoader.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include "IRFileLoader.h" @@ -18,7 +17,7 @@ #include #include "HCCLoader.h" -#include "ZFile.h" +// #include "ZFile.h" #include "Log.h" #include "Filters.h" #include "ReadFileChunk.h" @@ -311,51 +310,6 @@ namespace rir f = NULL; } } - else if (f->type == BIN_FILE_Z_COMPRESSED) - { - // f->file.close(); - f->zfile = z_open_file_read(f->file); - if (!f->zfile) - { - destroyFileReader(f->file); - delete f; - f = NULL; - goto end; - } - - f->width = infos.X; - f->height = infos.Y; - f->count = z_image_count(f->zfile); - - if (f->count == 0) - { - destroyFileReader(f->file); - delete f; - f = NULL; - goto end; - } - - f->times.resize(f->count); // = (int64_t*)malloc(f->count * sizeof(int64_t)); - memcpy(f->times.data(), z_get_timestamps(f->zfile), f->count * sizeof(int64_t)); - int64_t t0 = f->times[0]; - - if (t0 > 28000 && t0 < 32000) - { - // the time is in us since origin, do NOT subtract first timestamp - // remove 10ms to have a precision of +- 10ms - for (unsigned int i = 0; i < f->count; ++i) - f->times[i] = (f->times[i] * (int64_t)1000000) - 10000000ULL; // convert to ns - } - else - { - if (!(f->times.front() < -1000000000 || f->times.back() > 1000000000)) - { // time already in ns - for (unsigned int i = 0; i < f->count; ++i) - f->times[i] = (f->times[i] - t0) * (int64_t)1000000; // convert to ns - } - } - f->has_times = 1; - } else if (f->type == BIN_FILE_H264) { if (!f->h264.open(f->file)) @@ -462,8 +416,7 @@ namespace rir static void bin_close_file(BinFile *f) { - if (f->zfile) - z_close_file(f->zfile); + f->h264.close(); f->hcc.close(); if (f->other) @@ -521,8 +474,6 @@ namespace rir f->hcc.readImage(pos, 0, img); return 0; } - else if (f->zfile) - return z_read_image(f->zfile, pos, img, timestamp); else { if (timestamp) @@ -558,22 +509,17 @@ namespace rir int min_T_height; bool store_it; bool motionCorrectionEnabled; + std::vector translation_points; std::vector img; bool has_times; bool saturate; bool bp_enabled; int median_value; - int initOpticalTemperature; - int initSTEFITemperature; Polygon bad_pixels; BaseCalibration *calib; - std::map attributes; - - std::vector upper; // upper divertor or full view - std::vector lower; // lower divertor PrivateData() - : file(NULL), type(0), min_T(0), min_T_height(0), store_it(false), motionCorrectionEnabled(false), saturate(false), bp_enabled(false), median_value(-1), initOpticalTemperature(-1), initSTEFITemperature(-1), calib(NULL) + : file(NULL), type(0), min_T(0), min_T_height(0), store_it(false), motionCorrectionEnabled(false), saturate(false), bp_enabled(false), median_value(-1), calib(NULL) { } }; @@ -597,7 +543,14 @@ namespace rir bool IRFileLoader::isHCC() const { return m_data->type == BIN_FILE_HCC; } bool IRFileLoader::hasTimes() const { return m_data->has_times; } bool IRFileLoader::hasCalibration() const { return m_data->calib && m_data->calib->isValid(); } + bool IRFileLoader::is_in_T() const { return m_data->store_it; } + BaseCalibration *IRFileLoader::calibration() const { return m_data->calib; } + bool IRFileLoader::setCalibration(BaseCalibration *calibration) + { + m_data->calib = calibration; + return true; + } void IRFileLoader::setBadPixelsEnabled(bool enable) { @@ -668,29 +621,7 @@ namespace rir void IRFileLoader::removeMotion(unsigned short *img, int w, int h, int pos) { - if (!m_data->motionCorrectionEnabled) - return; - - if (m_data->upper.size() && !m_data->lower.size()) - { - - // Apply on the full image - std::vector tmp(w * h); - translate(img, tmp.data(), 0.f, w, h, -m_data->upper[pos].x(), -m_data->upper[pos].y(), rir::TranslateNearest); - std::copy(tmp.begin(), tmp.end(), img); - } - else if (m_data->upper.size() && m_data->lower.size()) - { - - // Apply upper part - std::vector tmp(w * h / 2); - translate(img, tmp.data(), 0.f, w, h / 2, -m_data->upper[pos].x(), -m_data->upper[pos].y(), rir::TranslateNearest); - std::copy(tmp.begin(), tmp.end(), img); - - // Apply lower part - translate(img + (h / 2) * w, tmp.data(), 0.f, w, h / 2, -m_data->lower[pos].x(), -m_data->lower[pos].y(), rir::TranslateNearest); - std::copy(tmp.begin(), tmp.end(), img + (h / 2) * w); - } + // TODO } bool IRFileLoader::loadTranslationFile(const char *filename) @@ -711,19 +642,15 @@ namespace rir return false; } - m_data->upper.resize(ar.height); - if (ar.width == 7) - m_data->lower.resize(ar.height); + m_data->translation_points.resize(ar.height); for (size_t i = 0; i < ar.height; ++i) { - m_data->upper[i] = PointF(ar(i, 1), ar(i, 2)); - if (ar.width == 7) - { - m_data->lower[i] = PointF(ar(i, 4), ar(i, 5)); - } + m_data->translation_points[i] = PointF(ar(i, 1), ar(i, 2)); } return true; + + return false; } void IRFileLoader::enableMotionCorrection(bool enable) { @@ -757,20 +684,6 @@ namespace rir m_data->filename = filename; replace(m_data->filename, "\\", "/"); - if ((m_data->calib = buildCalibration(m_data->filename.c_str(), this))) - { - this->setOpticaltemperature(m_data->calib->opticalTemperature()); - this->setSTEFItemperature(m_data->calib->STEFITemperature()); - } - else if (m_data->file->other) - { - if ((m_data->calib = m_data->file->other->calibration())) - { - this->setOpticaltemperature(m_data->calib->opticalTemperature()); - this->setSTEFItemperature(m_data->calib->STEFITemperature()); - } - } - m_data->min_T = m_data->min_T_height = 0; std::map::const_iterator min_T = this->globalAttributes().find("MIN_T"); std::map::const_iterator min_T_height = this->globalAttributes().find("MIN_T_HEIGHT"); @@ -789,6 +702,8 @@ namespace rir m_data->min_T_height = imageSize().height - 3; } + m_data->calib = buildCalibration(filename, this); + return true; } @@ -812,20 +727,6 @@ namespace rir m_data->size.width = w; m_data->size.height = h; - if ((m_data->calib = buildCalibration(NULL, this))) - { - this->setOpticaltemperature(m_data->calib->opticalTemperature()); - this->setSTEFItemperature(m_data->calib->STEFITemperature()); - } - else if (m_data->file->other) - { - if ((m_data->calib = m_data->file->other->calibration())) - { - this->setOpticaltemperature(m_data->calib->opticalTemperature()); - this->setSTEFItemperature(m_data->calib->STEFITemperature()); - } - } - m_data->min_T = m_data->min_T_height = 0; std::map::const_iterator min_T = this->globalAttributes().find("MIN_T"); std::map::const_iterator min_T_height = this->globalAttributes().find("MIN_T_HEIGHT"); @@ -844,6 +745,9 @@ namespace rir m_data->min_T_height = imageSize().height - 3; } + // TODO: add buildCalibration for file_reader + m_data->calib = buildCalibration(NULL, this); + return true; } @@ -909,198 +813,6 @@ namespace rir return res; } -#define PIXEL_ADDRESS_ALARM_32_ROIS 64 -#define PIXEL_ADDRESS_GLOBAL_ALARMS 65 -#define PIXEL_ADDRESS_FRAME_COUNTER 66 - -#define PIXEL_ADDRESS_TEMPERATURE_FPGA 71 -#define PIXEL_ADDRESS_VOLTAGE_FPGA 72 - -#define PIXEL_ADDRESS_CAMSTATUS 82 -#define PIXEL_ADDRESS_FRAME_PERIOD 83 -#define PIXEL_ADDRESS_CHRONO_TIME 84 -#define PIXEL_ADDRESS_BOARD_TIME 85 -#define PIXEL_ADDRESS_DMA_TIME 86 -#define PIXEL_ADDRESS_FIRMWARE_DATE 127 -#define PIXEL_ADDRESS_ABSOLUTE_TIME 89 - -#define PIXEL_ADDRESS_THR_POSITION 91 - - union U_CAMSTATUS - { - uint32_t values; - struct - { - uint32_t camtemp : 9; // camtemp * 10 - uint32_t filtertemp : 9; // filtertemp * 10 - uint32_t Peltierpower : 7; - uint32_t Watercooling : 1; - uint32_t Powersupplies : 1; - uint32_t Camident : 4; - }; - }; - union U_THR_POSITION - { - int64_t values; - struct - { - int64_t pfu : 7; - int64_t M1 : 19; - int64_t mb : 7; - int64_t M2 : 19; - int64_t focale : 12; // focale * 100 - }; - }; - - template - std::bitset extract_bits(std::bitset x, int begin, int end) - { - (x >>= (size - end)) <<= (size - end); - x <<= begin; - return x; - } - - std::map IRFileLoader::extractInfos(const unsigned short *img) - { - std::map attrs; - int w = imageSize().width; - int h = imageSize().height; - if ((w == 640 || w == 320) && (h == 515 || h == 243)) - { - - // extract infos in last lines - - // extract - unsigned int *line0 = (unsigned int *)(img + w * (h - 3)); - // unsigned int * line1 = (unsigned int *)(line0 + width); - // unsigned int * line2 = (unsigned int *)(line1 + width); - - // alarms - unsigned int alarm32ROIs, globalAlarm; - int firmware_date, day, month, year; - - memcpy(&alarm32ROIs, line0 + PIXEL_ADDRESS_ALARM_32_ROIS, 4); - memcpy(&globalAlarm, line0 + PIXEL_ADDRESS_GLOBAL_ALARMS, 4); - - // frame counter - unsigned frameCounter; - memcpy(&frameCounter, line0 + PIXEL_ADDRESS_FRAME_COUNTER, 4); - - // temperature du FPGA de la carte Sundance, tensions d'alimentation FPGA de la carte Sundance - unsigned board_T, board_V; - memcpy(&board_T, line0 + PIXEL_ADDRESS_TEMPERATURE_FPGA, 4); - memcpy(&board_V, line0 + PIXEL_ADDRESS_VOLTAGE_FPGA, 4); - - // TODO : TYPO ??? - // unsigned frame_period; - memcpy(&board_V, line0 + PIXEL_ADDRESS_FRAME_PERIOD, 4); - - // chrono time - unsigned chrono_time, board_time, dma_time; - time_t absolute_time; - - memcpy(&chrono_time, line0 + PIXEL_ADDRESS_CHRONO_TIME, 4); - memcpy(&board_time, line0 + PIXEL_ADDRESS_BOARD_TIME, 4); - memcpy(&dma_time, line0 + PIXEL_ADDRESS_DMA_TIME, 4); - memcpy(&absolute_time, line0 + PIXEL_ADDRESS_ABSOLUTE_TIME, 8); - memcpy(&firmware_date, line0 + PIXEL_ADDRESS_FIRMWARE_DATE, 4); - - std::chrono::system_clock::time_point tp{std::chrono::milliseconds{absolute_time}}; - - attrs["Alarm ROIs"] = tostring_binary32(alarm32ROIs, 12); - attrs["Alarm ALL"] = tostring_binary32(globalAlarm, 12); - // attrs["Board temperature"] = toString(board_T); - // attrs["Board tension"] = toString(board_V); - attrs["Frame number"] = toString(frameCounter); - // attrs["Frame period"] = toString(frame_period); - attrs["Time (chrono)"] = toString(chrono_time); - attrs["Time (board)"] = toString(board_time); - attrs["Time (DMA)"] = toString(dma_time); - attrs["Time (absolute in ms)"] = toString(absolute_time); - attrs["Datetime"] = serializeTimePoint(tp, "%FT%TZ"); - - day = (firmware_date >> 24) & 0xFF; - month = (firmware_date >> 16) & 0xFF; - year = (firmware_date) & 0xFFFF; - - attrs["Firmware Date"] = toString(day) + "-" + toString(month) + "-" + toString(year); - - attrs["Camera T (C)"] = "0"; - attrs["IR Filter T (C)"] = "0"; - attrs["Peltier Power (%)"] = "0"; - - if (h > 511) - { - /*unsigned int cam_status = line0[32 * 2 + 18]; - - int Filtertemp = (cam_status >> 9) & 0x1FF; - int Peltierpower = (cam_status >> 18) & 0x7F; - int Camtemp = cam_status & 0x1FF;*/ - - // Camstatus = Camtemp & 0x1FF | ((Filtertemp & 0x1FF) << 9) | ((Peltierpower & 0x7F) << 18) | ((Watercooling & 0x1) << 25) | ((Powersupplies & 0x1) << 26) | ((Camident & 0xF) << 27); - // reg_write(68, Camstatus); // Set cam status - - /*int c1 = cam_status & 0xFF; - int c_1 = (cam_status>>8) & 0xFF; - int c2 = cam_status & 0xFFF; - int c3 = cam_status & 0xFFFF; - int c4 = cam_status & 0xFFFFF; - int c5 = cam_status & 0xFFFFFF; - int c6 = cam_status & 0xFFFFFFF;*/ - - // unsigned int frame_period = line0[32 * 2 + 19] / 1000 * 8; // In us - unsigned char *pc_cam_header0 = (unsigned char *)(line0 + 320); - - int temp_pos = (pc_cam_header0[47] & 0x0FF) << 8 | (pc_cam_header0[46] & 0x0FF); - int temp_neg = (pc_cam_header0[49] & 0x0FF) << 8 | (pc_cam_header0[48] & 0x0FF); - double diode_temp = 77 - (((temp_pos - temp_neg) * 0.000045776) * 1000 - 1042) / 1.56; - attrs["Sensor T (K)"] = toString(diode_temp); - - double VCC = (((pc_cam_header0[53] & 0x0FF) << 8 | (pc_cam_header0[52] & 0x0FF)) * 0.000045776) * 2; - attrs["VCC (V)"] = toString(VCC); - - double VDD = (((pc_cam_header0[55] & 0x0FF) << 8 | (pc_cam_header0[54] & 0x0FF)) * 0.000045776); - attrs["VDD (V)"] = toString(VDD); - - double VDO = (((pc_cam_header0[57] & 0x0FF) << 8 | (pc_cam_header0[56] & 0x0FF)) * 0.000045776); - attrs["VDO (V)"] = toString(VDO); - - double VNEG = ((((pc_cam_header0[59] & 0x0FF) << 8 | (pc_cam_header0[58] & 0x0FF)) * 0.000045776)); - attrs["VNEG (V)"] = toString(VNEG); - - static_assert(sizeof(U_CAMSTATUS) == sizeof(uint32_t), "wrong size of U_CAMSTATUS"); - U_CAMSTATUS camstatus; - memcpy(&camstatus, line0 + PIXEL_ADDRESS_CAMSTATUS, sizeof(U_CAMSTATUS)); - attrs["Camera T (C)"] = toString((double)camstatus.camtemp / 10); - attrs["IR Filter T (C)"] = toString((double)camstatus.filtertemp / 10); - attrs["Peltier Power (%)"] = toString(camstatus.Peltierpower); - attrs["watercooling"] = toString(camstatus.Watercooling); - attrs["power_supply"] = toString(camstatus.Powersupplies); - attrs["Camera #"] = toString(camstatus.Camident); - - std::bitset<12> global_alarm{globalAlarm}; - - attrs["toohot_triggered"] = toString((globalAlarm >> 0) & 1); - attrs["external_alarm"] = toString((globalAlarm >> 1) & 1); - attrs["internal_alarm"] = toString((globalAlarm >> 2) & 1); - attrs["alarm_filter"] = toString(extract_bits(global_alarm, 4, 9).count()); - - static_assert(sizeof(U_THR_POSITION) == sizeof(int64_t), "wrong size of U_THR_POSITION"); - U_THR_POSITION thr_status; - memcpy(&thr_status, line0 + PIXEL_ADDRESS_THR_POSITION, sizeof(U_THR_POSITION)); - if (thr_status.values != 0) - { - attrs["M1"] = toString(thr_status.M1); - attrs["M2"] = toString(thr_status.M2); - attrs["PFU"] = toString(thr_status.pfu); - attrs["MB"] = toString(thr_status.mb); - attrs["focale"] = toString((double)thr_status.focale / 100); - } - } - } - return attrs; - } - bool IRFileLoader::extractAttributes(std::map &attrs) const { bool res = true; @@ -1111,7 +823,7 @@ namespace rir else if (m_data->type == BIN_FILE_OTHER) res = m_data->file->other->extractAttributes(attrs); - for (std::map::const_iterator it = m_data->attributes.begin(); it != m_data->attributes.end(); ++it) + for (std::map::const_iterator it = attributes.begin(); it != attributes.end(); ++it) attrs.insert(*it); return res; @@ -1129,10 +841,6 @@ namespace rir else if (calibration == 1) { // apply the calibration - if (m_data->calib->opticalTemperature() != this->opticalTemperature()) - m_data->calib->setOpticalTemperature(this->opticalTemperature()); - if (m_data->calib->STEFITemperature() != this->STEFITemperature()) - m_data->calib->setSTEFITemperature(this->STEFITemperature()); if (!m_data->calib->applyF(img, this->invEmissivities(), size, out, &m_data->saturate)) return false; return true; @@ -1146,10 +854,6 @@ namespace rir else if (calibration == 1) { // apply the calibration - if (m_data->calib->opticalTemperature() != this->opticalTemperature()) - m_data->calib->setOpticalTemperature(this->opticalTemperature()); - if (m_data->calib->STEFITemperature() != this->STEFITemperature()) - m_data->calib->setSTEFITemperature(this->STEFITemperature()); if (!m_data->calib->apply(img, this->invEmissivities(), size, img, &m_data->saturate)) return false; return true; @@ -1159,7 +863,6 @@ namespace rir bool IRFileLoader::readImage(int pos, int calibration, unsigned short *pixels) { - m_data->attributes.clear(); if (pos < 0 || pos >= size() || !m_data->file) return false; @@ -1180,11 +883,6 @@ namespace rir if (bin_read_image(m_data->file, pos, pixels, &time) != 0) return false; - if (m_data->initOpticalTemperature == -1 && m_data->calib) - m_data->initOpticalTemperature = m_data->calib->opticalTemperature(); - if (m_data->initSTEFITemperature == -1 && m_data->calib) - m_data->initSTEFITemperature = m_data->calib->STEFITemperature(); - bool is_in_T = m_data->store_it; // If the image is already in temperature with subtracted min, add the min temperature stored as attribute @@ -1203,8 +901,6 @@ namespace rir m_data->img.resize(m_data->size.height * m_data->size.width); memcpy(m_data->img.data(), pixels, m_data->img.size() * 2); - m_data->attributes = extractInfos(pixels); - m_data->saturate = false; if (calibration == 0) @@ -1229,29 +925,6 @@ namespace rir return false; else { - if (is_in_T) - { - if (m_data->initOpticalTemperature != this->opticalTemperature() || m_data->initSTEFITemperature != this->STEFITemperature() || this->globalEmissivity() != 1.f) - { - // switch back to DL without the last 3 lines - m_data->calib->applyInvert(pixels, m_data->file->h264.lastIt().data(), m_data->min_T_height * imageSize().width, pixels); - m_data->calib->setOpticalTemperature(this->opticalTemperature()); - m_data->calib->setSTEFITemperature(this->STEFITemperature()); - // back to T for the full image - if (!m_data->calib->apply(pixels, this->invEmissivities(), m_data->size.height * m_data->size.width, pixels, &m_data->saturate)) - return false; - } - } - else - { - // apply the calibration - if (m_data->calib->opticalTemperature() != this->opticalTemperature()) - m_data->calib->setOpticalTemperature(this->opticalTemperature()); - if (m_data->calib->STEFITemperature() != this->STEFITemperature()) - m_data->calib->setSTEFITemperature(this->STEFITemperature()); - if (!m_data->calib->apply(pixels, this->invEmissivities(), m_data->size.height * m_data->size.width, pixels, &m_data->saturate)) - return false; - } // Remove motion if possible removeMotion(pixels, imageSize().width, imageSize().height - 3, pos); diff --git a/src/cpp/video_io/IRFileLoader.h b/src/cpp/video_io/IRFileLoader.h index 54f8de6d..9b44622c 100644 --- a/src/cpp/video_io/IRFileLoader.h +++ b/src/cpp/video_io/IRFileLoader.h @@ -61,6 +61,9 @@ namespace rir A IR video reader that can read several IR video formats: PCR files, a BIN (WEST format) files, PCR encapsulated into a BIN file, H264 video files, or any additional formats defined using the IRVideoLoaderBuilder mechanism. */ + using time_point = std::chrono::time_point; + IO_EXPORT std::string tostring_binary32(unsigned value, unsigned size); + IO_EXPORT std::string serializeTimePoint(const time_point &time, const std::string &format); class IO_EXPORT IRFileLoader : public IRVideoLoader { public: @@ -81,7 +84,9 @@ namespace rir bool isHCC() const; bool hasTimes() const; bool hasCalibration() const; + bool is_in_T() const; virtual BaseCalibration *calibration() const; + virtual bool setCalibration(BaseCalibration *calibration); virtual bool supportBadPixels() const { return true; } virtual void setBadPixelsEnabled(bool enable); @@ -110,8 +115,10 @@ namespace rir void removeBadPixels(unsigned short *img, int w, int h); void removeMotion(unsigned short *img, int w, int h, int pos); + // protected: + std::map attributes; + private: - std::map extractInfos(const unsigned short *img); class PrivateData; PrivateData *m_data; }; diff --git a/src/cpp/video_io/IRVideoLoader.cpp b/src/cpp/video_io/IRVideoLoader.cpp index c89a5e19..48bd240f 100644 --- a/src/cpp/video_io/IRVideoLoader.cpp +++ b/src/cpp/video_io/IRVideoLoader.cpp @@ -14,7 +14,7 @@ namespace rir static std::recursive_mutex _mutex; IRVideoLoader::IRVideoLoader() - : m_opticalT(0), m_STEFIT(0), m_globalEmi(1) + : m_globalEmi(1) { _mutex.lock(); _instances.push_back(this); diff --git a/src/cpp/video_io/IRVideoLoader.h b/src/cpp/video_io/IRVideoLoader.h index daa17984..7c7fd86f 100644 --- a/src/cpp/video_io/IRVideoLoader.h +++ b/src/cpp/video_io/IRVideoLoader.h @@ -26,8 +26,6 @@ namespace rir class IO_EXPORT IRVideoLoader { std::vector m_invEmissivities; - unsigned short m_opticalT; - unsigned short m_STEFIT; float m_globalEmi; public: @@ -97,25 +95,6 @@ namespace rir } float globalEmissivity() const { return m_globalEmi; } - /**Set optics temperature (B30 temperature)*/ - void setOpticaltemperature(unsigned short temp_C) - { - m_opticalT = temp_C; - } - /**Returns B30 temperature*/ - unsigned short opticalTemperature() const { return m_opticalT; } - - /**Set STEFI temperature*/ - void setSTEFItemperature(unsigned short temp_C) - { - m_STEFIT = temp_C; - } - /**Returns STEFI temperature*/ - unsigned short STEFITemperature() const - { - return m_STEFIT; - } - /**Returns true if the last read image calibration saturated, false otherwise*/ virtual bool saturate() const { return false; } /**Movie size in number of images*/ @@ -148,6 +127,8 @@ namespace rir /**Returns calibration object attached to this loader (if any) */ virtual BaseCalibration *calibration() const = 0; + virtual bool setCalibration(BaseCalibration *calibration) = 0; + /**Returns supported calibration for this video reader. For IR videos, this function should return something like ("Raw Data","Temperature").*/ virtual StringList supportedCalibration() const = 0; diff --git a/src/cpp/video_io/InstallFfmpeg.cmake b/src/cpp/video_io/InstallFfmpeg.cmake index 1ecdb954..fa1a1ddf 100644 --- a/src/cpp/video_io/InstallFfmpeg.cmake +++ b/src/cpp/video_io/InstallFfmpeg.cmake @@ -1,15 +1,17 @@ -MESSAGE("Ffmpeg libs path: ${COMPLETE_FFMPEG_LIBS}") +MESSAGE(STATUS "Ffmpeg libs path: ${COMPLETE_FFMPEG_LIBS}") foreach(ffmpeg_lib IN ITEMS ${COMPLETE_FFMPEG_LIBS} ) + MESSAGE(STATUS "CORRECT_FFMPEG_LIB_PATH: ${CORRECT_FFMPEG_LIB_PATH}") + file(GLOB CORRECT_FFMPEG_LIB_PATH "${FFMPEG_LIB_DIR}/*${ffmpeg_lib}*${FFMPEG_SUFFIX}*") foreach(ffmpeg_lib_path ${CORRECT_FFMPEG_LIB_PATH}) IF(ffmpeg_lib_path) - MESSAGE("Found file ${ffmpeg_lib_path}, copy to ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}") - file(COPY ${ffmpeg_lib_path} DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}) + MESSAGE(STATUS "Found file ${ffmpeg_lib_path}, copy to ${FFMPEG_INSTALL_DIR}") + file(COPY ${ffmpeg_lib_path} DESTINATION ${FFMPEG_INSTALL_DIR}) endif() endforeach() endforeach() @@ -18,8 +20,8 @@ IF (WIN32) IF (NOT MSVC) MESSAGE("Custom mingw installation") # mingw/msys2 - file(GLOB ffmpeg_dlls "${CMAKE_SOURCE_DIR}/3rd_64/ffmpeg/install/bin/*.dll") + file(GLOB ffmpeg_dlls "${PROJECT_SOURCE_DIR}/3rd_64/ffmpeg/install/bin/*.dll") MESSAGE("Copy files: ${ffmpeg_dlls}") - file(COPY ${ffmpeg_dlls} DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}) + file(COPY ${ffmpeg_dlls} DESTINATION ${FFMPEG_INSTALL_DIR}) ENDIF() ENDIF() \ No newline at end of file diff --git a/src/cpp/video_io/ZFile.cpp b/src/cpp/video_io/ZFile.cpp deleted file mode 100644 index eacdf3a7..00000000 --- a/src/cpp/video_io/ZFile.cpp +++ /dev/null @@ -1,676 +0,0 @@ - - -#include -#include -#include -#include -#include - -#include "ZFile.h" -#include "Log.h" -#include "FileAttributes.h" -#include "ReadFileChunk.h" -#include "Misc.h" -#include "tools.h" - -using namespace rir; - -typedef union BIN_HEADER -{ - struct - { - uint8_t version; - uint8_t triggers; - uint8_t compression; - }; - char reserved[128]; -} BIN_HEADER; - -typedef union BIN_TRIGGER -{ - struct - { - uint64_t date; - uint64_t rate; - uint64_t samples; - uint64_t samples_pre_trigger; - uint64_t type; - uint64_t nb_channels; - uint64_t data_type; - uint64_t data_format; - uint64_t data_repetition; - uint64_t data_size_x; - uint64_t data_size_y; - }; - char reserved[128]; -} BIN_TRIGGER; - -typedef struct ZFile -{ - BIN_HEADER bheader; - BIN_TRIGGER btrigger; - - int readOnly; - FileAttributes attributes; - void *file_reader; - std::fstream file; - std::string filename; - unsigned char *mem; - uint64_t tot_size; // file/mem total size in bytes - uint64_t pos; // file/mem position in bytes - - // compression buffer - std::vector buffer; - int clevel; // compression level - - // image pos and timestamps for read only seeking - std::vector timestamps; - std::vector positions; -} ZFile; - -inline ZFile *createZFile() -{ - ZFile *f = new ZFile(); //(ZFile*)malloc(sizeof(ZFile)); - memset(&f->bheader, 0, sizeof(f->bheader)); - memset(&f->btrigger, 0, sizeof(f->btrigger)); - f->bheader.version = 1; - f->bheader.compression = 2; // 1 for raw zstd, 2 for blosc+zstd - f->bheader.triggers = 1; - f->btrigger.rate = 1; - f->readOnly = 1; - // f->file = NULL; - f->file.close(); - f->file_reader = NULL; - f->mem = NULL; - f->tot_size = 0; - f->pos = 0; - // f->buffer = NULL; - // f->buffer_size = 0; - f->clevel = 0; - // f->timestamps = NULL; - // f->positions = NULL; - return f; -} - -static void destroyZFile(void *_f) -{ - ZFile *f = (ZFile *)_f; - if (f) - { - // if (f->file) - // fclose(f->file); - // if (f->buffer) - // free(f->buffer); - // if (f->timestamps) - // free(f->timestamps); - // if (f->positions) - // free(f->positions); - // free(f); - delete f; - } -} - -void *z_open_file_read(void *file_reader) -{ - /*ZFile * res = createZFile(); - res->file.open(filename, std::ios::binary|std::ios::in);// = fopen(filename, "rb"); - if (!res->file) { - //logError("error"); - destroyZFile(res); - return NULL; - } - res->filename = filename;*/ - - if (!file_reader) - return NULL; - ZFile *res = createZFile(); - res->file_reader = file_reader; - - // get file size - /*res->file.seekg(0, std::ios::end); - res->tot_size = res->file.tellg(); - res->file.seekg(0);*/ - res->tot_size = fileSize(file_reader); - - // read headers - // res->file.read((char*)&res->bheader, sizeof(res->bheader)); - // res->file.read((char*)&res->btrigger, sizeof(res->btrigger)); - seekFile(file_reader, 0, AVSEEK_SET); - readFile(file_reader, &res->bheader, sizeof(res->bheader)); - readFile(file_reader, &res->btrigger, sizeof(res->btrigger)); - - // make sure BIN_HEADER is valid - if ((res->bheader.compression != 1 && res->bheader.compression != 2 && res->bheader.compression != 3) || res->bheader.version != 1 || res->bheader.triggers != 1) - { - destroyZFile(res); - // printf("error"); - return NULL; - } - - // make sure BIN_TRIGGER is valid - if (res->btrigger.data_size_x > 0 && res->btrigger.data_size_x < 3000 && res->btrigger.data_size_y > 0 && res->btrigger.data_size_y < 3000 && res->btrigger.rate > 0 && res->btrigger.rate < 1000) - { - res->readOnly = 1; - - // allocate compression buffer - res->buffer.resize((unsigned)zstd_compress_bound(res->btrigger.data_size_x * res->btrigger.data_size_y * 2)); - - // grab all images timestamps and positions in file - uint64_t pos = posFile(file_reader); // res->file.tellg(); - res->timestamps.resize(res->btrigger.samples); // = (int64_t*)malloc(sizeof(uint64_t) *res->btrigger.samples); - res->positions.resize(res->btrigger.samples); // = (int64_t*)malloc(sizeof(uint64_t) *res->btrigger.samples); - - bool has_partial_attributes = false; - bool has_attributes = false; - if (res->attributes.openReadOnly(file_reader)) - { - // the file contains attributes - // read timestamps and positions from the attributes if possible - has_partial_attributes = true; - res->btrigger.samples = res->attributes.size(); - std::map::const_iterator it = res->attributes.globalAttributes().find("positions"); - if (it != res->attributes.globalAttributes().end()) - { - const std::string positions = it->second; - if (positions.size() == res->btrigger.samples * 8) - { - - res->timestamps.resize(res->btrigger.samples); - res->positions.resize(res->btrigger.samples); - const char *poss = positions.c_str(); - for (size_t i = 0; i < res->btrigger.samples; ++i) - { - res->timestamps[i] = res->attributes.timestamp(i); - int64_t p = 0; - memcpy(&p, poss, 8); - poss += 8; - if (!is_little_endian()) - p = swap_int64(p); - res->positions[i] = p; - } - has_attributes = true; - } - } - } - - if (!has_attributes) - { - seekFile(file_reader, pos, AVSEEK_SET); - if (res->btrigger.samples == 0) - { - // scan the file - res->timestamps.clear(); - res->positions.clear(); - size_t table_size = res->attributes.tableSize(); - // remove the attributes table size when looking for images - size_t max_size = fileSize(file_reader) - (has_partial_attributes ? table_size : 0); - while (true) - { - uint32_t fsize = 0; - int64_t timestamp = 0; - int64_t pos = posFile(file_reader); // res->file.tellg(); - if ((size_t)pos >= max_size) - break; - - if (readFile(file_reader, ×tamp, sizeof(timestamp)) != sizeof(timestamp)) - break; - if (readFile(file_reader, &fsize, sizeof(fsize)) != sizeof(fsize)) - break; - if (seekFile(file_reader, fsize, AVSEEK_CUR) < 0) // go to next - break; - res->timestamps.push_back(timestamp); - res->positions.push_back(pos); - res->btrigger.samples++; - } - } - else - { - for (size_t i = 0; i < res->btrigger.samples; ++i) - { - uint32_t fsize = 0; - int64_t timestamp = 0; - - res->positions[i] = posFile(file_reader); // res->file.tellg(); - - if (readFile(file_reader, ×tamp, sizeof(timestamp)) != sizeof(timestamp)) - { - res->btrigger.samples = i + 1; - break; - } - if (readFile(file_reader, &fsize, sizeof(fsize)) != sizeof(fsize)) - { - res->btrigger.samples = i + 1; - break; - } - res->timestamps[i] = timestamp; - if (seekFile(file_reader, fsize, AVSEEK_CUR) < 0) - { - res->btrigger.samples = i + 1; - break; - } - } - } - } - // res->file.seekg(pos); - seekFile(file_reader, pos, AVSEEK_SET); - - return res; - } - - destroyZFile(res); - // printf("error"); - return NULL; -} - -void *z_open_memory_read(void *mem, uint64_t size) -{ - ZFile *res = createZFile(); - res->tot_size = size; - res->mem = (unsigned char *)mem; - - // read headers - memcpy(&res->bheader, res->mem + res->pos, sizeof(res->bheader)); - res->pos += sizeof(res->bheader); - memcpy(&res->btrigger, res->mem + res->pos, sizeof(res->btrigger)); - res->pos += sizeof(res->btrigger); - - // make sure BIN_HEADER is valid - if (res->bheader.compression < 1 || res->bheader.compression > 3 || res->bheader.version != 1 || res->bheader.triggers != 1) - { - destroyZFile(res); - // printf("error"); - return NULL; - } - - // make sure BIN_TRIGGER is valid - if (res->btrigger.data_size_x > 0 && res->btrigger.data_size_x < 2000 && res->btrigger.data_size_y > 0 && res->btrigger.data_size_y < 2000 && res->btrigger.rate > 0 && res->btrigger.rate < 1000) - { - res->readOnly = 1; - - // allocate compression buffer - // res->buffer_size = (unsigned)ZSTD_compressBound(res->btrigger.data_size_x*res->btrigger.data_size_y * 2); - // res->buffer = malloc(res->buffer_size); - res->buffer.resize(zstd_compress_bound(res->btrigger.data_size_x * res->btrigger.data_size_y * 2)); - - // grab all images timestamps and positions in file - uint64_t pos = res->pos; - res->timestamps.resize(res->btrigger.samples); // = (int64_t*)malloc(sizeof(uint64_t) *res->btrigger.samples); - res->positions.resize(res->btrigger.samples); // = (int64_t*)malloc(sizeof(uint64_t) *res->btrigger.samples); - for (size_t i = 0; i < res->btrigger.samples; ++i) - { - uint32_t fsize = 0; - int64_t timestamp = 0; - - res->positions[i] = pos; - memcpy(×tamp, res->mem + pos, sizeof(timestamp)); - pos += sizeof(timestamp); - memcpy(&fsize, res->mem + pos, sizeof(fsize)); - pos += sizeof(fsize); - res->timestamps[i] = timestamp; - - if ((pos += fsize) >= res->tot_size) - { - res->btrigger.samples = i + 1; - break; - } - } - - return res; - } - - destroyZFile(res); - // printf("error"); - return NULL; -} - -void *z_open_file_write(const char *filename, int width, int height, int rate, int method, int clevel) -{ - ZFile *res = createZFile(); - res->file.open(filename, std::ios::binary | std::ios::out); // = fopen(filename, "wb"); - if (!res->file) - { - // printf("error"); - destroyZFile(res); - return NULL; - } - res->filename = filename; - // set file size - res->tot_size = 0; - res->readOnly = 0; - res->clevel = clevel; - - // allocate compression buffer - // res->buffer_size = (unsigned) ZSTD_compressBound(width*height * 2); - // res->buffer = malloc(res->buffer_size); - res->buffer.resize(zstd_compress_bound(width * height * 2)); - - res->bheader.compression = method; - - // write headers - res->btrigger.type = 1; // continous acquisition - res->btrigger.nb_channels = 1; - res->btrigger.data_format = 2; // 2D array - res->btrigger.data_format = 3; // u16 - res->btrigger.data_repetition = 1; - res->btrigger.data_size_x = width; - res->btrigger.data_size_y = height; - res->btrigger.rate = rate; - // fwrite(&res->bheader, sizeof(res->bheader), 1, res->file); - // fwrite(&res->btrigger, sizeof(res->btrigger), 1, res->file); - // res->pos = ftell(res->file); - res->file.write((char *)(&res->bheader), sizeof(res->bheader)); - res->file.write((char *)(&res->btrigger), sizeof(res->btrigger)); - res->pos = res->file.tellp(); - res->attributes.open(filename); - return res; -} - -void *z_open_memory_write(void *mem, uint64_t size, int width, int height, int rate, int method, int clevel) -{ - ZFile *res = createZFile(); - res->tot_size = size; - res->mem = (unsigned char *)mem; - - // cannot store at least one image - if (size < sizeof(res->btrigger) + sizeof(res->bheader) + width * height * 2) - { - destroyZFile(res); - // printf("error"); - return NULL; - } - - // set file size - res->readOnly = 0; - res->clevel = clevel; - - // allocate compression buffer - // res->buffer_size = (unsigned)ZSTD_compressBound(width*height * 2); - // res->buffer = malloc(res->buffer_size); - res->buffer.resize((unsigned)zstd_compress_bound(width * height * 2)); - - // write headers - res->bheader.compression = method; - - res->btrigger.type = 1; // continous acquisition - res->btrigger.nb_channels = 1; - res->btrigger.data_format = 2; // 2D array - res->btrigger.data_format = 3; // u16 - res->btrigger.data_repetition = 1; - res->btrigger.data_size_x = width; - res->btrigger.data_size_y = height; - res->btrigger.rate = rate; - - memcpy(res->mem + res->pos, &res->bheader, sizeof(res->bheader)); - res->pos += sizeof(res->bheader); - memcpy(res->mem + res->pos, &res->btrigger, sizeof(res->btrigger)); - res->pos += sizeof(res->btrigger); - - return res; -} - -uint64_t z_close_file(void *file) -{ - ZFile *f = (ZFile *)file; - uint64_t res = 0; - if (f && !f->readOnly) - { - // write again the trigger header to update the sample count - if (f->file) - { - f->file.seekp(sizeof(BIN_HEADER)); - f->file.write((char *)&f->btrigger, sizeof(f->btrigger)); - // fseek(f->file, sizeof(BIN_HEADER), SEEK_SET); - // fwrite(&f->btrigger, sizeof(f->btrigger), 1, f->file); - } - else if (f->mem) - { - memcpy(f->mem + sizeof(BIN_HEADER), &f->btrigger, sizeof(BIN_TRIGGER)); - } - - res = f->pos; - f->file.close(); - - // TODO - // write timestamps and positions as attributes - std::string positions(f->btrigger.samples * 8, (char)0); - char *poss = (char *)positions.data(); - f->attributes.resize(f->btrigger.samples); - for (size_t i = 0; i < f->btrigger.samples; ++i) - { - f->attributes.setTimestamp(i, f->timestamps[i]); - int64_t p = f->positions[i]; - if (!is_little_endian()) - p = swap_int64(p); - memcpy(poss, &p, 8); - poss += 8; - } - f->attributes.addGlobalAttribute("positions", positions); - f->attributes.close(); - } - - destroyZFile(file); - return res; -} - -int z_image_count(void *file) -{ - if (!file) - return -1; - ZFile *f = (ZFile *)file; - return (int)f->btrigger.samples; -} - -int z_image_size(void *file, int *width, int *height) -{ - if (!file) - return -1; - ZFile *f = (ZFile *)file; - *width = (int)f->btrigger.data_size_x; - *height = (int)f->btrigger.data_size_y; - return 0; -} - -int64_t z_file_size(void *file) -{ - if (!file) - return -1; - ZFile *f = (ZFile *)file; - if (!f->readOnly) - return f->pos; - else - return f->tot_size; -} - -int z_write_image(void *file, const unsigned short *img, int64_t timestamp) -{ - if (!file) - return -1; - - ZFile *f = (ZFile *)file; - if (f->readOnly) - return -1; - - // compress image - uint32_t csize = 0; - if (f->bheader.compression == 1) - { - csize = (uint32_t)zstd_compress((char *)img, f->btrigger.data_size_x * f->btrigger.data_size_y * 2, f->buffer.data(), f->buffer.size(), f->clevel); - if (csize == (uint32_t)-1) - return -1; - } - else if (f->bheader.compression == 2) - { - int tmpsize = (int)blosc_compress_zstd((char *)img, f->btrigger.data_size_x * f->btrigger.data_size_y * 2, f->buffer.data(), f->buffer.size(), 2, RIR_BLOSC_SHUFFLE, f->clevel); - // int tmpsize = blosc_compress(f->clevel, BLOSC_SHUFFLE, 2, - // f->btrigger.data_size_x*f->btrigger.data_size_y * 2, img, f->buffer.data(), f->buffer.size()); - if (tmpsize < 0) - return tmpsize; - csize = tmpsize; - } - else if (f->bheader.compression == 3) - { - // int tmpsize = blosc_compress(f->clevel, BLOSC_BITSHUFFLE, 2, - // f->btrigger.data_size_x*f->btrigger.data_size_y * 2, img, f->buffer.data(), f->buffer.size()); - int tmpsize = (int)blosc_compress_zstd((char *)img, f->btrigger.data_size_x * f->btrigger.data_size_y * 2, f->buffer.data(), f->buffer.size(), 2, RIR_BLOSC_BITSHUFFLE, f->clevel); - if (tmpsize < 0) - return tmpsize; - csize = tmpsize; - } - - if (f->file) - { - int64_t pos = f->file.tellp(); - // write timestamp - // fwrite(×tamp, sizeof(timestamp), 1, f->file); - f->file.write((char *)×tamp, sizeof(timestamp)); - - // write compressed size - // fwrite(&csize, sizeof(csize), 1, f->file); - f->file.write((char *)&csize, sizeof(csize)); - - // write compressed image - // if (fwrite(f->buffer, csize, 1, f->file) != 1) - // return -1; - if (!f->file.write(f->buffer.data(), csize)) - return -1; - - f->pos = f->file.tellp(); // ftell(f->file); - f->btrigger.samples++; - - f->timestamps.push_back(timestamp); - f->positions.push_back(pos); - - return 0; - } - else if (f->mem) - { - if (f->pos + 8 + 4 + csize >= f->tot_size) - return -1; - - memcpy(f->mem + f->pos, ×tamp, sizeof(timestamp)); - f->pos += sizeof(timestamp); - memcpy(f->mem + f->pos, &csize, sizeof(csize)); - f->pos += sizeof(csize); - memcpy(f->mem + f->pos, f->buffer.data(), csize); - f->pos += csize; - f->btrigger.samples++; - return 0; - } - - return -1; -} - -int z_read_image(void *file, int pos, unsigned short *img, int64_t *timestamp) -{ - if (!file) - return -1; - - ZFile *f = (ZFile *)file; - if (!f->readOnly) - return -1; - - if (pos < 0 || pos >= (int)f->btrigger.samples) - return -1; - - /*if (!f->mem && !f->file && !f->filename.empty()) { - f->file.close(); - f->file.open(f->filename.c_str(), std::ios::in | std::ios::binary); - }*/ - - uint32_t fsize; - int64_t time; - if (f->file_reader) // f->file) - { - /*f->file.seekg(f->positions[pos]); - - //read timestamp - f->file.read((char*)&time, sizeof(time)); - if (timestamp) *timestamp = time; - - //read compressed data size - f->file.read((char*)&fsize, sizeof(fsize)); - - //read compressed image - if (!f->file.read(f->buffer.data(), fsize)) { - f->file.clear(); - return -1; - } - f->file.clear();*/ - - seekFile(f->file_reader, f->positions[pos], AVSEEK_SET); - readFile(f->file_reader, &time, sizeof(time)); - if (timestamp) - *timestamp = time; - - // read compressed data size - readFile(f->file_reader, &fsize, sizeof(fsize)); - - // read compressed image - if (readFile(f->file_reader, f->buffer.data(), fsize) != (int)fsize) - { - return -1; - } - - if (f->bheader.compression == 1) - { - if (zstd_decompress(f->buffer.data(), fsize, (char *)img, f->btrigger.data_size_x * f->btrigger.data_size_y * 2) < 0) - return -1; - - // if (ZSTD_isError(ZSTD_decompress(img, f->btrigger.data_size_x*f->btrigger.data_size_y * 2, f->buffer.data(), fsize))) - // return -1; - } - else if (f->bheader.compression == 2 || f->bheader.compression == 3) - { - if (blosc_decompress_zstd(f->buffer.data(), fsize, (char *)img, f->btrigger.data_size_x * f->btrigger.data_size_y * 2) < 0) - return -1; - // int cread = blosc_decompress(f->buffer.data(), img, f->btrigger.data_size_x*f->btrigger.data_size_y * 2); - // if (cread < 0) - // return cread; - } - - return 0; - } - else if (f->mem) - { - uint64_t loc = f->positions[pos]; - memcpy(&time, f->mem + loc, sizeof(time)); - loc += sizeof(time); - if (timestamp) - *timestamp = time; - memcpy(&fsize, f->mem + loc, sizeof(fsize)); - loc += sizeof(fsize); - memcpy(f->buffer.data(), f->mem + loc, fsize); - - if (f->bheader.compression == 1) - { - // if (ZSTD_isError(ZSTD_decompress(img, f->btrigger.data_size_x*f->btrigger.data_size_y * 2, f->buffer.data(), fsize))) - // return -1; - if (zstd_decompress(f->buffer.data(), fsize, (char *)img, f->btrigger.data_size_x * f->btrigger.data_size_y * 2) < 0) - return -1; - } - else if (f->bheader.compression == 2 || f->bheader.compression == 3) - { - // int cread = blosc_decompress(f->buffer.data(), img, f->btrigger.data_size_x*f->btrigger.data_size_y * 2); - // if (cread < 0) - // return cread; - if (blosc_decompress_zstd(f->buffer.data(), fsize, (char *)img, f->btrigger.data_size_x * f->btrigger.data_size_y * 2) < 0) - return -1; - } - - return 0; - } - - return -1; -} - -int64_t *z_get_timestamps(void *file) -{ - if (!file) - return NULL; - - ZFile *f = (ZFile *)file; - if (!f->readOnly) - return NULL; - - return f->timestamps.data(); -} diff --git a/src/cpp/video_io/ZFile.h b/src/cpp/video_io/ZFile.h deleted file mode 100644 index 2e21a93d..00000000 --- a/src/cpp/video_io/ZFile.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef Z_FILE_H -#define Z_FILE_H - -#include "rir_config.h" -#include - -/** @file - -C interface to manage video files compressed with zstd -*/ - -/** -Open in read-only mode given compressed BIN file and return an opaque handle on the file. -*/ -IO_EXPORT void *z_open_file_read(void *file_reader); -/** -Open in read-only mode given compressed BIN file stored in memory and return an opaque handle on the file. -*/ -IO_EXPORT void *z_open_memory_read(void *mem, uint64_t size); -/** -Open in write-only mode given compressed BIN file and return an opaque handle on the file. -*/ -IO_EXPORT void *z_open_file_write(const char *filename, int width, int height, int rate, int method, int clevel = 2); -/** -Open in read-only mode given compressed BIN file stored in memory and return an opaque handle on the file. -*/ -IO_EXPORT void *z_open_memory_write(void *mem, uint64_t size, int width, int height, int rate, int method, int clevel = 2); -/** -Close a compressed BIN file and return its size (the size is only valid for write-only handle) -*/ -IO_EXPORT uint64_t z_close_file(void *file); -/** -Return the number of images stored in given file. -*/ -IO_EXPORT int z_image_count(void *file); -/** -Return the image dimension of given file. -*/ -IO_EXPORT int z_image_size(void *file, int *width, int *height); -/** -Write an image in a write-only handle with given \a timestamp. -*/ -IO_EXPORT int z_write_image(void *file, const unsigned short *img, int64_t timestamp); -/** -Read the image at position \a pos from given handle. -If \a timestamp is not NULL, the image timestamp in nanoseconds will be stored inside. -*/ -IO_EXPORT int z_read_image(void *file, int pos, unsigned short *img, int64_t *timestamp = NULL); -/** -For read-only handle, return the a pointer to the timestamp array. -This array has a size given by #z_image_count and contains timestamps in nanoseconds. -*/ -IO_EXPORT int64_t *z_get_timestamps(void *file); - -#endif \ No newline at end of file diff --git a/src/cpp/video_io/h264.cpp b/src/cpp/video_io/h264.cpp index 19b4a491..a857478c 100644 --- a/src/cpp/video_io/h264.cpp +++ b/src/cpp/video_io/h264.cpp @@ -78,6 +78,341 @@ extern "C" namespace rir { + static double std_dev_diff(const unsigned short* p1, const unsigned short* p2, int size) + { + double x = 0.0; + double x_squared = 0.0; + + for (int i= 0; i < size; ++i) + { + int diff = (int)p1[i] - (int)p2[i]; + diff = (diff < 0 ? -diff : diff); + x += diff; + x_squared += diff * diff; + } + + return std::sqrt((x_squared - (x * x) / size) / + (size - 1) + ); + } + + static double mean_image(const unsigned short* p, int size) + { + std::int64_t sum1 = 0, sum2=0; + + int size2 = size / 2; + for (int i = 0; i < size2; ++i) + sum1 += p[i]; + for (int i = size2; i < size; ++i) + sum2 += p[i]; + + sum1 += sum2; + return (double)sum1 / size; + } + + static std::pair mean_std(const double* p, int size) + { + double x = 0.0; + double x_squared = 0.0; + + for (int i = 0; i < size; ++i) + { + x += p[i]; + x_squared += p[i] * p[i]; + } + + return {x/size, std::sqrt((x_squared - (x * x) / size) / + (size - 1) + ) }; + } + + static void max_image(unsigned short* dst, const unsigned short* im, int width, int height, int lossy_height) + { + int lossy_size = width * lossy_height; + for (int i = 0; i < lossy_size; ++i) + dst[i] = std::max(dst[i], im[i]); + + // forward last lines + if (height != lossy_height) { + memcpy(dst + lossy_size, im + lossy_size, (height - lossy_height) * width * 2); + } + } + + class VideoDownsampler::PrivateData + { + public: + int width; + int height; + int lossy_height; + int factor; + double factor_std; + callback_type callback; + + std::vector std_dev; + std::vector prev; + std::vector last_saved; + int buffer_size; + + int part; + std::vector std_dev_copy; + int last_added; + int count; + std::vector max_im; + std::int64_t max_im_time; + std::vector timestamps; + int i; + std::map attributes; + + PrivateData(int _width, int _height, int _lossy_height, int _factor, double _factor_std, callback_type _callback) + :width(_width), height(_height), lossy_height(_lossy_height), factor(_factor), factor_std(_factor_std), callback(_callback) + { + prev.resize(width * height); + last_saved.resize(width * height); + buffer_size = 96; + + //factor_std = (1. - (1. / factor)); + //double factor2 = (factor * 1.); + //part = (int)((buffer_size / factor2) * (factor2 - 1)); + part = (int)(factor_std * buffer_size); + if (part < 0) part = 0; + else if (part >= buffer_size) part = buffer_size - 1; + std_dev_copy.resize(buffer_size); + last_added = 0; + count = 0; + max_im.resize(width * height); + std::fill_n(max_im.data(), max_im.size(), 0); + max_im_time = 0; + i = 0; + } + }; + + VideoDownsampler::VideoDownsampler() + :d_data(nullptr) + { + } + VideoDownsampler::~VideoDownsampler() + { + if(d_data) + delete d_data; + } + + bool VideoDownsampler::open(int width, int height, int lossy_height, int factor, double factor_std, callback_type callback) + { + if (width <= 0 || height <= 0 || lossy_height > height || factor < 1 || factor_std < 0 || factor_std > 1 || !callback) + return false; + + if (d_data) { + delete d_data; + d_data = nullptr; + } + d_data = new PrivateData(width, height, lossy_height, factor, factor_std, callback); + return true; + } + + int VideoDownsampler::close() + { + if (!d_data) + return 0; + int res = d_data->count; + delete d_data; + d_data = nullptr; + return res; + } + + bool VideoDownsampler::addImage2(const unsigned short* img, std::int64_t timestamp, const std::map& attributes) + { + if (!d_data) + return false; + + if (!d_data->timestamps.empty() && timestamp <= d_data->timestamps.back()) + return false; + + d_data->timestamps.push_back(timestamp); + + if (d_data->factor == 1) { + d_data->callback(img, timestamp, attributes); + d_data->i++; + d_data->count++; + return true; + } + + if (d_data->i == 0) { + //update prev image + memcpy(d_data->prev.data(), img, d_data->width * d_data->height * 2); + d_data->i++; + d_data->callback(img, timestamp, attributes); + d_data->last_added = 0; + d_data->count++; + return true; + } + + double current_std = std_dev_diff(img, d_data->prev.data(), d_data->width * d_data->lossy_height); + if (d_data->std_dev.size() < 10) { + d_data->std_dev.push_back(current_std); + // compute maximum image + max_image(d_data->max_im.data(), img, d_data->width, d_data->height, d_data->lossy_height); + d_data->max_im_time = timestamp; + d_data->attributes = attributes; + + if (d_data->i % d_data->factor == 0) { + // add image + d_data->callback(d_data->max_im.data(), d_data->max_im_time, d_data->attributes); + d_data->last_added = d_data->i; + d_data->count++; + //memcpy(d_data->last_saved.data(), img, d_data->width * d_data->height*2); + // reset max image + std::fill_n(d_data->max_im.data(), d_data->max_im.size(), 0); + } + //update prev image + memcpy(d_data->prev.data(), img, d_data->width * d_data->height * 2); + d_data->i++; + return true; + } + + auto tmp = mean_std(d_data->std_dev.data(), d_data->std_dev.size()); + double mean = tmp.first; + double std = tmp.second; + + // compute maximum image + max_image(d_data->max_im.data(), img, d_data->width, d_data->height, d_data->lossy_height); + d_data->max_im_time = timestamp; + d_data->attributes = attributes; + + double std_ratio = std / mean; + bool nothing = std_ratio < 0.1; + if (nothing) + nothing = nothing && (current_std < mean + 2*std); + //if (nothing == true && current_std > mean + 0.5 * std) + // printf("nothing %i\n", d_data->i); + + if (d_data->i % d_data->factor == 0 || (!nothing && current_std > mean + 0.5*std)) { + //add image + d_data->callback(d_data->max_im.data(), d_data->max_im_time, attributes); + d_data->last_added = d_data->i; + d_data->count++; + + // reset max image + std::fill_n(d_data->max_im.data(), d_data->max_im.size(), 0); + } + if ((current_std < mean + 5 * std && current_std > mean - std) || d_data->i % d_data->factor == 0) + { + if(d_data->std_dev.size() < d_data->buffer_size) + d_data->std_dev.push_back(current_std); + else { + // update array of standard deviation ONLY if current image is not completely different from previous one + memmove(d_data->std_dev.data(), d_data->std_dev.data() + 1, (d_data->std_dev.size() - 1) * sizeof(double)); + d_data->std_dev.back() = current_std; + } + } + + //update prev image + memcpy(d_data->prev.data(), img, d_data->width * d_data->height * 2); + d_data->i++; + return true; + } + + bool VideoDownsampler::addImage(const unsigned short* img, std::int64_t timestamp, const std::map& attributes) + { + if (!d_data) + return false; + + if (!d_data->timestamps.empty() && timestamp <= d_data->timestamps.back()) + return false; + + d_data->timestamps.push_back(timestamp); + + if (d_data->factor == 1) { + d_data->callback(img, timestamp, attributes); + d_data->i++; + d_data->count++; + return true; + } + + + if (d_data->std_dev.size() < d_data->buffer_size) { + + if (d_data->i > 0) { + // Add standardd eviation of difference of 2 consecutive images + double std = std_dev_diff(img, d_data->prev.data(), d_data->width * d_data->lossy_height); + d_data->std_dev.push_back(std); + } + + // compute maximum image + max_image(d_data->max_im.data(), img, d_data->width, d_data->height, d_data->lossy_height); + d_data->max_im_time = timestamp; + d_data->attributes = attributes; + + if (d_data->i % d_data->factor == 0) { + // add image + d_data->callback(d_data->max_im.data(), d_data->max_im_time, d_data->attributes); + d_data->last_added = d_data->i; + d_data->count++; + //memcpy(d_data->last_saved.data(), img, d_data->width * d_data->height*2); + // reset max image + std::fill_n(d_data->max_im.data(), d_data->max_im.size(), 0); + } + } + else { + + std::copy(d_data->std_dev.begin(), d_data->std_dev.end(), d_data->std_dev_copy.begin()); + std::nth_element(d_data->std_dev_copy.begin(), d_data->std_dev_copy.begin() + d_data->part, d_data->std_dev_copy.end()); + double val = d_data->std_dev_copy[d_data->part]; + + auto tmp = mean_std(d_data->std_dev.data(), d_data->std_dev.size()); + double mean = tmp.first; + double std = tmp.second; + + double current_std = std_dev_diff(img, d_data->prev.data(), d_data->width * d_data->lossy_height); + + double nothing_factor = 0.5; + bool nothing = current_std < tmp.first - nothing_factor *tmp.second; + if ((d_data->i - d_data->last_added) >= (d_data->factor * 2)) + nothing = false; + + // compute maximum image + max_image(d_data->max_im.data(), img, d_data->width, d_data->height, d_data->lossy_height); + d_data->max_im_time = timestamp; + + if ((current_std > val || (d_data->i - d_data->last_added) >= (d_data->factor)) && !nothing) { + // Something new that must be recorded + + //d_data->attributes = attributes; + + //add image + d_data->callback(d_data->max_im.data(), d_data->max_im_time, attributes); + d_data->last_added = d_data->i; + d_data->count++; + + // reset max image + std::fill_n(d_data->max_im.data(), d_data->max_im.size(), 0); + } + + + if ( current_std < mean + 10 * std) + { + // update array of standard deviation ONLY if current image is not completely different from previous one + memmove(d_data->std_dev.data(), d_data->std_dev.data() + 1, (d_data->std_dev.size() - 1) * sizeof(double)); + d_data->std_dev.back() = current_std; + } + } + + //update prev image + memcpy(d_data->prev.data(), img, d_data->width * d_data->height * 2); + d_data->i++; + return true; + } + + + + + + + + + + + + int init_libavcodec() { av_register_all(); diff --git a/src/cpp/video_io/h264.h b/src/cpp/video_io/h264.h index 7377010c..f92b3108 100644 --- a/src/cpp/video_io/h264.h +++ b/src/cpp/video_io/h264.h @@ -1,11 +1,31 @@ #pragma once +#include + #include "IRVideoLoader.h" #include "FileAttributes.h" namespace rir { + class IO_EXPORT VideoDownsampler + { + public: + using callback_type = std::function&)>; + + VideoDownsampler(); + ~VideoDownsampler(); + + bool open(int width, int height, int lossy_height, int factor, double factor_std, callback_type callback); + int close(); + + bool addImage(const unsigned short* img, std::int64_t timestamp, const std::map& attributes); + bool addImage2(const unsigned short* img, std::int64_t timestamp, const std::map& attributes); + private: + class PrivateData; + PrivateData* d_data; + }; + /// @brief Class implementing lossless/lossy compression of infrared videos as described in article []. /// /// H264_Saver class is used to generate compressed IR video files with additional attributes. @@ -186,6 +206,7 @@ namespace rir /// @brief Reimplemented from IRVideoLoader virtual BaseCalibration *calibration() const { return NULL; } + virtual bool setCalibration(BaseCalibration *calibration) { return false; } /// @brief Reimplemented from IRVideoLoader virtual bool getRawValue(int x, int y, unsigned short *value) const; /// @brief Reimplemented from IRVideoLoader diff --git a/src/cpp/video_io/video_io.cpp b/src/cpp/video_io/video_io.cpp index 991c75d5..931a63c8 100644 --- a/src/cpp/video_io/video_io.cpp +++ b/src/cpp/video_io/video_io.cpp @@ -7,7 +7,6 @@ extern "C" #include "Log.h" #include "IRFileLoader.h" #include "h264.h" -#include "ZFile.h" using namespace rir; @@ -225,7 +224,7 @@ int flip_camera_calibration(int camera, int flip_rl, int flip_ud) IRVideoLoader *cam = (IRVideoLoader *)c; BaseCalibration *full = cam->calibration(); if (!full) - return -1; + return -2; Size s = cam->imageSize(); if (s.width == 640) { @@ -319,84 +318,6 @@ int support_emissivity(int cam) return 0;*/ } -int set_optical_temperature(int cam, unsigned short temp_C) -{ - int ok = support_optical_temperature(cam); - if (!ok) - return -1; - - void *camera = get_void_ptr(cam); - IRVideoLoader *l = static_cast(camera); - if (!l) - { - logError("set_optical_temperature: NULL camera"); - return -1; - } - - l->setOpticaltemperature(temp_C); - return 0; -} -unsigned short get_optical_temperature(int cam) -{ - void *camera = get_void_ptr(cam); - IRVideoLoader *l = static_cast(camera); - if (!l) - { - logError("get_optical_temperature: NULL camera"); - return -1; - } - return l->opticalTemperature(); -} - -int set_STEFI_temperature(int cam, unsigned short temp_C) -{ - int ok = support_optical_temperature(cam); - if (!ok) - return -1; - - void *camera = get_void_ptr(cam); - IRVideoLoader *l = static_cast(camera); - if (!l) - { - logError("set_STEFI_temperature: NULL camera"); - return -1; - } - - l->setSTEFItemperature(temp_C); - return 0; -} -unsigned short get_STEFI_temperature(int cam) -{ - void *camera = get_void_ptr(cam); - IRVideoLoader *l = static_cast(camera); - if (!l) - { - logError("get_STEFI_temperature: NULL camera"); - return -1; - } - return l->STEFITemperature(); -} - -int support_optical_temperature(int cam) -{ - void *camera = get_void_ptr(cam); - IRVideoLoader *l = static_cast(camera); - if (!l || !l->calibration()) - { - return 0; - } - return (l->calibration()->supportedFeatures() & BaseCalibration::SupportOpticalTemperature) ? 1 : 0; - /*if (strcmp(l->typeName(), "IRLoader") != 0) - { - return 1; - } - IRLoader* ir = static_cast(l); - std::string id = ir->identifier(); - if (id.find("CEA") != std::string::npos) - return 0; - return 1;*/ -} - int load_image(int cam, int pos, int calibration, unsigned short *data) { void *camera = get_void_ptr(cam); @@ -911,28 +832,6 @@ int get_table(int cam, const char *name, float *dst, int *dst_size) return 0; } -int open_video_write(const char *filename, int width, int height, int rate, int method, int clevel) -{ - return set_void_ptr(z_open_file_write(filename, width, height, rate, method, clevel)); -} -int image_write(int writter, unsigned short *img, int64_t time) -{ - void *w = get_void_ptr(writter); - if (w) - return z_write_image(w, img, time); - return -1; -} -int64_t close_video(int writter) -{ - void *w = get_void_ptr(writter); - if (w) - { - return z_close_file(w); - rm_void_ptr(writter); - } - return 0; -} - int get_last_image_raw_value(int cam, int x, int y, unsigned short *value) { void *camera = get_void_ptr(cam); diff --git a/src/labview/CMakeLists.txt b/src/labview/CMakeLists.txt new file mode 100644 index 00000000..52806870 --- /dev/null +++ b/src/labview/CMakeLists.txt @@ -0,0 +1 @@ +# TODO: some packaging to make it installable \ No newline at end of file diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt new file mode 100644 index 00000000..ef06c191 --- /dev/null +++ b/src/python/CMakeLists.txt @@ -0,0 +1,72 @@ + + + + + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME} + DESTINATION ${CMAKE_INSTALL_PREFIX}/python + FILES_MATCHING PATTERN "*.py" +) + +install(DIRECTORY $${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/LICENSES + DESTINATION ${CMAKE_INSTALL_PREFIX}/python/ + FILES_MATCHING PATTERN "*.txt" +) + +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/LICENSE + DESTINATION ${CMAKE_INSTALL_PREFIX}/python/${PROJECT_NAME} +) + +# install(TARGETS ${LIBRIR_TARGETS} RUNTIME +# DESTINATION ${CMAKE_INSTALL_PREFIX}/python/${PROJECT_NAME}/libs +# ) +install(CODE "set(FFMPEG_LIB_DIR \"${FFMPEG_LIB_DIR}\")") +install(CODE "set(FFMPEG_INCLUDE_DIR \"${FFMPEG_INCLUDE_DIR}\")") +install(CODE "set(FFMPEG_LIBS \"${FFMPEG_LIBS}\")") +install(CODE "set(ADDITIONAL_FFMPEG_LIBS \"${ADDITIONAL_FFMPEG_LIBS}\")") +install(CODE "set(FFMPEG_PREFIX \"${FFMPEG_PREFIX}\")") +install(CODE "set(FFMPEG_SUFFIX \"${FFMPEG_SUFFIX}\")") +install(CODE "set(COMPLETE_FFMPEG_LIBS \"${COMPLETE_FFMPEG_LIBS}\")") +install(CODE "set(FFMPEG_INSTALL_DIR \"${CMAKE_INSTALL_PREFIX}/python/${PROJECT_NAME}/libs\")") +install(SCRIPT ../cpp/video_io/InstallFfmpeg.cmake) + +install(IMPORTED_RUNTIME_ARTIFACTS ${LIBRIR_TARGETS} DESTINATION ${CMAKE_INSTALL_PREFIX}/python/${PROJECT_NAME}/libs) + +if(MSVC OR WIN32) +set(SHARED_LIBRARY_SUFFIX_PATTERN "*.dll") +else() +set(SHARED_LIBRARY_SUFFIX_PATTERN "*.so*") +endif() +configure_file(pyproject.toml.in pyproject.toml @ONLY) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pyproject.toml ${PROJECT_SOURCE_DIR}/README.md + DESTINATION ${CMAKE_INSTALL_PREFIX}/python +) + +# setting version in __init__.py + +function(cat IN_FILE OUT_FILE) + file(READ ${IN_FILE} CONTENTS) + file(APPEND ${OUT_FILE} "${CONTENTS}") +endfunction() + +set(INIT_PY_FILES) +file(REMOVE __init__.py.in) +file(WRITE __init__.py.in "") + +cat( ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/__init__.py __init__.py.in) +file(APPEND __init__.py.in "\n__version__ = \"@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@\"") + +configure_file(__init__.py.in __init__.py @ONLY) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/__init__.py DESTINATION ${CMAKE_INSTALL_PREFIX}/python/${PROJECT_NAME}/) +FILE(REMOVE __init__.py.in) + +# for development purposes +install(IMPORTED_RUNTIME_ARTIFACTS ${LIBRIR_TARGETS} + DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/libs +) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pyproject.toml ${PROJECT_SOURCE_DIR}/README.md + DESTINATION ${CMAKE_CURRENT_SOURCE_DIR} +) +install(CODE "set(FFMPEG_INSTALL_DIR \"${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/libs\")") +install(SCRIPT ../cpp/video_io/InstallFfmpeg.cmake) \ No newline at end of file diff --git a/src/python/librir/__init__.py b/src/python/librir/__init__.py index 8cac1298..3eebe0b8 100644 --- a/src/python/librir/__init__.py +++ b/src/python/librir/__init__.py @@ -13,37 +13,3 @@ logging.basicConfig() logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) - -# __all__ = [ -# "rir_tools", -# "rir_signal_processing", -# "rir_geometry", -# "rir_video_io", -# ] - -# import importlib -# import pkgutil - -# from . import plugins - -# # import sys -# # if sys.version_info < (3, 10): -# # from importlib_metadata import entry_points -# # else: -# # from importlib.metadata import entry_points - - -# # discovered_plugins = entry_points(group='plugins') - -# def iter_namespace(ns_pkg): -# # Specifying the second argument (prefix) to iter_modules makes the -# # returned name an absolute name instead of a relative one. This allows -# # import_module to work without having to do additional modification to -# # the name. -# return pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + ".") - -# discovered_plugins = { -# name: importlib.import_module(name) -# for finder, name, ispkg -# in iter_namespace(plugins) -# } diff --git a/src/python/librir/registration/compute_registration.py b/src/python/librir/registration/compute_registration.py deleted file mode 100644 index d7f22987..00000000 --- a/src/python/librir/registration/compute_registration.py +++ /dev/null @@ -1,329 +0,0 @@ -""" -Ce fichier est le fichier qui permet d'avoir les estimations du deplacement -des pixels par l'algorithme masked_registrator_ecc. -""" - -########################### - -import os -from librir.video_io import IRMovie -import numpy as np -import pandas as pd -import cv2 -import librir - -# from librir.west.WESTIRMovie import WESTIRMovie -from .masked_registration_ecc import MaskedRegistratorECC - -########################### - - -def manage_computation_and_tries(img, regis_obj: MaskedRegistratorECC): - """ - Fonction qui calcule les deplacements en x, en y et le niveau de - confiance. Si l'algorithme ne parvient pas a converger pour une image, - il essaie 4 autres fois en baissant la mediane a chaque essai de 0.01. - Si au bout de 5 essais, il n'a toujours pas reussit a converger, il prend - le deplacement en x, en y et le niveau de confiance de l'image precedente. - """ - nb_try = 0 - max_try = 5 - compute = False - - while nb_try < max_try and not compute: - try: - regis_obj.compute(img) - compute = True - if regis_obj.check_median_value(1): - regis_obj.define_median_value(1) - except cv2.error: - regis_obj.decrease_median(0.01) - nb_try += 1 - if nb_try > 0: - print("try number : {}".format(nb_try)) - - if nb_try >= max_try: - regis_obj.append_last_coordinates_and_confidence() - print("took previous estimates.") - - return regis_obj - - -def compute_confidence_and_x_y_trajectories( - ir_movie: IRMovie, view, lower_bound, upper_bound, calibration, *args -): - """ - Fonction qui permet d'estimer les deplacements (x,y) des pixels - par rapport a une image de reference pour tout un choc. - """ - if view.startswith("DIV"): - is_div = True - else: - is_div = False - - for img_number in range(lower_bound, upper_bound + 1): - if img_number % 50 == 0: - print(img_number) - img = ir_movie.load_pos(img_number, calibration) - - if is_div: - regis_obj_up = manage_computation_and_tries(img, args[0]) - regis_obj_down = manage_computation_and_tries(img, args[1]) - else: - regis_obj = manage_computation_and_tries(img, args[0]) - - if is_div: - return regis_obj_up, regis_obj_down - return regis_obj - - -def correct_view(nb_pulse, view): - """ - Fonction qui permet de donner le bon nom a la visee qui se - situe en dessous d'un certain numero de choc. - """ - if nb_pulse > 55995: - return view - elif view == "ICRQ1B": - return "ICR2Q1B" - elif view == "ICRQ2B": - return "ICR1Q2B" - elif view == "ICRQ4A": - return "ICR3Q4A" - else: - return view - - -def load_mask(view, path, modif): - """ - Fonction qui permet de charger le masque associe - a la visee actuellement etudiee. - """ - if view == "WAQ5B": - mask = np.loadtxt(os.path.join(path, "masks", "WAQ5B.txt")) - # mask = np.rot90(mask, k=3).copy() - elif view == "DIVQ1B": - mask = np.loadtxt(os.path.join(path, "masks", "DIVQ1B.txt")) - elif view == "DIVQ6A": - mask = np.loadtxt(os.path.join(path, "masks", "DIVQ6A.txt")) - elif view == "DIVQ4A": - mask = np.loadtxt(os.path.join(path, "masks", "DIVQ4A.txt")) - elif view == "DIVQ2B": - mask = np.loadtxt(os.path.join(path, "masks", "DIVQ2B.txt")) - elif view == "DIVQ6B": - mask = np.loadtxt(os.path.join(path, "masks", "DIVQ6B.txt")) - elif view == "ICRQ1B": - mask = np.loadtxt(os.path.join(path, "masks", "ICRQ1B.txt")) - elif view == "ICRQ2B": - mask = np.loadtxt(os.path.join(path, "masks", "ICRQ2B.txt")) - elif view == "ICRQ4A": - mask = np.loadtxt(os.path.join(path, "masks", "ICRQ4A.txt")) - elif view == "LH1Q6A": - mask = np.loadtxt(os.path.join(path, "masks", "LH1Q6A.txt")) - elif view == "LH2Q6B": - mask = np.loadtxt(os.path.join(path, "masks", "LH2Q6B.txt")) - else: - mask = np.loadtxt(os.path.join(path, "masks", "HRQ3B.txt")) - - mask = modif(np.array(mask, np.uint8)).copy() - return mask - - -def modify_img_wa(img): - """ - Fonction qui modifie l'image etudiee lorsque - l'image est issue du grand angle. - """ - return np.rot90(img[0:512, :]).copy() - - -def modify_img_divup(img): - """ - Fonction qui modifie l'image etudiee lorsque - l'image est issue du divertor haut. - """ - return img[0 : int(512 / 2), :] - - -def modify_img_divdown(img): - """ - Fonction qui modifie l'image etudiee lorsque - l'image est issue du divertor bas. - """ - return img[int(512 / 2) : 512, :] - - -def modify_img(img): - """ - Fonction qui modifie l'image etudiee lorsque - la visee est differente du divertor ou le grand angle. - """ - return img[0:512, :] - - -def save_results(lower_bound, upper_bound, filename, *args): - """ - Fonction qui permet de sauvegarder les listes contenant l'estimation - du deplacement des pixels dans des fichiers au format .csv. - """ - if len(args) > 1: - tab_up = args[0].return_coordinates_and_confidence_values() - tab_down = args[1].return_coordinates_and_confidence_values() - - concat = np.concatenate((tab_up, tab_down), axis=1) - - stab_data = pd.DataFrame( - data=concat, - index=np.arange(lower_bound, upper_bound + 1), - columns=[ - "x-axis translations up", - "y-axis translations up", - "Confidence level up", - "x axis translations down", - "y axis translations down", - "Confidence level down", - ], - ) - else: - tab_other_view = args[0].return_coordinates_and_confidence_values() - - stab_data = pd.DataFrame( - data=tab_other_view, - index=np.arange(lower_bound, upper_bound + 1), - columns=["x-axis translations", "y-axis translations", "Confidence level"], - ) - - stab_data.to_csv(filename, sep="\t") - - -def cam_to_file(cam, viewname, lower_bound, path): - """ - Fonction qui permet, a partir d'un nom de camera - de creer le fichier permettant de stabiliser un - film IR de WEST - """ - - med = 1 - - # open video - cam.enable_bad_pixels = True - - upper_bound = cam.images - 1 - - if viewname.startswith("DIV"): - modify_up = modify_img_divup - modify_down = modify_img_divdown - - mask_img_up = load_mask(viewname, os.path.dirname(librir.__file__), modify_up) - mask_img_down = load_mask( - viewname, os.path.dirname(librir.__file__), modify_down - ) - - regis_obj_up = MaskedRegistratorECC( - window_factorh=1, - window_factorv=1, - sigma=0.5, - mask=mask_img_up, - median=med, - pre_process=modify_up, - view=viewname, - ) - - regis_obj_down = MaskedRegistratorECC( - window_factorh=1, - window_factorv=1, - sigma=0.5, - mask=mask_img_down, - median=med, - pre_process=modify_down, - view=viewname, - ) - - regis_obj_up.start(cam.load_pos(lower_bound, 1)) - regis_obj_down.start(cam.load_pos(lower_bound, 1)) - - regis_obj_up, regis_obj_down = compute_confidence_and_x_y_trajectories( - cam, viewname, lower_bound + 1, upper_bound, 1, regis_obj_up, regis_obj_down - ) - - save_results(lower_bound, upper_bound, path, regis_obj_up, regis_obj_down) - - # Delete camera and registrator object - del cam, regis_obj_up, regis_obj_down - else: - modify = modify_img - - mask_img = load_mask(viewname, os.path.dirname(librir.__file__), modify) - - regis_obj = MaskedRegistratorECC( - window_factorh=1, - window_factorv=1, - sigma=0.5, - mask=mask_img, - median=med, - pre_process=modify, - view=viewname, - ) - - regis_obj.start(cam.load_pos(lower_bound, 1)) - - regis_obj = compute_confidence_and_x_y_trajectories( - cam, viewname, lower_bound + 1, upper_bound, 1, regis_obj - ) - - save_results(lower_bound, upper_bound, path, regis_obj) - - # Delete camera and masked_registrator_ECC object - del cam, regis_obj - - -def compute_registration_ir(view_name, pulse_or_filename, outfile): - """ - Fonction qui charge un film IR de WEST et appelle la fonction - qui permet de sauvegarder le fichier de stabilisation. - """ - import librir - - print(librir.__file__) - import librir.west as west - import librir.video_io as video - - view = view_name - pulse = 0 - filename = str() - try: - pulse = int(pulse_or_filename) - except ValueError: - filename = pulse_or_filename - outfilename = outfile - - # cam = None - if pulse > 0: - cam = west.WESTIRMovie(pulse, view) - else: - cam = video.IRMovie.from_filename(filename) - - cam_to_file(cam, view, 0, outfilename) - - -########################## - - -if __name__ == "__main__": - # Arguments: view_name pulse_or_file out_file - import sys - - if len(sys.argv) != 4: - raise RuntimeError("wrong arguments (expected 3)") - - compute_registration_ir(str(sys.argv[1]), sys.argv[2], sys.argv[3]) - - # PULSES = np.arange(55555, 55556) - - # CAMERA = 'HR' - - # FIRST_IMG_STUDIED = 0 - - # PATH_TO_STABILISATION_FOLDER = r"path\to\your\stabilisation\folder" - - # from_pulses_to_csv_files(PULSES, CAMERA, FIRST_IMG_STUDIED, PATH_TO_STABILISATION_FOLDER) diff --git a/src/python/librir/registration/masked_registration_ecc.py b/src/python/librir/registration/masked_registration_ecc.py index 75029a9a..9868f2a7 100644 --- a/src/python/librir/registration/masked_registration_ecc.py +++ b/src/python/librir/registration/masked_registration_ecc.py @@ -12,6 +12,7 @@ ) import numpy as np import cv2 +import pandas as pd ######################## @@ -189,20 +190,6 @@ def compute(self, img): return shift - def decrease_median(self, value_of_decrease): - self.median -= value_of_decrease - return self.median - - def check_median_value(self, upper_value): - if self.median < upper_value: - return True - else: - return False - - def define_median_value(self, defined_value): - self.median = defined_value - return self.median - def append_last_coordinates_and_confidence(self): self.x.append(self.x[-1]) self.y.append(self.y[-1]) @@ -210,3 +197,49 @@ def append_last_coordinates_and_confidence(self): def return_coordinates_and_confidence_values(self): return np.array([self.x, self.y, self.confidences]).T + + @property + def stabilisation_data(self): + arr = self.return_coordinates_and_confidence_values() + stab_data = pd.DataFrame( + data=arr, + columns=[ + "x-axis translations", + "y-axis translations", + "Confidence level", + ], + ) + return stab_data + + def to_reg_file(self, dest_file): + self.stabilisation_data.to_csv(dest_file, sep="\t") + + +def manage_computation_and_tries(img, regis_obj: MaskedRegistratorECC): + """ + Fonction qui calcule les deplacements en x, en y et le niveau de + confiance. Si l'algorithme ne parvient pas a converger pour une image, + il essaie 4 autres fois en baissant la mediane a chaque essai de 0.01. + Si au bout de 5 essais, il n'a toujours pas reussit a converger, il prend + le deplacement en x, en y et le niveau de confiance de l'image precedente. + """ + nb_try = 0 + max_try = 5 + compute = False + + while nb_try < max_try and not compute: + try: + regis_obj.compute(img) + compute = True + regis_obj.median = 1 if regis_obj.median < 1 else regis_obj.median + except cv2.error: + regis_obj.median -= 0.01 + nb_try += 1 + if nb_try > 0: + print("try number : {}".format(nb_try)) + + if nb_try >= max_try: + regis_obj.append_last_coordinates_and_confidence() + print("took previous estimates.") + + return regis_obj diff --git a/src/python/librir/registration/registration_thermavip.py b/src/python/librir/registration/registration_thermavip.py deleted file mode 100644 index 4f94040c..00000000 --- a/src/python/librir/registration/registration_thermavip.py +++ /dev/null @@ -1,21 +0,0 @@ -if __name__ == "__main__": - import sys - - sys.path.insert(0, sys.argv[1]) - import librir - import os - - print(sys.path) - print(os.path.abspath(librir.__file__)) - print(os.path.abspath(librir.__file__)) - import librir.west - import librir.registration - from librir.registration.compute_registration import compute_registration_ir - - # Arguments: view_name pulse_or_file out_file - import sys - - if len(sys.argv) != 5: - raise RuntimeError("wrong arguments (expected 3)") - - compute_registration_ir(str(sys.argv[2]), sys.argv[3], sys.argv[4]) diff --git a/src/python/librir/signal_processing/rir_signal_processing.py b/src/python/librir/signal_processing/rir_signal_processing.py index cfd3f6c2..66a08ec6 100644 --- a/src/python/librir/signal_processing/rir_signal_processing.py +++ b/src/python/librir/signal_processing/rir_signal_processing.py @@ -5,6 +5,20 @@ from ..low_level.misc import _signal_processing, toCharP +_DTYPES = { + np.dtype(np.bool_): "?", + np.dtype(np.int8): "b", + np.dtype(np.uint8): "B", + np.dtype(np.int16): "h", + np.dtype(np.uint16): "H", + np.dtype(np.int32): "i", + np.dtype(np.uint32): "I", + np.dtype(np.int64): "l", + np.dtype(np.int64): "L", + np.dtype(np.float32): "f", + np.dtype(np.float64): "d", +} + def translate(image, dx, dy, strategy=str(), background=None): """ @@ -48,141 +62,20 @@ def translate(image, dx, dy, strategy=str(), background=None): _tr[0] = dx _tr[1] = dy back = _back.ctypes.data_as(ct.c_void_p) - - if image.dtype == np.bool_: - r = _signal_processing.translate( - ord("?"), - src, - dst, - img.shape[1], - img.shape[0], - _tr[0], - _tr[1], - back, - strategy, - ) - elif image.dtype == np.int8: - r = _signal_processing.translate( - ord("b"), - src, - dst, - img.shape[1], - img.shape[0], - _tr[0], - _tr[1], - back, - strategy, - ) - elif image.dtype == np.uint8: - r = _signal_processing.translate( - ord("B"), - src, - dst, - img.shape[1], - img.shape[0], - _tr[0], - _tr[1], - back, - strategy, - ) - elif image.dtype == np.int16: - r = _signal_processing.translate( - ord("h"), - src, - dst, - img.shape[1], - img.shape[0], - _tr[0], - _tr[1], - back, - strategy, - ) - elif image.dtype == np.uint16: - r = _signal_processing.translate( - ord("H"), - src, - dst, - img.shape[1], - img.shape[0], - _tr[0], - _tr[1], - back, - strategy, - ) - elif image.dtype == np.int32: - r = _signal_processing.translate( - ord("i"), - src, - dst, - img.shape[1], - img.shape[0], - _tr[0], - _tr[1], - back, - strategy, - ) - elif image.dtype == np.uint32: - r = _signal_processing.translate( - ord("I"), - src, - dst, - img.shape[1], - img.shape[0], - _tr[0], - _tr[1], - back, - strategy, - ) - elif image.dtype == np.int64: - r = _signal_processing.translate( - ord("l"), - src, - dst, - img.shape[1], - img.shape[0], - _tr[0], - _tr[1], - back, - strategy, - ) - elif image.dtype == np.int64: - r = _signal_processing.translate( - ord("L"), - src, - dst, - img.shape[1], - img.shape[0], - _tr[0], - _tr[1], - back, - strategy, - ) - elif image.dtype == np.float32: - r = _signal_processing.translate( - ord("f"), - src, - dst, - img.shape[1], - img.shape[0], - _tr[0], - _tr[1], - back, - strategy, - ) - elif image.dtype == np.float64: - r = _signal_processing.translate( - ord("d"), - src, - dst, - img.shape[1], - img.shape[0], - _tr[0], - _tr[1], - back, - strategy, - ) - else: + _dtype = _DTYPES.get(image.dtype, None) + if _dtype is None: raise RuntimeError("An error occured while calling 'translate'") + r = _signal_processing.translate( + ord(_dtype), + src, + dst, + img.shape[1], + img.shape[0], + _tr[0], + _tr[1], + back, + strategy, + ) if r < 0: raise RuntimeError("An error occured while calling 'translate'") @@ -487,7 +380,7 @@ def jpegls_decode(buff, width, height): return image -def label_image(image, background_value=0): +def label_image(image: np.ndarray, background_value=0): """ Closed Component Labelling algorithm Returns a tuple (image,areas, first_points), each index of the list corresponding @@ -526,52 +419,12 @@ def label_image(image, background_value=0): dareas = areas.ctypes.data_as(ct.POINTER(ct.c_int)) dxy = xy.ctypes.data_as(ct.POINTER(ct.c_double)) - if image.dtype == np.bool_: - r = _signal_processing.label_image( - ord("?"), src, dst, img.shape[1], img.shape[0], back, dxy, dareas - ) - elif image.dtype == np.int8: - r = _signal_processing.label_image( - ord("b"), src, dst, img.shape[1], img.shape[0], back, dxy, dareas - ) - elif image.dtype == np.uint8: - r = _signal_processing.label_image( - ord("B"), src, dst, img.shape[1], img.shape[0], back, dxy, dareas - ) - elif image.dtype == np.int16: - r = _signal_processing.label_image( - ord("h"), src, dst, img.shape[1], img.shape[0], back, dxy, dareas - ) - elif image.dtype == np.uint16: - r = _signal_processing.label_image( - ord("H"), src, dst, img.shape[1], img.shape[0], back, dxy, dareas - ) - elif image.dtype == np.int32: - r = _signal_processing.label_image( - ord("i"), src, dst, img.shape[1], img.shape[0], back, dxy, dareas - ) - elif image.dtype == np.uint32: - r = _signal_processing.label_image( - ord("I"), src, dst, img.shape[1], img.shape[0], back, dxy, dareas - ) - elif image.dtype == np.int64: - r = _signal_processing.label_image( - ord("l"), src, dst, img.shape[1], img.shape[0], back, dxy, dareas - ) - elif image.dtype == np.int64: - r = _signal_processing.label_image( - ord("L"), src, dst, img.shape[1], img.shape[0], back, dxy, dareas - ) - elif image.dtype == np.float32: - r = _signal_processing.label_image( - ord("f"), src, dst, img.shape[1], img.shape[0], back, dxy, dareas - ) - elif image.dtype == np.float64: - r = _signal_processing.label_image( - ord("d"), src, dst, img.shape[1], img.shape[0], back, dxy, dareas - ) - else: + _dtype = _DTYPES.get(image.dtype, None) + if _dtype is None: raise RuntimeError("An error occured while calling 'label_image'") + r = _signal_processing.label_image( + ord(_dtype), src, dst, img.shape[1], img.shape[0], back, dxy, dareas + ) if r < 0: raise RuntimeError("An error occured while calling 'label_image'") @@ -613,52 +466,12 @@ def keep_largest_area(image, background_value=0, foreground_value=1): dst = res.ctypes.data_as(ct.POINTER(ct.c_int)) back = background.ctypes.data_as(ct.c_void_p) - if image.dtype == np.bool_: - r = _signal_processing.keep_largest_area( - ord("?"), src, dst, img.shape[1], img.shape[0], back, foreground_value - ) - elif image.dtype == np.int8: - r = _signal_processing.keep_largest_area( - ord("b"), src, dst, img.shape[1], img.shape[0], back, foreground_value - ) - elif image.dtype == np.uint8: - r = _signal_processing.keep_largest_area( - ord("B"), src, dst, img.shape[1], img.shape[0], back, foreground_value - ) - elif image.dtype == np.int16: - r = _signal_processing.keep_largest_area( - ord("h"), src, dst, img.shape[1], img.shape[0], back, foreground_value - ) - elif image.dtype == np.uint16: - r = _signal_processing.keep_largest_area( - ord("H"), src, dst, img.shape[1], img.shape[0], back, foreground_value - ) - elif image.dtype == np.int32: - r = _signal_processing.keep_largest_area( - ord("i"), src, dst, img.shape[1], img.shape[0], back, foreground_value - ) - elif image.dtype == np.uint32: - r = _signal_processing.keep_largest_area( - ord("I"), src, dst, img.shape[1], img.shape[0], back, foreground_value - ) - elif image.dtype == np.int64: - r = _signal_processing.keep_largest_area( - ord("l"), src, dst, img.shape[1], img.shape[0], back, foreground_value - ) - elif image.dtype == np.int64: - r = _signal_processing.keep_largest_area( - ord("L"), src, dst, img.shape[1], img.shape[0], back, foreground_value - ) - elif image.dtype == np.float32: - r = _signal_processing.keep_largest_area( - ord("f"), src, dst, img.shape[1], img.shape[0], back, foreground_value - ) - elif image.dtype == np.float64: - r = _signal_processing.keep_largest_area( - ord("d"), src, dst, img.shape[1], img.shape[0], back, foreground_value - ) - else: + _dtype = _DTYPES.get(image.dtype, None) + if _dtype is None: raise RuntimeError("An error occured while calling 'keep_largest_area'") + r = _signal_processing.keep_largest_area( + ord(_dtype), src, dst, img.shape[1], img.shape[0], back, foreground_value + ) if r < 0: raise RuntimeError("An error occured while calling 'keep_largest_area'") diff --git a/src/python/librir/tools/__init__.py b/src/python/librir/tools/__init__.py index b2e9297e..e02bffdf 100644 --- a/src/python/librir/tools/__init__.py +++ b/src/python/librir/tools/__init__.py @@ -1,8 +1,3 @@ -import os -import sys - -sys.path.insert(1, os.path.realpath(os.path.pardir)) - # import useful functions from rir_tools from .rir_tools import ( zstd_compress_bound, @@ -15,6 +10,8 @@ BLOSC_BITSHUFFLE, ) +from . import _thermavip + __all__ = [ "zstd_compress_bound", "zstd_compress", @@ -24,4 +21,5 @@ "BLOSC_NOSHUFFLE", "BLOSC_SHUFFLE", "BLOSC_BITSHUFFLE", + "_thermavip", ] diff --git a/src/python/librir/tools/_thermavip.py b/src/python/librir/tools/_thermavip.py new file mode 100644 index 00000000..2764b473 --- /dev/null +++ b/src/python/librir/tools/_thermavip.py @@ -0,0 +1,77 @@ +import logging +import os +from subprocess import PIPE, Popen, check_output, CalledProcessError + +logging.basicConfig() +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + + +def get_pid_windows(app_name): + final_list = [] + command = Popen( + ["tasklist", "/FI", f"IMAGENAME eq {app_name}", "/fo", "CSV"], + stdout=PIPE, + shell=False, + ) + msg = command.communicate() + output = str(msg[0]) + if "INFO" not in output: + output_list = output.split(app_name) + for i in range(1, len(output_list)): + j = int(output_list[i].replace('"', "")[1:].split(",")[0]) + if j not in final_list: + final_list.append(j) + + return final_list + + +def is_thermavip_opened(): + names = ["Thermavip.exe", "Thermavip", "thermavip"] + processes = [] + for n in names: + try: + processes = get_pid_of(n) + except CalledProcessError: + continue + if processes: + break + return any(processes) + + +def get_pid_of(name): + strategy = {"nt": get_pid_windows, "posix": get_pid_unix} + return strategy[os.name](name) + + +def get_pid_unix(name): + return check_output(["pidof", name]) + + +def init_thermavip(th_instance="Thermavip-1"): + if not is_thermavip_opened(): + logger.error("Thermavip couldn't be found") + return + + try: + import Thermavip as th + except ImportError as e: + logger.error("Thermavip Python module couldn't be imported") + logger.error(e) + else: + th.setSharedMemoryName(th_instance) + return th + + +def unbind_thermavip_shared_mem(th): + if th is not None: + th._SharedMemory.thread.stopth = True + + +def thermavip(func, *args, **kwargs): + def wrapper(*args, **kwargs): + th = init_thermavip() + func(*args, **kwargs) + unbind_thermavip_shared_mem(th) + + return wrapper diff --git a/src/python/librir/tools/utils.py b/src/python/librir/tools/utils.py deleted file mode 100644 index 9f2e7490..00000000 --- a/src/python/librir/tools/utils.py +++ /dev/null @@ -1,184 +0,0 @@ -import logging -import os -from subprocess import PIPE, Popen, check_output, CalledProcessError - - -import functools -import inspect -import warnings - -logging.basicConfig() -logger = logging.getLogger(__name__) -logger.setLevel(logging.INFO) - -string_types = (type(b""), type("")) - - -def deprecated(reason): - """ - This is a decorator which can be used to mark functions - as deprecated. It will result in a warning being emitted - when the function is used. - """ - - if isinstance(reason, string_types): - # The @deprecated is used with a 'reason'. - # - # .. code-block:: python - # - # @deprecated("please, use another function") - # def old_function(x, y): - # pass - - def decorator(func1): - if inspect.isclass(func1): - fmt1 = "Call to deprecated class {name} ({reason})." - else: - fmt1 = "Call to deprecated function {name} ({reason})." - - @functools.wraps(func1) - def new_func1(*args, **kwargs): - warnings.simplefilter("always", DeprecationWarning) - warnings.warn( - fmt1.format(name=func1.__name__, reason=reason), - category=DeprecationWarning, - stacklevel=2, - ) - warnings.simplefilter("default", DeprecationWarning) - return func1(*args, **kwargs) - - return new_func1 - - return decorator - - elif inspect.isclass(reason) or inspect.isfunction(reason): - # The @deprecated is used without any 'reason'. - # - # .. code-block:: python - # - # @deprecated - # def old_function(x, y): - # pass - - func2 = reason - - if inspect.isclass(func2): - fmt2 = "Call to deprecated class {name}." - else: - fmt2 = "Call to deprecated function {name}." - - @functools.wraps(func2) - def new_func2(*args, **kwargs): - warnings.simplefilter("always", DeprecationWarning) - warnings.warn( - fmt2.format(name=func2.__name__), - category=DeprecationWarning, - stacklevel=2, - ) - warnings.simplefilter("default", DeprecationWarning) - return func2(*args, **kwargs) - - return new_func2 - - else: - raise TypeError(repr(type(reason))) - - -def is_thermavip_opened(): - names = ["Thermavip.exe", "Thermavip", "thermavip"] - processes = [] - for n in names: - try: - processes = get_pid_of(n) - except CalledProcessError: - continue - if processes: - break - return any(processes) - - -def get_pid_of(name): - strategy = {"nt": get_pid_windows, "posix": get_pid_unix} - return strategy[os.name](name) - - -def get_pid_unix(name): - return check_output(["pidof", name]) - - -def init_thermavip(th_instance="Thermavip-1"): - if not is_thermavip_opened(): - logger.error("Thermavip couldn't be found") - return - - try: - import Thermavip as th - except ImportError as e: - logger.error(f"Thermavip Python module couldn't be imported") - logger.error(e) - else: - th.setSharedMemoryName(th_instance) - return th - - -def unbind_thermavip_shared_mem(th): - if th is not None: - th._SharedMemory.thread.stopth = True - - -def thermavip(func, *args, **kwargs): - def wrapper(*args, **kwargs): - th = init_thermavip() - func(*args, **kwargs) - unbind_thermavip_shared_mem(th) - - return wrapper - - -def get_pid_windows(app_name): - final_list = [] - command = Popen( - ["tasklist", "/FI", f"IMAGENAME eq {app_name}", "/fo", "CSV"], - stdout=PIPE, - shell=False, - ) - msg = command.communicate() - output = str(msg[0]) - if "INFO" not in output: - output_list = output.split(app_name) - for i in range(1, len(output_list)): - j = int(output_list[i].replace('"', "")[1:].split(",")[0]) - if j not in final_list: - final_list.append(j) - - return final_list - - -def remove_quotes(original: dict): - d = original.copy() - for key, value in d.items(): - if isinstance(value, str): - d[key] = remove_quotes_string(d[key]) - - if isinstance(value, dict): - d[key] = remove_quotes(value) - # - return d - - -def remove_quotes_string(s): - if s.startswith(('"', "'")): - s = s[1:] - if s.endswith(('"', "'")): - s = s[:-1] - return s - - -def clean_string(s): - # todo: generalise - data = remove_quotes_string(s) - data = data.replace("°", "") - if "," in data: - logger.warning("all commas ',' are replaced with '.' for float casting") - data = data.replace(",", ".") - return data diff --git a/src/python/librir/video_io/IRMovie.py b/src/python/librir/video_io/IRMovie.py index cea00842..f93d5407 100644 --- a/src/python/librir/video_io/IRMovie.py +++ b/src/python/librir/video_io/IRMovie.py @@ -1,4 +1,3 @@ -import datetime import functools import logging import math @@ -11,7 +10,7 @@ import pandas as pd from librir.tools.FileAttributes import FileAttributes -from librir.tools.utils import init_thermavip, unbind_thermavip_shared_mem +from librir.tools._thermavip import init_thermavip, unbind_thermavip_shared_mem from librir.video_io.rir_video_io import ( FILE_FORMAT_H264, enable_motion_correction, @@ -35,12 +34,10 @@ get_image_count, get_image_size, get_image_time, - get_optical_temperature, load_image, open_camera_file, set_emissivity, set_global_emissivity, - set_optical_temperature, support_emissivity, supported_calibrations, ) @@ -50,10 +47,6 @@ logger.setLevel(logging.INFO) -class IncoherentMetadata(Exception): - pass - - class CalibrationNotFound(Exception): pass @@ -75,18 +68,6 @@ class IRMovie(object): __tempfile__ = None handle = -1 _calibration_nickname_mapper = {"DL": "Digital Level"} - _roi_result_line = {"CEDIP": 240, "WEST": 512, "NIT": 256} - - _SHAPES = { - (512, 640): "WEST", - (515, 640): "WEST", - (240, 320): "CEDIP", - (242, 320): "CEDIP", - (243, 320): "CEDIP", - (256, 320): "NIT", - (259, 320): "NIT", - } - _th = None @classmethod @@ -154,10 +135,9 @@ def __init__(self, handle): self.handle = handle self.times = None - self._enable_bad_pixels = False + self._bad_pixels_correction = False self._last_lines = None - self._payload = None - self._survir_data = None + self.__tempfile__ = "" self._calibration_index = 0 self._timestamps = None @@ -172,8 +152,7 @@ def __init__(self, handle): def calibration(self): return list(self._calibration_nickname_mapper.keys())[self._calibration_index] - @calibration.setter - def calibration(self, value: Union[str, int]): + def _parse_calibration_index(self, value: Union[str, int]): searching_keys = self.calibrations + list( self._calibration_nickname_mapper.keys() ) @@ -186,23 +165,31 @@ def calibration(self, value: Union[str, int]): f"Calibration index out of range : {self._calibration_index}" ) - self._calibration_index = value - return + return value if value not in searching_keys: raise CalibrationNotFound( f"{value} not in available calibrations : {searching_keys}" ) lists = list(self._calibration_nickname_mapper), self.calibrations - _old_calib_idx = self._calibration_index idx = None for _list in lists: try: idx = _list.index(value) - self._calibration_index = idx + return idx except ValueError: pass - if idx is None: + + @calibration.setter + def calibration(self, value: Union[str, int]): + _old_calib_idx = self._calibration_index + _calibration_index = self._parse_calibration_index(value) + if _old_calib_idx != _calibration_index: + self._calibration_index = _calibration_index + self._payload = None + # clearing lru cache of IRMovie + IRMovie.data.fget.cache_clear() + if _calibration_index is None: self._calibration_index = _old_calib_idx raise CalibrationNotFound(f"calibration '{value}' is not registered") @@ -255,6 +242,7 @@ def registration_file(self, value) -> None: raise FileNotFoundError(f"{value} doesn't exist") self._registration_file = value load_motion_correction_file(self.handle, self._registration_file) + self.registration = True @property def registration(self) -> bool: @@ -285,13 +273,6 @@ def images(self): def image_size(self): return get_image_size(self.handle) - @property - def payload_size(self): - shape = get_image_size(self.handle) - limit = self._roi_result_line[self._SHAPES[shape]] - shape = (limit, shape[1]) - return shape - @property def calibrations(self): return supported_calibrations( @@ -307,8 +288,8 @@ def load_pos(self, pos, calibration=None): """ if calibration is None: calibration = 0 - - self.calibration = calibration + if self._parse_calibration_index(calibration) != self._calibration_index: + self.calibration = self._parse_calibration_index(calibration) res = load_image(self.handle, pos, self._calibration_index) self._frame_attributes_d[pos] = get_attributes(self.handle) # self.frame_attributes = get_attributes(self.handle) @@ -331,13 +312,13 @@ def load_secs(self, time, calibration=None): return res @property - def enable_bad_pixels(self): - return self._enable_bad_pixels + def bad_pixels_correction(self): + return self._bad_pixels_correction - @enable_bad_pixels.setter - def enable_bad_pixels(self, value): - self._enable_bad_pixels = bool(value) - enable_bad_pixels(self.handle, self._enable_bad_pixels) + @bad_pixels_correction.setter + def bad_pixels_correction(self, value): + self._bad_pixels_correction = bool(value) + enable_bad_pixels(self.handle, self._bad_pixels_correction) @property def filename(self): @@ -409,20 +390,6 @@ def data(self) -> np.ndarray: else: return self[:] - @property - def optical_temperature(self): - return get_optical_temperature(self.handle) - - @optical_temperature.setter - def optical_temperature(self, temperature): - """ - Set the optical temperature for given handle in degree Celsius. - This should be the temperature of the B30. - Not all cameras support this feature. Use support_optical_temperature() function - to test it. - """ - set_optical_temperature(self.handle, temperature) - @property def support_emissivity(self): return support_emissivity(self.handle) @@ -459,11 +426,11 @@ def tis(self): :return: """ if self.calibration == "Digital Level": - tis = (self.payload & (2**16 - 2**13)) >> 13 + tis = (self.data & (2**16 - 2**13)) >> 13 else: old_calib = self.calibration self.calibration = "DL" - tis = (self.payload & (2**16 - 2**13)) >> 13 + tis = (self.data & (2**16 - 2**13)) >> 13 self.calibration = old_calib return tis @@ -654,57 +621,12 @@ def to_h264( def __repr__(self): return "IRMovie({})".format(self.filename) - @property - def last_line_index(self): - return self._roi_result_line.get(self.camera_type, -3) - - @property - def payload(self): - if self._payload is None: - self._payload = self.data[:, : self.last_line_index, :] - return self._payload - - @property - def payload_generator(self): - for img in self: - yield img[: self.last_line_index, :] - - # @cached_property - @property - @functools.lru_cache() - def internal_camera_number(self): - res = self.frames_attributes["Camera #"].astype(np.uint8) - cam_numbers = np.unique(res) - if not len(np.unique(res)) == 1: - msg = ( - f"Many cameras used for this movie: {cam_numbers}\n" - "Something is wrong..." - ) - raise IncoherentMetadata(msg) - return cam_numbers[0] - - @classmethod - def _read_last_line_fpga_embedded_32_bits_word(cls, arr, address): - return (arr[:, 0, address + 1].astype(np.int32) << 16) | arr[:, 0, address] - - @property - # @functools.lru_cache() - def camera_temperatures(self): - return self._frame_attribute_getter("Camera T (C)") - - @property - # @functools.lru_cache() - def sensor_temperatures(self): - return self._frame_attribute_getter("Sensor T (K)") - @property @functools.lru_cache() def frames_attributes(self) -> pd.DataFrame: - # l = [] - for i in range(self.images): - self.load_pos(i) - # val = self.frame_attributes[key] - # l.append(self._frame_attributes_d[i]) + if len(self._frame_attributes_d) != self.images: + for i in range(self.images): + self.load_pos(i, self._calibration_index) df = pd.DataFrame(self._frame_attributes_d).T return df @@ -717,63 +639,6 @@ def _frame_attribute_getter(self, key) -> np.ndarray: finally: return np.array(values, dtype=float) - # @cached_property - @property - @functools.lru_cache() - def ir_filter_temperatures(self): - return self._frame_attribute_getter("IR Filter T (C)") - - # @cached_property - @property - @functools.lru_cache() - def peltier_powers(self): - return self._frame_attribute_getter("Peltier Power (%)") - - # @cached_property - @property - @functools.lru_cache() - def absolute_timestamps_ms(self): - return self._frame_attribute_getter("Time (absolute in ms)") - - # @cached_property - @property - @functools.lru_cache() - def absolute_timestamps_str(self): - return self._frame_attribute_getter("Time (formatted)") - - @property - def absolute_timestamps(self): - return [ - datetime.datetime.fromtimestamp(t / 1e3) - for t in self.absolute_timestamps_ms - ] - - @property - def metadata(self): - if self._last_lines is None: - self._last_lines = self.data[:, self.last_line_index :, :] - return self._last_lines - - @property - def camera_type(self): - return self._SHAPES.get(self.image_size, "TEST") - - @property - def has_metadata(self): - return self._last_lines is not None - - @property - def roi_line(self): - return self.metadata[:, 0] - - @property - def survir_data(self): - if self._survir_data is None: - df = self._extract_survir_data() - df = df.swaplevel(0, 1, axis=1).sort_index(axis=1) - self._survir_data = df - return self._survir_data - def to_thermavip(self, th_instance="Thermavip-1", player_id=0): th = init_thermavip(th_instance) if th: diff --git a/src/python/librir/video_io/IRSaver.py b/src/python/librir/video_io/IRSaver.py index 3e3b39db..39251cb1 100644 --- a/src/python/librir/video_io/IRSaver.py +++ b/src/python/librir/video_io/IRSaver.py @@ -5,8 +5,7 @@ @author: VM213788 """ -from .. import low_level -from ..low_level.misc import * +import numpy as np from .rir_video_io import ( h264_open_file, h264_close_file, @@ -38,6 +37,12 @@ class IRSaver(object): This class should not be used to mix lossy and lossless frames within the same video file. """ + handle: int = 0 + width: int = 0 + height: int = 0 + global_attrs = {} + params = {} + def __init__( self, outfile=None, width=None, height=None, lossy_height=None, clevel=0 ): @@ -45,11 +50,6 @@ def __init__( Constructor. If outfile, width and height are not NULL, open the file. """ - self.saver = 0 - self.width = 0 - self.height = 0 - self.global_attrs = {} - self.params = {} if outfile is not None and width is not None and height is not None: self.filename = outfile @@ -72,7 +72,7 @@ def is_open(self): """ Returns true if the output file is open """ - return self.saver > 0 + return self.handle > 0 def close(self): """ @@ -80,9 +80,9 @@ def close(self): Note that you MUST close the file after writting frames and before using it, as it will write the file trailer (image timestamps and attributes). """ - if self.saver > 0: - h264_close_file(self.saver) - self.saver = 0 + if self.handle > 0: + h264_close_file(self.handle) + self.handle = 0 def open(self, outfile, width, height, lossy_height=None): """ @@ -95,7 +95,7 @@ def open(self, outfile, width, height, lossy_height=None): default, lossy_height is equal to height. """ self.close() - self.saver = h264_open_file(outfile, width, height, lossy_height) + self.handle = h264_open_file(outfile, width, height, lossy_height) self.width = width self.height = height self.lossy_height = lossy_height @@ -103,10 +103,10 @@ def open(self, outfile, width, height, lossy_height=None): # set attributes and parameters (if any) if len(self.global_attrs) > 0: - h264_set_global_attributes(self.saver, self.global_attrs) + h264_set_global_attributes(self.handle, self.global_attrs) if len(self.params) > 0: for k in self.params: - h264_set_parameter(self.saver, k, self.params[k]) + h264_set_parameter(self.handle, k, self.params[k]) self.global_attrs = {} self.params = {} @@ -126,7 +126,7 @@ def set_parameter(self, param, value): - runningAverage: running average length as described in [], used for lossy compression. Default to 32. """ if self.is_open(): - h264_set_parameter(self.saver, param, str(value)) + h264_set_parameter(self.handle, param, str(value)) else: self.params[param] = str(value) @@ -136,7 +136,7 @@ def set_global_attributes(self, attributes): Note that only the pairs (key,value) convertible to a string are stored. """ if self.is_open(): - h264_set_global_attributes(self.saver, attributes) + h264_set_global_attributes(self.handle, attributes) else: self.global_attrs = attributes @@ -151,9 +151,9 @@ def add_image(self, image, timestamp, attributes=dict()): ): raise RuntimeError("wrong image dimension") - h264_add_image_lossless(self.saver, image, timestamp, attributes) + h264_add_image_lossless(self.handle, image, timestamp, attributes) - def add_image_lossy(self, image_DL, timestamp, attributes=dict()): + def add_image_lossy(self, image_DL: np.ndarray, timestamp, attributes=dict()): """ Add an image compressed in a lossy way. """ @@ -166,9 +166,9 @@ def add_image_lossy(self, image_DL, timestamp, attributes=dict()): # if len(image_T.shape) != 2 or image_T.shape[1] != self.width or image_T.shape[0] != self.height: # raise RuntimeError("wrong T image dimension") - h264_add_image_lossy(self.saver, image_DL, timestamp, attributes) + h264_add_image_lossy(self.handle, image_DL, timestamp, attributes) - def add_loss(self, image): + def add_loss(self, image: np.ndarray): """ Add loss to image (without writing it) and returns the result """ @@ -181,10 +181,10 @@ def add_loss(self, image): # if len(image_T.shape) != 2 or image_T.shape[1] != self.width or image_T.shape[0] != self.height: # raise RuntimeError("wrong T image dimension") - return h264_add_loss(self.saver, image) + return h264_add_loss(self.handle, image) def get_low_errors(self): - return h264_get_low_errors(self.saver) + return h264_get_low_errors(self.handle) def get_high_errors(self): - return h264_get_high_errors(self.saver) + return h264_get_high_errors(self.handle) diff --git a/src/python/librir/video_io/__init__.py b/src/python/librir/video_io/__init__.py index feb295a4..f33bf0d8 100644 --- a/src/python/librir/video_io/__init__.py +++ b/src/python/librir/video_io/__init__.py @@ -1,19 +1,32 @@ import os import sys -sys.path.insert(1, os.path.realpath(os.path.pardir)) - -# import useful functions from rir_video_io -from .rir_video_io import correct_PCR_file, video_file_format - -from .rir_video_io import FILE_FORMAT_PCR -from .rir_video_io import FILE_FORMAT_WEST -from .rir_video_io import FILE_FORMAT_PCR_ENCAPSULATED -from .rir_video_io import FILE_FORMAT_ZSTD_COMPRESSED -from .rir_video_io import FILE_FORMAT_H264 - from .IRMovie import IRMovie from .IRSaver import IRSaver + +# import useful functions from rir_video_io +from .rir_video_io import ( + FILE_FORMAT_H264, + FILE_FORMAT_PCR, + FILE_FORMAT_PCR_ENCAPSULATED, + FILE_FORMAT_WEST, + FILE_FORMAT_ZSTD_COMPRESSED, + correct_PCR_file, + video_file_format, + get_filename, +) from .utils import is_ir_file_corrupted -__all__ = ["correct_PCR_file", "IRSaver", "IRMovie", "is_ir_file_corrupted"] +__all__ = [ + "correct_PCR_file", + "IRSaver", + "IRMovie", + "is_ir_file_corrupted", + "video_file_format", + "FILE_FORMAT_PCR", + "FILE_FORMAT_WEST", + "FILE_FORMAT_PCR_ENCAPSULATED", + "FILE_FORMAT_ZSTD_COMPRESSED", + "FILE_FORMAT_H264", + "get_filename", +] diff --git a/src/python/librir/video_io/rir_video_io.py b/src/python/librir/video_io/rir_video_io.py index fd842e9a..c2dec828 100644 --- a/src/python/librir/video_io/rir_video_io.py +++ b/src/python/librir/video_io/rir_video_io.py @@ -1,8 +1,13 @@ import ctypes as ct +import logging import numpy as np -from ..low_level.misc import _video_io, toString, toArray, toBytes +from ..low_level.misc import _video_io, toArray, toBytes, toString + +logging.basicConfig() +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) FILE_FORMAT_PCR = 1 @@ -15,7 +20,8 @@ def open_camera_file(filename): """ Open a video file. - Returns an integer value representing the camera. This value can be used by the functions: + Returns an integer value representing the camera. This value can be used by the + functions: - close_camera - get_camera_identifier - get_camera_pulse @@ -208,7 +214,18 @@ def set_emissivity(camera, emissivity_array): def get_emissivity(camera): - """Returns the emissivity map for given camera""" + """ + Returns the emissivity map for given camera. + + Args: + camera (_type_): _description_ + + Raises: + RuntimeError: _description_ + + Returns: + _type_: _description_ + """ size = get_image_size(camera) pixels = np.zeros(size, dtype=np.float32) _video_io.get_emissivity.argtypes = [ct.c_int, ct.POINTER(ct.c_float), ct.c_int] @@ -221,65 +238,37 @@ def get_emissivity(camera): def support_emissivity(camera): - """Returns True if given camera supports setting a custom emissivity value, False otherwise""" - res = _video_io.support_emissivity(int(camera)) - if res == 1: - return True - return False + """ + Returns True if given camera supports setting a custom emissivity value, False + otherwise + Args: + camera (_type_): _description_ -def camera_saturate(camera): + Returns: + _type_: _description_ """ - Returns true if setting the emissivity saturates the temperature calibration for the last call - to load_image, False otherwise. - """ - res = _video_io.camera_saturate(int(camera)) + res = _video_io.support_emissivity(int(camera)) if res == 1: return True return False -def set_optical_temperature(camera, temperature): +def camera_saturate(movie_handle): """ - Set the optical temperature for given camera in degree Celsius. - This should be the temperature of the B30. - Not all cameras supoort this feature. Use support_optical_temperature() function to test it. - """ - _video_io.set_optical_temperature.argtypes = [ct.c_int, ct.c_uint16] - res = _video_io.set_optical_temperature(int(camera), np.ushort(temperature)) - if res < 0: - raise RuntimeError("An error occured while calling 'set_optical_temperature'") + Returns true if setting the emissivity saturates the temperature + calibration for the last call to load_image, False otherwise. + Args: + movie_handle (int): movie handle -def get_optical_temperature(camera): - """Returns the optical temperature degree Celsius for given camera""" - res = _video_io.get_optical_temperature(camera) - return res - - -def set_STEFI_temperature(camera, temperature): - """ - Set the STEFI temperature for given camera in degree Celsius. - Not all cameras supoort this feature. Use support_optical_temperature() function to test it. + Returns: + bool: is the image saturated ? """ - _video_io.set_STEFI_temperature.argtypes = [ct.c_int, ct.c_uint16] - res = _video_io.set_STEFI_temperature(int(camera), np.ushort(temperature)) - if res < 0: - raise RuntimeError("An error occured while calling 'set_STEFI_temperature'") - - -def get_STEFI_temperature(camera): - """Returns the STEFI temperature degree Celsius for given camera""" - res = _video_io.get_STEFI_temperature(camera) - return res - - -def support_optical_temperature(camera): - """Returns True if given camera supports custom optical temperature; False otherwise""" - res = _video_io.support_optical_temperature(camera) - if res == 0: - return False - return True + res = _video_io.camera_saturate(int(movie_handle)) + if res == 1: + return True + return False def enable_bad_pixels(camera, enable=True): @@ -312,11 +301,13 @@ def flip_camera_calibration(camera, flip_rl, flip_ud): For given camera, flip the calibration files """ ret = _video_io.flip_camera_calibration(int(camera), int(flip_rl), int(flip_ud)) - if ret < 0: - raise RuntimeError("An error occured while calling 'flip_camera_calibration'") + if ret == -1: + raise RuntimeError("camera is a NULL pointer") + if ret == -2: + raise RuntimeError("There is no calibration in movie") -def calibration_files(camera): +def calibration_files(movie_handle): """ Returns the calibration file names for given camera. """ @@ -324,14 +315,14 @@ def calibration_files(camera): dstSize = np.zeros((1), dtype="i") dstSize[0] = 100 ret = _video_io.calibration_files( - camera, + movie_handle, dst.ctypes.data_as(ct.POINTER(ct.c_char)), dstSize.ctypes.data_as(ct.POINTER(ct.c_int)), ) if ret == -2: dst = np.zeros((dstSize[0]), dtype="c") ret = _video_io.calibration_files( - camera, + movie_handle, dst.ctypes.data_as(ct.POINTER(ct.c_char)), dstSize.ctypes.data_as(ct.POINTER(ct.c_int)), ) @@ -623,12 +614,12 @@ def h264_add_loss(saver, image): return image -def h264_get_low_errors(saver): +def h264_get_low_errors(saver: int): """ Returns the low error vector for last saved movie """ - _video_io.h264_get_low_erros.argtypes = [ + _video_io.h264_get_low_errors.argtypes = [ ct.c_int, ct.POINTER(ct.c_uint16), ct.POINTER(ct.c_int), @@ -658,7 +649,7 @@ def h264_get_high_errors(saver): Returns the high error vector for last saved movie """ - _video_io.h264_get_high_erros.argtypes = [ + _video_io.h264_get_high_errors.argtypes = [ ct.c_int, ct.POINTER(ct.c_uint16), ct.POINTER(ct.c_int), @@ -723,57 +714,6 @@ def correct_PCR_file(filename, width, height, frequency): # raise RuntimeError("'correct_PCR_file': unknown error") -def bzstd_open_file(filename, width, height, rate, method, clevel): - """ - Open output video file compressed using zstd and blosc, with given image width and height, frame rate, comrpession method and compression level. - method == 1 means ZSTD standard compression (clevel goes from 0 to 22), - method == 2 means blosc+ZSTD standard compression (clevel goes from 1 to 10), - method == 3 means blosc+ZSTD advanced compression (clevel goes from 1 to 10). - Returns file identifier on success. - The bzstd format is provided for tests only and should not be used (prefer the h264 format instead) - """ - _video_io.open_video_write.argtypes = [ - ct.c_char_p, - ct.c_int, - ct.c_int, - ct.c_int, - ct.c_int, - ct.c_int, - ] - tmp = _video_io.open_video_write( - str(filename).encode("ascii"), - int(width), - int(height), - int(rate), - int(method), - int(clevel), - ) - if tmp < 0: - raise RuntimeError("An error occured while calling 'open_video_write'") - return tmp - - -def bzstd_close_file(saver): - """ - Close video saver - """ - _video_io.close_video(saver) - - -def bzstd_add_image(saver, image, timestamp): - """ - Add and compress (lossless) an image to the bzstd file. - """ - _video_io.image_write.argtypes = [ct.c_int, ct.POINTER(ct.c_uint16), ct.c_longlong] - timestamp = np.int64(timestamp) - image = np.array(image, dtype="H") - tmp = _video_io.image_write( - saver, image.ctypes.data_as(ct.POINTER(ct.c_uint16)), timestamp - ) - if tmp < 0: - raise RuntimeError("An error occured while calling 'h264_add_image_lossless'") - - def load_motion_correction_file(cam, filename): """ Load registration file for given camera diff --git a/src/python/librir/video_io/utils.py b/src/python/librir/video_io/utils.py index 7625b25e..0b104ca9 100644 --- a/src/python/librir/video_io/utils.py +++ b/src/python/librir/video_io/utils.py @@ -45,7 +45,7 @@ def split_rush(filename, index=None, step=30, dest_folder=None) -> List[Path]: def check_ir_file(filename): - with IRMovie.from_filename(filename) as cam: + with IRMovie.from_filename(filename) as mov: pass diff --git a/src/python/pyproject.toml.in b/src/python/pyproject.toml.in new file mode 100644 index 00000000..0c0fcfc9 --- /dev/null +++ b/src/python/pyproject.toml.in @@ -0,0 +1,33 @@ +[build-system] +requires = ["setuptools", "setuptools-scm"] +build-backend = "setuptools.build_meta" + +[project] +name = "@PROJECT_NAME@" +authors = [ + {name = "Victor MONCADA", email = "victor.moncada@cea.fr"}, + {name = "Léo DUBUS", email = "leo.dubus@cea.fr"}, + {name = "Erwan GRELIER", email = "erwan.grelier@cea.fr"}, +] +description = "Librir is a C/C++/Python library to manipulate infrared video and building Cognitive Vision/Machine Learning applications." +requires-python = ">=3.7" +keywords = ["infrared", "imagery", "fusion"] +license = {text = "BSD-3-Clause"} +classifiers = [ + "Programming Language :: Python :: 3", +] +dependencies = [ + "numpy", + "pandas", + "opencv-python==4.5.5.64", + "joblib", + "future-annotations;python_version<'3.7'", +] +dynamic = ["readme"] +version = "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@" + +[tool.setuptools.dynamic] +readme = {file = ["README.md"]} + +[tool.setuptools.package-data] +"@PROJECT_NAME@.libs" = ["@SHARED_LIBRARY_SUFFIX_PATTERN@"] \ No newline at end of file diff --git a/src/python/setup.py b/src/python/setup.py deleted file mode 100644 index 1525d097..00000000 --- a/src/python/setup.py +++ /dev/null @@ -1,123 +0,0 @@ -import os -import re -import sys - -from setuptools import setup, find_packages - - -def find_version(): - filename = "CMakeLists.txt" - with open(filename, "r") as f: - txt = f.read() - - start = txt.find("project(") - end = txt[start:].find(")") - sub = txt[start : start + end + 1] - m = re.search(r"VERSION.*(?P\d+\.\d+\.\d+).*", sub) - if m: - d = m.groupdict() - return d["version"] - return "" - # mm = re.search(r"(?P\d+)\.(?P\d+)\.(?P\d+)", d['version']) - # if mm: - # mm.groupdict() - - -def get_requires(path): - try: - res = [] - with open(path, "r") as file: - # reading each line - for line in file: - # reading each word - for word in line.split(): - res.append(word) - except: - pass - return res - - -def get_extra_requires(path, add_all=True): - """ - https://hanxiao.io/2019/11/07/A-Better-Practice-for-Managing-extras-require-Dependencies-in-Python/ - - @param path: - @param add_all: - @return: - """ - from collections import defaultdict - - with open(path) as fp: - extra_deps = defaultdict(set) - for k in fp: - if k.strip() and not k.startswith("#"): - tags = set() - if ":" in k: - k, v = k.split(":") - tags.update(vv.strip() for vv in v.split(",")) - for t in tags: - extra_deps[t].add(k) - - # add tag `all` at the end - if add_all: - extra_deps["all"] = set(vv for v in extra_deps.values() for vv in v) - - return extra_deps - - -def package_files(directory): - print(directory) - paths = [] - for path, directories, filenames in os.walk(directory): - for filename in filenames: - paths.append(os.path.join(".", path, filename)) - paths[-1] = paths[-1].replace("\\", "/") - return paths - - -licenses_files = package_files("librir/LICENSES") -libs_files = package_files("librir/libs") -masks_files = package_files("librir/masks") -print(licenses_files) - - -DYNAMIC_LIBRARY_SUFFIX = "dll" if sys.platform == "win32" else "so" - -EXCLUDED_PACKAGES = [] -PACKAGES = find_packages(exclude=EXCLUDED_PACKAGES) - -LIB_GLOBPATH = f"libs/*.{DYNAMIC_LIBRARY_SUFFIX}*" -# add masks -MASK_GLOBPATH = "masks/*.txt*" - -setup( - name="librir", - version=find_version(), - packages=PACKAGES, - package_data={"librir": [LIB_GLOBPATH, MASK_GLOBPATH]}, - data_files=[ - ("librir-core/LICENSES", licenses_files), - ("librir-core/masks", masks_files), - ("librir-core", ["./librir/LICENSE"]), - ], - include_package_data=True, - install_requires=[ - "numpy", - "pandas", - "joblib", - "future-annotations;python_version<'3.7'", - ] - + get_requires("requirements.txt"), - python_requires=">=3.7", - url="", - license="BSD-3-clause", - author="VM213788, LD243615, EG264877", - author_email="victor.moncada@cea.fr, leo.dubus@cea.fr, erwan.grelier@cea.fr", - description=( - "Librir is a C/C++/Python library to manipulate infrared video from the" - "WEST tokamak, accessing WEST diagnostic signals and building " - "Cognitive Vision/Machine Learning applications." - ), - long_description=open("README.md", "rt").read(), - long_description_content_type="text/markdown", -) diff --git a/tests/python/conftest.py b/tests/python/conftest.py index 39f4ffa4..9cc89f7c 100644 --- a/tests/python/conftest.py +++ b/tests/python/conftest.py @@ -3,6 +3,8 @@ import sys from pathlib import Path +from typing import List +from librir.tools import FileAttributes import numpy as np import pytest @@ -21,6 +23,16 @@ DL_MAX_VALUE = 8192 +def add_noise(image, mean=0, var=0.5): + """Add gaussian noise to input image""" + row, col = image.shape + sigma = var**0.5 + gauss = np.random.normal(mean, sigma, (row, col)) + gauss = gauss.reshape(row, col) + noisy = image + gauss + return np.array(noisy, dtype=np.uint16) + + def generate_mock_movie_data_uniform(*shape): if len(shape) == 2: n_images = 1 @@ -152,28 +164,23 @@ def filename(movie: IRMovie): return movie.filename +@pytest.fixture(scope="session") +def wrong_filename(): + return "not_existing_filename" + + @pytest.fixture(scope="session") def timestamps(array): return np.arange(len(array), dtype=float) * 2 @pytest.fixture(scope="session") -def movie_with_firmware_date(valid_2D_array): - # 23-01-2023 - day = 23 - month = 1 - year = 2023 - firmware_date = np.uint32(0) - - firmware_date = (day << 24) | (month << 16) | year - - # struct.pack("I", firmware_date) - r = bytearray(valid_2D_array.tobytes("C")) - v = struct.pack("I", firmware_date) - offset = (valid_2D_array.shape[0] - 3) * valid_2D_array.shape[1] * 2 - r[(offset + 254 * 2) : (offset + 256 * 2)] = v - - arr = np.frombuffer(bytes(r), dtype=np.uint16).reshape(valid_2D_array.shape) - arr[-3, 254:256] - with IRMovie.from_numpy_array(arr) as mov: - yield mov +def images() -> List[np.ndarray]: + # generate 100 noisy images with an average background value going from 10 to 110 by step of 1 + images = [] + background = np.random.rand(512, 640) * 1000 + for i in range(10, 110, 1): + img = background + np.ones((512, 640)) * i + img = add_noise(img, 0, 0.5) + images.append(img) + return images diff --git a/tests/python/test_IRMovie.py b/tests/python/test_IRMovie.py index 64d3aeb7..6f6ca93e 100644 --- a/tests/python/test_IRMovie.py +++ b/tests/python/test_IRMovie.py @@ -1,4 +1,5 @@ import random +from librir.video_io.utils import is_ir_file_corrupted, split_rush import numpy as np import numpy.testing as npt @@ -8,26 +9,29 @@ from librir.video_io.rir_video_io import FILE_FORMAT_H264 -@pytest.mark.instanstiation +@pytest.mark.instantiation def test_IRMovie_with_filename_as_input(filename): mov = IRMovie.from_filename(filename) assert type(mov) is IRMovie + with pytest.raises(RuntimeError): + IRMovie.from_filename("") + assert mov.filename == filename -@pytest.mark.instanstiation +@pytest.mark.instantiation def test_IRMovie_instantiation_with_2D_numpy_array(valid_2D_array): mov = IRMovie.from_numpy_array(valid_2D_array) expected = valid_2D_array[np.newaxis, :] npt.assert_array_equal(mov.data, expected) -@pytest.mark.instanstiation +@pytest.mark.instantiation def test_IRMovie_instantiation_with_3D_numpy_array(valid_3d_array): mov = IRMovie.from_numpy_array(valid_3d_array) npt.assert_array_equal(mov.data, valid_3d_array) -@pytest.mark.instanstiation +@pytest.mark.instantiation def test_IRMovie_instantiation_with_bad_numpy_array(bad_array): with pytest.raises(ValueError) as e: mov = IRMovie.from_numpy_array(bad_array) @@ -113,7 +117,7 @@ def movie_as_bytes(filename) -> bytes: return data -@pytest.mark.instanstiation +@pytest.mark.instantiation def test_from_filename(filename): mov = IRMovie.from_filename(filename) assert mov.filename == filename @@ -129,26 +133,45 @@ def test_from_bytes_with_timestamps(movie_as_bytes, timestamps): def test_close(array): mov = IRMovie.from_numpy_array(array) del mov - - -def test_payload_generator(movie: IRMovie): - npt.assert_array_equal(movie.payload[0], next(movie.payload_generator)) + mov = IRMovie.from_numpy_array(array, attrs={"name": "toto"}) + assert mov.attributes["name"] + del mov def test_video_file_format(movie: IRMovie): assert movie.video_file_format == FILE_FORMAT_H264 -# def test_last_line() -def test_firmware_date_pixel(movie_with_firmware_date: IRMovie): - movie_with_firmware_date.load_pos(0) - assert movie_with_firmware_date.frame_attributes["Firmware Date"] == "23-1-2023" +def test_split_rush(movie: IRMovie): + total = movie.images + steps = 2 + filenames = split_rush(movie.filename, step=steps) + assert len(filenames) == (total // steps) + + for f in filenames: + assert not is_ir_file_corrupted(f) + + +def test_is_ir_file_corrupted(filename): + assert not is_ir_file_corrupted(filename) + assert is_ir_file_corrupted("inexistent_filename") + + +def test_movie_getitem(movie: IRMovie): + img = movie[0] + + +def test_frames_attributes(movie: IRMovie): + movie.frame_attributes + movie.frames_attributes + - # firmware_date = movie_with_firmware_date.metadata[0, 0, 254:256].tobytes() - # firmware_date = np.frombuffer(firmware_date, dtype=np.uint32)[0] - # day = (firmware_date >> 24) & 0xFF - # assert day == 23 - # month = (firmware_date >> 16) & 0xFF - # assert month == 1 - # year = (firmware_date) & 0xFFFF - # assert year == 2023 +def test_flip_camera_calibration_when_no_calibration(movie: IRMovie): + with pytest.raises(RuntimeError): + movie.flip_calibration(True, False) + with pytest.raises(RuntimeError): + movie.flip_calibration(False, False) + with pytest.raises(RuntimeError): + movie.flip_calibration(False, True) + with pytest.raises(RuntimeError): + movie.flip_calibration(True, True) diff --git a/tests/python/test_registration.py b/tests/python/test_registration.py new file mode 100644 index 00000000..7f554299 --- /dev/null +++ b/tests/python/test_registration.py @@ -0,0 +1,138 @@ +import os +from librir.geometry.rir_geometry import draw_polygon +from librir.registration.masked_registration_ecc import ( + MaskedRegistratorECC, + manage_computation_and_tries, +) +import numpy as np +import pytest +from librir.signal_processing import translate +from librir.geometry import extract_convex_hull +from librir.video_io.IRMovie import IRMovie +from .conftest import add_noise + + +# background = np.zeros((512, 640)) # np.random.rand(512,640) * 5 +@pytest.fixture +def background(): + return np.zeros((512, 640)) + + +# polygon = [[42, 42], [100, 42], [200, 200], [80, 300]] +@pytest.fixture +def polygon(): + return [[42, 42], [100, 42], [200, 200], [80, 300]] + + +def test_extract_convex_hull(polygon): + extract_convex_hull(polygon) + + +# polygon_img = np.zeros((512, 640)) +# polygon_img = draw_polygon(polygon_img, polygon, 10) +@pytest.fixture +def polygon_img(polygon): + _polygon_img = np.zeros((512, 640)) + return draw_polygon(_polygon_img, polygon, 10) + + +@pytest.fixture +def translated_images_t(background, polygon_img): + images = [] + translations_x = [] + translations_y = [] + for i in range(10, 110, 1): + # define translation (dx,dy) + dx = i - 10 + dy = i - 10 + # translate polygon image + pimg = translate(polygon_img, dx, dy, "nearest") + # compute image + img = background + np.ones((512, 640)) * i + pimg + # add noise + img = add_noise(img, 0, 1) + images.append(img) + translations_x.append(dx) + translations_y.append(dy) + return images, translations_x, translations_y + + +@pytest.fixture +def movie_with_polygon_drawn(movie: IRMovie, polygon): + step = 10 + images = [] + translations_x = [] + translations_y = [] + for i in range(movie.images): + _img = movie[i] + avg_value = _img.mean().astype(int) + draw_polygon(_img, polygon, avg_value + 50) + dx = i - step + dy = i - step + pimg = translate(_img, dx, dy, "nearest") + img = _img.mean().astype(int) + np.ones(movie.data.shape[1:]) * i + pimg + img = add_noise(img, 0, 1) + images.append(img) + translations_x.append(dx) + translations_y.append(dy) + data = np.array(images) + with IRMovie.from_numpy_array(data) as mov: + yield mov + + +@pytest.fixture +def reg(translated_images_t): + return MaskedRegistratorECC(1, 1) + + +def test_mask_registrator(reg: MaskedRegistratorECC, translated_images_t): + images, translations_x, translations_y = translated_images_t + + # Set the first image + reg.start(images[0]) + # Compute remaining images + for i in range(1, len(images)): + reg.compute(images[i]) + + # reg.to_reg_file("toto.regfile") + # Print x and y translations. We expect translations going from 0 to 99 by steps of 1 + # x = np.array(reg.x) + # x = (x/5)*5 + # x = np.ceil(x) + # assert all(x.astype(int) == range(100)) + # y = np.array(reg.y) + # y = y.round() + # assert all(x.astype(int) == range(100)) + # assert reg.y == range(100) + + +# def test_motion_correction() + + +def test_set_registration_file_to_IRMovie( + movie_with_polygon_drawn: IRMovie, reg: MaskedRegistratorECC +): + images = movie_with_polygon_drawn.data + + # Set the first image + reg.start(images[0]) + # Compute remaining images + for i in range(1, len(images)): + reg.compute(images[i]) + + reg_file = f"{movie_with_polygon_drawn.filename.stem}.regfile" + reg.to_reg_file(reg_file) + movie_with_polygon_drawn.registration_file = reg_file + movie_with_polygon_drawn[0] + os.unlink(reg_file) + + +def test_manage_computation_and_tries( + movie_with_polygon_drawn: IRMovie, reg: MaskedRegistratorECC +): + images = movie_with_polygon_drawn.data + + # Set the first image + reg.start(images[0]) + for img in images[1:]: + manage_computation_and_tries(img, reg) diff --git a/tests/python/test.py b/tests/python/test_rir.py similarity index 65% rename from tests/python/test.py rename to tests/python/test_rir.py index ea883b96..99c5e917 100644 --- a/tests/python/test.py +++ b/tests/python/test_rir.py @@ -1,205 +1,205 @@ -import numpy as np - -import librir.geometry as ge -import librir.signal_processing as sp -import librir.signal_processing.BadPixels as bp -import librir.video_io.IRMovie as movie -import librir.video_io.IRSaver as saver -import librir.west as west -import librir.west.WESTIRMovie as w -from librir import tools as rts - - -def test_temp_directory_access(): - print(rts.get_temp_directory()) - - -def test_get_default_temp_directory(): - print(rts.get_default_temp_directory()) - - -def test_set_temp_directory(): - print(rts.set_temp_directory(rts.get_default_temp_directory())) - - -def test_zstd_compress_bound(): - print(rts.zstd_compress_bound(100)) - - -def test_zstd_compress(): - global c - c = rts.zstd_compress(b"toto") - print(len(c)) - - -def test_zstd_decompress(): - print(rts.zstd_decompress(c)) - - -def test_blosc_compress_zstd(): - global c - c = rts.blosc_compress_zstd(b"toto", 2, rts.BLOSC_SHUFFLE, 1) - print(len(c)) - - -def test_blosc_decompress_zstd(): - print(rts.blosc_decompress_zstd(c)) - - -def test_file_attributes(): - import librir.tools.FileAttributes as fa - - attrs = fa.FileAttributes("attrs.test") - attrs.attributes = {"toto": 2, "tutu": "tata"} - attrs.timestamps = range(11) - attrs.set_frame_attributes(10, {"toto": 2, "tutu": "tata"}) - attrs.close() - - attrs = fa.FileAttributes("attrs.test") - print(attrs.attributes) - print(attrs.frame_count()) - print(attrs.frame_attributes(0)) - print(attrs.frame_attributes(10)) - - -polygon = [[0, 0], [1, 0], [1.2, 0.2], [1.8, 0.1], [5, 5], [3.2, 5], [0, 9]] -polygon2 = np.array(polygon, dtype=np.float64) * 1.5 - - -def test_polygon_interpolate(): - print(ge.polygon_interpolate(polygon, polygon2, 0.5)) - - -img = np.zeros((10, 10), dtype=np.int32) - - -def test_draw_polygon(): - im = ge.draw_polygon(img, polygon, 1) - print(im) - - -def test_extract_polygon(): - print(ge.extract_polygon(img, 1)) - - -def tesextract_convex_hull(): - print(ge.extract_convex_hull(polygon)) - - -def test_rdp_simplify_polygon(): - print(ge.rdp_simplify_polygon(polygon, 0)) - - -def test_rdp_simplify_polygon2(): - print(ge.rdp_simplify_polygon2(polygon, 5)) - - -def test_minimum_area_bbox(): - print(ge.minimum_area_bbox(polygon)) - assert ge.minimum_area_bbox(polygon) != ([np.nan, np.nan], 0.0, 0.0, np.nan, np.nan) - - -def test_translate(): - global img - img = np.ones((12, 12), dtype=np.float64) - img = sp.translate(img, 1.2, 1.3, "constant", 0) - print(img) - - -def test_gaussian_filter(): - global img - img = sp.gaussian_filter(img, 0.75) - print(img) - - -def test_find_median_pixel(): - img = np.array(range(100), dtype=np.uint16) - img.shape = (10, 10) - print(sp.find_median_pixel(img, 0.5)) - print(sp.find_median_pixel(img, 0.2)) - - -def test_extract_times(): - times1 = [0, 0.2, 1, 1.5, 2.3, 3.3, 4, 5] - times2 = [-1, 3, 4, 4.3, 4.7] - print(sp.extract_times((times1, times2), "union")) - print(sp.extract_times((times1, times2), "inter")) - - -def test_resample_time_serie(): - x = range(10) - y = range(10) - times = [0, 0.2, 1, 1.5, 2.3, 3.3, 4, 5, 5.6, 9.9, 10, 12, 13] - print(sp.resample_time_serie(x, y, times)) - print(sp.resample_time_serie(x, y, times, 0)) - print(sp.resample_time_serie(x, y, times, None, False)) - - -def test_jpegls_encode(): - global c - img = np.ones((20, 12), dtype=np.uint16) - c = sp.jpegls_encode(img) - print(len(c)) - - -def test_jpegls_decode(): - global c - img = sp.jpegls_decode(c, 12, 20) - print(img) - - -def test_bad_pixels(): - img = np.ones((20, 12), dtype=np.uint16) - b = bp.BadPixels(img) - print(b.correct(img)) - del b - - -def test_label_image(): - polygon = [[0, 0], [1, 0], [1.2, 0.2], [1.8, 0.1], [5, 5], [3.2, 5], [0, 9]] - polygon2 = [[15, 15], [16, 17], [14, 17]] - img = np.zeros((20, 20), np.int32) - img = ge.draw_polygon(img, polygon, 5) - img = ge.draw_polygon(img, polygon2, 5) - print(img) - print(sp.label_image(img)) - print(sp.keep_largest_area(img)) - - -def test_ir_saver_movie(): - img0 = np.zeros((20, 20), dtype=np.int32) - img1 = np.ones((20, 20), dtype=np.int32) - s = saver.IRSaver("test.h264", 20, 20, 20, 8) - s.add_image(img0, 0) - s.add_image(img1, 1) - s.close() - - m = movie.IRMovie.from_filename("test.h264") - print(m.images) - print(m.image_size) - print(m.timestamps) - print(m.calibrations) - print(m.filename) - print(m.calibration_files) - print(m[0]) - print(m[1]) - - -def test_pulse_infos(): - print(west.get_camera_infos(56927)) - print(west.get_views(56927)) - print(west.ts_exists(55210, "SMAG_IP")) - print(west.ts_date(56927)) - print(west.ts_last_pulse()) - print(west.ts_read_signal(55210, "SMAG_IP")) - - -def test_west_ir_movie(): - m = w.WESTIRMovie(56927, "WA") - print(m.images) - print(m.image_size) - print(m.timestamps) - print(m.calibrations) - print(m.filename) - print(m.calibration_files) - print(m.rois) +from librir.video_io import IRMovie, IRSaver +import numpy as np + +import librir.geometry as ge +import librir.signal_processing as sp +import librir.signal_processing.BadPixels as bp +from librir.video_io import video_file_format, get_filename +import librir.tools.FileAttributes as fa +from librir import tools as rts +import pytest + + +def test_zstd_compress_bound(): + print(rts.zstd_compress_bound(100)) + + +@pytest.fixture +def data_bytes(): + return b"toto" + + +@pytest.fixture +def c(data_bytes): + return rts.zstd_compress(data_bytes) + + +def test_zstd_compress(data_bytes): + c = rts.zstd_compress(data_bytes) + print(len(c)) + + +def test_zstd_decompress(c, data_bytes): + rts.zstd_decompress(c) == data_bytes + + +def test_blosc_compress_zstd(data_bytes): + rts.blosc_compress_zstd(data_bytes, 2, rts.BLOSC_SHUFFLE, 1) + + +@pytest.fixture +def compressed_blosc(data_bytes): + return rts.blosc_compress_zstd(data_bytes, 2, rts.BLOSC_SHUFFLE, 1) + + +def test_blosc_decompress_zstd(compressed_blosc): + print(rts.blosc_decompress_zstd(compressed_blosc)) + + +def test_file_attributes(): + attrs = fa.FileAttributes("attrs.test") + attrs.attributes = {"toto": 2, "tutu": "tata"} + attrs.timestamps = range(11) + attrs.set_frame_attributes(10, {"toto": 2, "tutu": "tata"}) + attrs.close() + + attrs = fa.FileAttributes("attrs.test") + print(attrs.attributes) + print(attrs.frame_count()) + print(attrs.frame_attributes(0)) + print(attrs.frame_attributes(10)) + + +polygon = [[0, 0], [1, 0], [1.2, 0.2], [1.8, 0.1], [5, 5], [3.2, 5], [0, 9]] +polygon2 = np.array(polygon, dtype=np.float64) * 1.5 + + +def test_polygon_interpolate(): + print(ge.polygon_interpolate(polygon, polygon2, 0.5)) + + +# img = np.zeros((10, 10), dtype=np.int32) + + +def test_draw_polygon(img): + im = ge.draw_polygon(img, polygon, 1) + print(im) + + +def test_extract_polygon(img): + print(ge.extract_polygon(img, 1)) + + +def tesextract_convex_hull(): + print(ge.extract_convex_hull(polygon)) + + +def test_rdp_simplify_polygon(): + print(ge.rdp_simplify_polygon(polygon, 0)) + + +def test_rdp_simplify_polygon2(): + print(ge.rdp_simplify_polygon2(polygon, 5)) + + +def test_minimum_area_bbox(): + print(ge.minimum_area_bbox(polygon)) + assert ge.minimum_area_bbox(polygon) != ([np.nan, np.nan], 0.0, 0.0, np.nan, np.nan) + + +def test_translate(): + global img + img = np.ones((12, 12), dtype=np.float64) + img = sp.translate(img, 1.2, 1.3, "constant", 0) + print(img) + with pytest.raises(RuntimeError): + _img = np.ones((12, 12, 12), dtype=np.float64) + _img = sp.translate(_img, 1.2, 1.3, "constant", 0) + + +def test_gaussian_filter(): + global img + img = sp.gaussian_filter(img, 0.75) + print(img) + + +def test_find_median_pixel(): + img = np.array(range(100), dtype=np.uint16) + img.shape = (10, 10) + print(sp.find_median_pixel(img, 0.5)) + print(sp.find_median_pixel(img, 0.2)) + + +def test_extract_times(): + times1 = [0, 0.2, 1, 1.5, 2.3, 3.3, 4, 5] + times2 = [-1, 3, 4, 4.3, 4.7] + print(sp.extract_times((times1, times2), "union")) + print(sp.extract_times((times1, times2), "inter")) + + +def test_resample_time_serie(): + x = range(10) + y = range(10) + times = [0, 0.2, 1, 1.5, 2.3, 3.3, 4, 5, 5.6, 9.9, 10, 12, 13] + print(sp.resample_time_serie(x, y, times)) + print(sp.resample_time_serie(x, y, times, 0)) + print(sp.resample_time_serie(x, y, times, None, False)) + + +@pytest.fixture +def img(): + img = np.ones((20, 12), dtype=np.uint16) + return img + + +@pytest.fixture +def jpegls_encoded(img): + return sp.jpegls_encode(img) + + +def test_jpegls_encode(img): + c = sp.jpegls_encode(img) + print(len(c)) + + +def test_jpegls_decode(img, jpegls_encoded): + _img = sp.jpegls_decode(jpegls_encoded, 12, 20) + assert (_img == img).all() + print(img) + + +def test_bad_pixels(): + img = np.ones((20, 12), dtype=np.uint16) + b = bp.BadPixels(img) + print(b.correct(img)) + del b + + +def test_label_image(): + polygon = [[0, 0], [1, 0], [1.2, 0.2], [1.8, 0.1], [5, 5], [3.2, 5], [0, 9]] + polygon2 = [[15, 15], [16, 17], [14, 17]] + img = np.zeros((20, 20), np.int32) + img = ge.draw_polygon(img, polygon, 5) + img = ge.draw_polygon(img, polygon2, 5) + print(img) + print(sp.label_image(img)) + print(sp.keep_largest_area(img)) + + +def test_ir_saver_movie(): + img0 = np.zeros((20, 20), dtype=np.int32) + img1 = np.ones((20, 20), dtype=np.int32) + s = IRSaver("test.h264", 20, 20, 20, 8) + s.add_image(img0, 0) + s.add_image(img1, 1) + s.close() + + m = IRMovie.from_filename("test.h264") + print(m.images) + print(m.image_size) + print(m.timestamps) + print(m.calibrations) + print(m.filename) + print(m.calibration_files) + print(m[0]) + print(m[1]) + + +def test_wrong_filename_video_file_format(wrong_filename): + with pytest.raises(RuntimeError): + get_filename(0) + video_file_format(wrong_filename) + + +def test_set_global_emissivity(movie: IRMovie): + movie.emissivity diff --git a/tests/python/test_video_io.py b/tests/python/test_video_io.py new file mode 100644 index 00000000..84263b99 --- /dev/null +++ b/tests/python/test_video_io.py @@ -0,0 +1,172 @@ +import os +import tempfile +import time +from pathlib import Path + +import numpy as np +import pytest +from librir.video_io import IRMovie, IRSaver +from librir.video_io.rir_video_io import ( + FILE_FORMAT_H264, + FILE_FORMAT_PCR, + camera_saturate, + correct_PCR_file, + get_emissivity, + h264_get_high_errors, + h264_get_low_errors, + support_emissivity, +) + + +@pytest.mark.slow +def test_record_movie_with_lossless_compression(images): + temp_folder = Path(tempfile.gettempdir()) + + start = time.time() + + # create saver by specifying the output filename (any suffix), width and height + s = IRSaver(temp_folder / "my_file.bin", 640, 512) + + # set some parameters + s.set_parameter("compressionLevel", 8) # maximum compression + s.set_parameter("GOP", 20) # Group Of Pictures set to 20 + s.set_parameter("threads", 4) # Compress using 4 threads + # Define 4 slices to allow multithreaded video reading + s.set_parameter("slices", 4) + + # add global attributes + s.set_global_attributes( + { + "Title": "A new video!", + "Description": "Test case for lossless video compression", + } + ) + + # add images + for i in range(len(images)): + # The second argument is the image timestamp in nanoseconds + # Wa could also add frame attributes by passing a dict as third argument + s.add_image(images[i], i * 1e6) + + # close video + s.close() + + elapsed = time.time() - start + theoric_size = len(images) * images[0].shape[0] * images[0].shape[1] * 2 + file_size = os.stat(temp_folder / "my_file.bin").st_size + + print( + "Compression took {} seconds ({} frames/s)".format( + elapsed, len(images) / elapsed + ) + ) + print("Compression factor is {}".format(float(theoric_size) / file_size)) + m = IRMovie.from_filename(temp_folder / "my_file.bin") + + assert m.images == len(images) + assert m.image_size == (512, 640) + assert m.filename == Path(temp_folder / "my_file.bin") + + print("Number of images: ", m.images) + print("First timestamps (s): ", m.timestamps[0:10]) + print("Frame size: ", m.image_size) + print("Filename: ", m.filename) + print("Global attributes: ", m.attributes) + print("Image as position 5: ", m.load_pos(5)) + print("Image as t = 0s: ", m.load_secs(0)) + + +@pytest.mark.slow +def test_record_movie_with_lossy_compression(images): + ######### + # Record video using lossy compression + ######### + temp_folder = Path(tempfile.gettempdir()) + start = time.time() + + # create saver by specifying the output filename (any suffix), width and height + s = IRSaver(temp_folder / "my_file2.bin", 640, 512) + + # set some parameters + s.set_parameter("compressionLevel", 8) # maximum compression + s.set_parameter("GOP", 20) # Group Of Pictures set to 20 + s.set_parameter("threads", 4) # Compress using 4 threads + # Define 4 slices to allow multithreaded video reading + s.set_parameter("slices", 4) + s.set_parameter("lowValueError", 3) # Set low and high compression errors to 3 + # Set low and high compression errors to 3 + s.set_parameter("highValueError", 3) + # For this example, remove the error reduction factor + s.set_parameter("stdFactor", 0) + + # add images + for i in range(len(images)): + s.add_image_lossy(images[i], i * 1e6) + low_errors = h264_get_low_errors(s.handle) + high_errors = h264_get_high_errors(s.handle) + # close video + s.close() + + elapsed = time.time() - start + theoric_size = len(images) * images[0].shape[0] * images[0].shape[1] * 2 + file_size = os.stat(temp_folder / "my_file2.bin").st_size + + print( + "Lossy compression took {} seconds ({} frames/s)".format( + elapsed, len(images) / elapsed + ) + ) + print("Lossy compression factor is {}".format(float(theoric_size) / file_size)) + + +def test_calibration_files(movie: IRMovie): + cfiles = movie.calibration_files + + +@pytest.fixture(scope="session") +def pcr_filename(images): + temp_folder = Path(tempfile.gettempdir()) + filename = temp_folder / "tmp.pcr" + header = np.zeros((256,), dtype=np.uint32) + data = np.array(images, dtype=np.uint16) + with open(filename, "wb") as f: + f.write(header) + f.write(data) + correct_PCR_file(filename, data.shape[0], data.shape[1], 50) + yield filename + os.unlink(filename) + + +def test_correct_PCR_file(pcr_filename, images): + data = np.array(images, dtype=np.uint16) + correct_PCR_file(pcr_filename, data.shape[0], data.shape[1], 50) + with IRMovie.from_filename(pcr_filename) as mov: + assert mov.video_file_format == FILE_FORMAT_PCR + + +def test_pcr2h264(pcr_filename): + with IRMovie.from_filename(pcr_filename) as mov: + res = mov.pcr2h264() + + with IRMovie.from_filename(res) as mov: + assert mov.video_file_format == FILE_FORMAT_H264 + + +def test_get_emissivity(movie: IRMovie): + get_emissivity(movie.handle) + + +def test_support_emissivity(movie: IRMovie): + support_emissivity(movie.handle) + + +def test_enable_bad_pixels(movie: IRMovie): + assert not movie.bad_pixels_correction + movie.bad_pixels_correction = True + assert movie.bad_pixels_correction + movie.bad_pixels_correction = False + assert not movie.bad_pixels_correction + + +def test_camera_saturate(movie: IRMovie): + camera_saturate(movie.handle)