diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml new file mode 100644 index 0000000..10524a1 --- /dev/null +++ b/.github/workflows/cmake.yml @@ -0,0 +1,50 @@ +name: CMake +on: + push: + workflow_dispatch: + pull_request: + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally + # well on Windows or Mac. You can convert this to a matrix build if you need + # cross-platform coverage. + # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/configuring-a-workflow#configuring-a-build-matrix + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: | + cmake -E make_directory ${{runner.workspace}}/build + sudo apt-get install libopencv-dev libusb-1.0-0-dev + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{runner.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config $BUILD_TYPE + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C $BUILD_TYPE diff --git a/.gitignore b/.gitignore index aad7225..cee856d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,10 @@ tags *.o *.a *.so +.vs/ +out/** +CMakeSettings.json +macbuild +pixelmathA.c +seek.py +test_frames \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index ec204c6..705d782 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ addons: packages: - libopencv-dev - libusb-1.0-0-dev - - libboost-program-options-dev script: - mkdir -p build diff --git a/CMakeLists.txt b/CMakeLists.txt index 4332a0a..067cddf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ project(libseek-thermal) -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.5) # Request C++11 standard, using new CMake variables. set (CMAKE_CXX_STANDARD 11) @@ -12,7 +12,11 @@ set (WITH_ADDRESS_SANITIZER false CACHE BOOL "Enable address sanitizer") set (WITH_DEBUG_VERBOSITY false CACHE BOOL "Enable verbose stdout messages") set (default_build_type "Release") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") + +if (NOT MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wextra") +endif () if (WITH_DEBUG_VERBOSITY) add_definitions(-DSEEK_DEBUG) @@ -20,7 +24,20 @@ endif () message (STATUS "Build type: ${CMAKE_BUILD_TYPE}") +include (MacroLogFeature) + +find_package (LibUSB) +macro_log_feature (LIBUSB_FOUND "libusb" "Required to communicate via USB" "https://libusb.info/" TRUE) + find_package (OpenCV REQUIRED) +macro_log_feature (OPENCV_FOUND "OpenCV" "Required to handle image processing" "https://opencv.org/" TRUE) + +if (MSVC) + include_directories (${PROJECT_SOURCE_DIR}/win) +endif () + add_subdirectory (src) add_subdirectory (examples) + +macro_display_feature_log() diff --git a/IR camera demo.docx b/IR camera demo.docx new file mode 100644 index 0000000..a60c4fa Binary files /dev/null and b/IR camera demo.docx differ diff --git a/README.md b/README.md index abf24a7..435c3ab 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # libseek-thermal -[![Build Status](https://travis-ci.org/maartenvds/libseek-thermal.svg?branch=master)](https://travis-ci.org/maartenvds/libseek-thermal) master - -[![Build Status](https://travis-ci.org/maartenvds/libseek-thermal.svg?branch=development)](https://travis-ci.org/maartenvds/libseek-thermal) development +![CMake](https://github.com/OpenThermal/libseek-thermal/workflows/CMake/badge.svg?branch=master) ## Description @@ -33,7 +31,6 @@ Dependencies: * cmake * libopencv-dev (>= 2.4) * libusb-1.0-0-dev -* libboost-program-options-dev NOTE: you can just 'apt-get install' all libs above @@ -41,7 +38,7 @@ NOTE: you can just 'apt-get install' all libs above cd libseek-thermal mkdir build cd build -cmake ../ +cmake .. make ``` @@ -58,27 +55,43 @@ For more build options (debug/release, install prefix, opencv install dir, addre cmake-gui ../ ``` -## Getting USB access +### Windows -You need to add a udev rule to be able to run the program as non root user: +This library and example programs can be built on Windows with multiple versions of Microsoft Visual Studio. This is most readily done with Visual Studio 2015 or newer, as dependancy binaries for Windows are available from the official projects, as described below. -Udev rule: +libusb is required, and Windows binaries are available from the [offical libusb project](https://libusb.info/). +* Download the latest binary release (files ending in `.7z`) from [libusb GitHub Releases](https://github.com/libusb/libusb/releases) +* Extract the archive +* If using libusb-1.0.24 or greater you will need to copy the contents (`MS64` & `MS32`) of the `VS201X` directory approriate for your version of visual studio up into the root `libusb-1.0.X` directory. +* Set `LIBUSB_DIR` to the extracted directory (e.g., `C:\local\libusb-1.0.23`) -``` -SUBSYSTEM=="usb", ATTRS{idVendor}=="289d", ATTRS{idProduct}=="XXXX", MODE="0666", GROUP="users" -``` +OpenCV is required, and Windows binaries are available from the [official OpenCV project](https://opencv.org/). +* Download one of the Windows releases from the [OpenCV releases page](https://opencv.org/releases/) (3.x and 4.x work) +* Run the self-extracting archive +* Set `OpenCV_DIR` to the build directory containing `OpenCVConfig.cmake` (e.g., `C:\local\opencv-3.4.10\build`) + +Consider setting the `CMAKE_INSTALL_PREFIX` to a location in your build directory. Then after running the INSTALL target, copy the libusb and OpenCV libraries (e.g., `libusb-1.0.dll` and `opencv_world430.dll`) to the `bin\` directory containing `seek_test.exe`. + +Before this library or example programs will work, you will need to set the driver for the USB device. The simplest way to do this is to use [Zadig](https://zadig.akeo.ie/). Run Zadig, then select `iAP Interface`, select `libusb-win32`, then click `Install Driver`. + +## Configuring USB access -Replace 'XXXX' with: -* 0010: Seek Thermal Compact/CompactXR -* 0011: Seek Thermal CompactPRO -or manually chmod the device file after plugging the usb cable: +You need to add a udev rule to be able to run the program as non root user, and another rule to prevent the kernel from putting the device to sleep. (If the camera is put to sleep, running any utility will fail with `Error: control transfer failed: LIBUSB_ERROR_PIPE` and you will be forced to unplug the camera and plug it back in again.) + +Udev rules: ``` -sudo chmod 666 /dev/bus/usb/00x/00x +SUBSYSTEM=="usb", ATTRS{idVendor}=="289d", ATTRS{idProduct}=="0010", MODE="0666", GROUP="users" +SUBSYSTEM=="usb", ATTRS{idVendor}=="289d", ATTRS{idProduct}=="0010", TEST=="power/control", ATTR{power/control}:="on" + +SUBSYSTEM=="usb", ATTRS{idVendor}=="289d", ATTRS{idProduct}=="0011", MODE="0666", GROUP="users" +SUBSYSTEM=="usb", ATTRS{idVendor}=="289d", ATTRS{idProduct}=="0011", TEST=="power/control", ATTR{power/control}:="on" ``` -with '00x' the usb bus found with the lsusb command +Put the above in `/etc/udev/rules.d/90-seekcam.rules`. + +The lines with `MODE=` set the device to be readable and writable by everyone, and the lines with `power/control` prevent sleep. `idProduct==0011` corresponds to the Seek Pro and `idProduct==0010` applies to the Seek Compact and Seek Compact XR. ## Running example binaries @@ -86,6 +99,7 @@ with '00x' the usb bus found with the lsusb command ./examples/seek_test # Minimal Thermal Compact/CompactXR example ./examples/seek_test_pro # Minimal Thermal CompactPRO example ./examples/seek_viewer # Example with more features supporting all cameras, run with --help for command line options +./examples/seek_snapshot # Takes still images, run with --help for command line options ``` Or if you installed the library you can run from any location: @@ -96,13 +110,19 @@ seek_test_pro seek_viewer ``` -Some example command lines: +### seek_viewer +seek_viewer is bare bones UI for the seek thermal devices. It can display video on screen, record it to a file, or stream it to a v4l2 loopback device for integration with image processing pipelines. It supports image rotation, scaling, and color mapping using any of the OpenCV color maps. While running `f` will set the display output full screen and `s` will freezeframe. ``` -seek_viewer --camtype=seekpro --colormap=11 --rotate=0 # view color mapped thermal video -seek_viewer --camtype=seekpro --colormap=11 --rotate=0 --output=seek.avi # record color mapped thermal video +seek_viewer --camtype=seekpro --colormap=11 --rotate=0 # view color mapped thermal video +seek_viewer --camtype=seekpro --colormap=11 --mode=file --output=seek.avi # record color mapped thermal video +seek_viewer --camtype=seekpro --colormap=11 --mode=v4l2 --output=/dev/video0 # stream the thermal video to v4l2 device ``` +### seek_snapshot +seek_snapshot takes still images. This is useful for intergrating into shell scripts. It supports rotation and color mapping in the same manner as seek_viewer. Run with --help for all options. + + ## Linking the library to another program After you installed the library you can compile your own programs/libs with: @@ -131,23 +151,23 @@ Procedure: 2) Run: ``` # when using the Seek Thermal compact -seek_create_flat_field -c seek seek_ffc.png +seek_create_flat_field -tseek # When using the Seek Thermal compact pro -seek_create_flat_field -c seekpro seekpro_ffc.png +seek_create_flat_field -tseekpro ``` -The program will run for a few seconds and produces a .png file. +The program will run for a few seconds and produces a flat_field.png file. 3) Provide the produced .png file to one of the test programs: ``` # when using the Seek Thermal compact -seek_test seek_ffc.png -seek_viewer -t seek -F seek_ffc.png +seek_test flat_field.png +seek_viewer -t seek -F flat_field.png # When using the Seek Thermal compact pro -seek_test_pro seekpro_ffc.png -seek_viewer -t seekpro -F seekpro_ffc.png +seek_test_pro flat_field.png +seek_viewer -t seekpro -F flat_field.png ``` ## Framebuffer Implementation In order to run this program from a raspberry pi zero connected to a small 2.2" 320x240 screen, I have implemented a framebuffer diff --git a/cmake/FindLibUSB.cmake b/cmake/FindLibUSB.cmake new file mode 100644 index 0000000..2ea91ca --- /dev/null +++ b/cmake/FindLibUSB.cmake @@ -0,0 +1,143 @@ +# This is a modified version of the file written by Hedrik Sattler, +# from the OpenOBEX project (licensed GPLv2/LGPL). (If this is not correct, +# please contact us so we can attribute the author appropriately.) +# +# https://github.com/zuckschwerdt/openobex/blob/master/CMakeModules/FindLibUSB.cmake +# http://dev.zuckschwerdt.org/openobex/ +# +# Find libusb-1.0 +# +# It will use PkgConfig if present and supported, otherwise this +# script searches for binary distribution in the path defined by +# the LIBUSB_DIR variable. +# +# Define LIBUSB_SKIP_VERSION_CHECK=Yes to skip the execution of a program to fetch +# libusb's version number. LIBUSB_VERSION will not be set if this if this is used. +# To check the version number, this script expects CMAKE_HELPERS_SOURCE_DIR to +# be defined with the path to libusb_version.c. +# +# The following standard variables get defined: +# LIBUSB_FOUND: true if LibUSB was found +# LIBUSB_HEADER_FILE: the location of the C header file +# LIBUSB_INCLUDE_DIRS: the directorys that contain headers +# LIBUSB_LIBRARIES: the library files +# LIBUSB_VERSION the detected libusb version +# LIBUSB_HAVE_GET_VERSION True if libusb has libusb_get_version() + + +include(CheckLibraryExists) +include(CheckIncludeFile) + + +# In Linux, folks should generally be able to simply fetch the libusb library and +# development packages from their distros package repository. Windows users will +# likely want to fetch a binary distribution +# See http://www.libusb.org/wiki/windows_backend#LatestBinarySnapshots +if(WIN32) + set(LIBUSB_DIR + $ENV{LIBUSB_DIR} + CACHE + PATH + "Path to libusb files. (This is generally only needed for Windows users who downloaded binary distributions.)" + ) +endif() + +# FreeBSD has built-in libusb since 800069 +if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") + exec_program(sysctl ARGS -n kern.osreldate OUTPUT_VARIABLE FREEBSD_VERSION) + set(MIN_FREEBSD_VERSION 800068) + if(FREEBSD_VERSION GREATER ${MIN_FREEBSD_VERSION}) + set(LIBUSB_FOUND TRUE) + set(LIBUSB_SKIP_VERSION_CHECK TRUE) + set(LIBUSB_INCLUDE_DIRS "/usr/include") + set(LIBUSB_HEADER_FILE "${LIBUSB_INCLUDE_DIRS}/libusb.h") + set(LIBUSB_LIBRARIES "usb") + set(LIBUSB_LIBRARY_DIRS "/usr/lib/") + endif(FREEBSD_VERSION GREATER ${MIN_FREEBSD_VERSION}) +endif(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") + +find_package(PkgConfig) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PKGCONFIG_LIBUSB libusb-1.0 QUIET) +endif(PKG_CONFIG_FOUND) + +if(PKGCONFIG_LIBUSB_FOUND AND NOT LIBUSB_FOUND) + set(LIBUSB_INCLUDE_DIRS ${PKGCONFIG_LIBUSB_INCLUDE_DIRS}) + foreach(i ${PKGCONFIG_LIBUSB_LIBRARIES}) + string(REGEX MATCH "[^-]*" ibase "${i}") + find_library(${ibase}_LIBRARY + NAMES ${i} + PATHS ${PKGCONFIG_LIBUSB_LIBRARY_DIRS} + ) + if(${ibase}_LIBRARY) + list(APPEND LIBUSB_LIBRARIES ${${ibase}_LIBRARY}) + endif(${ibase}_LIBRARY) + mark_as_advanced(${ibase}_LIBRARY) + endforeach(i) + +else(PKGCONFIG_LIBUSB_FOUND) + if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + # The libusbx binary distribution contains several libs. + # Use the lib that got compiled with the same compiler. + if(MSVC) + if(CMAKE_CL_64) + set(LIBUSB_LIBRARY_PATH_SUFFIX MS64/dll) + else(CMAKE_CL_64) + set(LIBUSB_LIBRARY_PATH_SUFFIX MS32/dll) + endif(CMAKE_CL_64) + elseif(CMAKE_COMPILER_IS_GNUCC) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(LIBUSB_LIBRARY_PATH_SUFFIX MinGW32/dll) + else(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(LIBUSB_LIBRARY_PATH_SUFFIX MinGW64/dll) + endif(CMAKE_SIZEOF_VOID_P EQUAL 8) + endif(MSVC) + else() + set(LIBUSB_LIBRARY_PATH_SUFFIX lib) + set(LIBUSB_EXTRA_PATHS /usr /usr/local /opt/local) + endif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + + find_file(LIBUSB_HEADER_FILE + NAMES + libusb.h + PATHS + ${LIBUSB_DIR} + ${LIBUSB_EXTRA_PATHS} + PATH_SUFFIXES + include include/libusbx-1.0 include/libusb-1.0 + ) + mark_as_advanced(LIBUSB_HEADER_FILE) + get_filename_component(LIBUSB_INCLUDE_DIRS "${LIBUSB_HEADER_FILE}" PATH) + + + find_library(usb_LIBRARY + NAMES + libusb-1.0 usb-1.0 + PATHS + ${LIBUSB_DIR} + ${LIBUSB_EXTRA_PATHS} + PATH_SUFFIXES + ${LIBUSB_LIBRARY_PATH_SUFFIX} lib + ) + mark_as_advanced(usb_LIBRARY) + if(usb_LIBRARY) + set(LIBUSB_LIBRARIES ${usb_LIBRARY}) + endif(usb_LIBRARY) + +endif(PKGCONFIG_LIBUSB_FOUND AND NOT LIBUSB_FOUND) + +if(NOT LIBUSB_INCLUDE_DIRS) + message("LIBUSB_INCLUDE_DIRS Not Found") +endif(NOT LIBUSB_INCLUDE_DIRS) +if(NOT LIBUSB_LIBRARIES) + message("LIBUSB_LIBRARIES Not Found") +endif(NOT LIBUSB_LIBRARIES) + +if(LIBUSB_INCLUDE_DIRS AND LIBUSB_LIBRARIES) + set(LIBUSB_FOUND TRUE) +endif(LIBUSB_INCLUDE_DIRS AND LIBUSB_LIBRARIES) + +if(LIBUSB_FOUND) + set(CMAKE_REQUIRED_INCLUDES "${LIBUSB_INCLUDE_DIRS}") + check_include_file("{LIBUSB_HEADER_FILE}" LIBUSB_FOUND) +endif(LIBUSB_FOUND) diff --git a/cmake/MacroLogFeature.cmake b/cmake/MacroLogFeature.cmake new file mode 100644 index 0000000..86e1c3c --- /dev/null +++ b/cmake/MacroLogFeature.cmake @@ -0,0 +1,146 @@ +# This file defines the Feature Logging macros. +# +# MACRO_LOG_FEATURE(VAR FEATURE DESCRIPTION URL [REQUIRED [MIN_VERSION [COMMENTS]]]) +# Logs the information so that it can be displayed at the end +# of the configure run +# VAR : TRUE or FALSE, indicating whether the feature is supported +# FEATURE: name of the feature, e.g. "libjpeg" +# DESCRIPTION: description what this feature provides +# URL: home page +# REQUIRED: TRUE or FALSE, indicating whether the featue is required +# MIN_VERSION: minimum version number. empty string if unneeded +# COMMENTS: More info you may want to provide. empty string if unnecessary +# +# MACRO_DISPLAY_FEATURE_LOG() +# Call this to display the collected results. +# Exits CMake with a FATAL error message if a required feature is missing +# +# Example: +# +# INCLUDE(MacroLogFeature) +# +# FIND_PACKAGE(JPEG) +# MACRO_LOG_FEATURE(JPEG_FOUND "libjpeg" "Support JPEG images" "http://www.ijg.org" TRUE "3.2a" "") +# ... +# MACRO_DISPLAY_FEATURE_LOG() + +# Copyright (c) 2006, Alexander Neundorf, +# Copyright (c) 2006, Allen Winter, +# Copyright (c) 2009, Sebastian Trueg, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +IF (NOT _macroLogFeatureAlreadyIncluded) + SET(_file ${CMAKE_BINARY_DIR}/MissingRequirements.txt) + IF (EXISTS ${_file}) + FILE(REMOVE ${_file}) + ENDIF (EXISTS ${_file}) + + SET(_file ${CMAKE_BINARY_DIR}/EnabledFeatures.txt) + IF (EXISTS ${_file}) + FILE(REMOVE ${_file}) + ENDIF (EXISTS ${_file}) + + SET(_file ${CMAKE_BINARY_DIR}/DisabledFeatures.txt) + IF (EXISTS ${_file}) + FILE(REMOVE ${_file}) + ENDIF (EXISTS ${_file}) + + SET(_macroLogFeatureAlreadyIncluded TRUE) +ENDIF (NOT _macroLogFeatureAlreadyIncluded) + + +MACRO(MACRO_LOG_FEATURE _var _package _description _url ) # _required _minvers _comments) + + STRING(TOUPPER "${ARGV4}" _required) + SET(_minvers "${ARGV5}") + SET(_comments "${ARGV6}") + + IF (${_var}) + SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/EnabledFeatures.txt) + ELSE (${_var}) + IF ("${_required}" STREQUAL "TRUE") + SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/MissingRequirements.txt) + ELSE ("${_required}" STREQUAL "TRUE") + SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/DisabledFeatures.txt) + ENDIF ("${_required}" STREQUAL "TRUE") + ENDIF (${_var}) + + SET(_logtext " * ${_package}") + + IF (NOT ${_var}) + IF (${_minvers} MATCHES ".*") + SET(_logtext "${_logtext} (${_minvers} or higher)") + ENDIF (${_minvers} MATCHES ".*") + SET(_logtext "${_logtext} <${_url}>\n ") + ELSE (NOT ${_var}) + SET(_logtext "${_logtext} - ") + ENDIF (NOT ${_var}) + + SET(_logtext "${_logtext}${_description}") + + IF (NOT ${_var}) + IF (${_comments} MATCHES ".*") + SET(_logtext "${_logtext}\n ${_comments}") + ENDIF (${_comments} MATCHES ".*") +# SET(_logtext "${_logtext}\n") #double-space missing features? + ENDIF (NOT ${_var}) + + FILE(APPEND "${_LOGFILENAME}" "${_logtext}\n") + +ENDMACRO(MACRO_LOG_FEATURE) + + +MACRO(MACRO_DISPLAY_FEATURE_LOG) + + SET(_missingFile ${CMAKE_BINARY_DIR}/MissingRequirements.txt) + SET(_enabledFile ${CMAKE_BINARY_DIR}/EnabledFeatures.txt) + SET(_disabledFile ${CMAKE_BINARY_DIR}/DisabledFeatures.txt) + + IF (EXISTS ${_missingFile} OR EXISTS ${_enabledFile} OR EXISTS ${_disabledFile}) + SET(_printSummary TRUE) + ENDIF (EXISTS ${_missingFile} OR EXISTS ${_enabledFile} OR EXISTS ${_disabledFile}) + + IF(_printSummary) + SET(_missingDeps 0) + IF (EXISTS ${_enabledFile}) + FILE(READ ${_enabledFile} _enabled) + FILE(REMOVE ${_enabledFile}) + SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following external packages were located on your system.\n-- This installation will have the extra features provided by these packages.\n-----------------------------------------------------------------------------\n${_enabled}") + ENDIF (EXISTS ${_enabledFile}) + + + IF (EXISTS ${_disabledFile}) + SET(_missingDeps 1) + FILE(READ ${_disabledFile} _disabled) + FILE(REMOVE ${_disabledFile}) + SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following OPTIONAL packages could NOT be located on your system.\n-- Consider installing them to enable more features from this software.\n-----------------------------------------------------------------------------\n${_disabled}") + ENDIF (EXISTS ${_disabledFile}) + + + IF (EXISTS ${_missingFile}) + SET(_missingDeps 1) + FILE(READ ${_missingFile} _requirements) + SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following REQUIRED packages could NOT be located on your system.\n-- You must install these packages before continuing.\n-----------------------------------------------------------------------------\n${_requirements}") + FILE(REMOVE ${_missingFile}) + SET(_haveMissingReq 1) + ENDIF (EXISTS ${_missingFile}) + + + IF (NOT ${_missingDeps}) + SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- Congratulations! All external packages have been found.") + ENDIF (NOT ${_missingDeps}) + + + MESSAGE(${_summary}) + MESSAGE("-----------------------------------------------------------------------------\n") + + + IF(_haveMissingReq) + MESSAGE(FATAL_ERROR "Exiting: Missing Requirements") + ENDIF(_haveMissingReq) + + ENDIF(_printSummary) + +ENDMACRO(MACRO_DISPLAY_FEATURE_LOG) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 0bcb88c..76901cd 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,27 +1,29 @@ include_directories ( ${libseek-thermal_SOURCE_DIR}/src ${OpenCV_INCLUDE_DIRS} - /usr/include/libusb-1.0 + ${LIBUSB_INCLUDE_DIRS} + ) link_libraries ( seek_static - -lboost_program_options ${OpenCV_LIBRARIES} - -lusb-1.0 - -lwiringPi + ${LIBUSB_LIBRARIES} ) add_executable (seek_test seek_test.cpp) add_executable (seek_test_pro seek_test_pro.cpp) add_executable (seek_viewer seek_viewer.cpp args.h) add_executable (seek_create_flat_field seek_create_flat_field.cpp) +add_executable (seek_snapshot seek_snapshot.cpp) add_executable (seek_viewer_fb seek_viewer_fb.cpp args.h) + install (TARGETS seek_test seek_test_pro seek_viewer - seek_viewer_fb + seek_create_flat_field + seek_snapshot seek_create_flat_field DESTINATION "bin" ) diff --git a/examples/seek_create_flat_field.cpp b/examples/seek_create_flat_field.cpp index 9742e34..5a69b8b 100644 --- a/examples/seek_create_flat_field.cpp +++ b/examples/seek_create_flat_field.cpp @@ -4,100 +4,87 @@ */ #include #include "seek.h" -#include "boost/program_options.hpp" #include +#include "args.h" -namespace po = boost::program_options; - -static po::options_description get_description() +int main(int argc, char** argv) { - po::options_description description; + int i; + cv::Mat frame_u16, frame, avg_frame; + LibSeek::SeekThermalPro seekpro; + LibSeek::SeekThermal seek; + LibSeek::SeekCam* cam; - description.add_options() - ("help,h", "Show this help") - ("cam,c", po::value()->default_value(std::string("seekpro")), "Camera type: choises: seekpro|seek" - " -> default is 'seekpro'") - ("smoothing,s", po::value()->default_value(100), "Number of images to average before creating an output image") - ("outfile", po::value()->required(), "Name of the output file"); + args::ArgumentParser parser("Create Flat Frame"); + args::HelpFlag help(parser, "help", "Display this help menu", { 'h', "help" }); + args::ValueFlag _output(parser, "outfile", "Name of the file to write to - default flat_field.png", { 'o', "outfile" }); + args::ValueFlag _smoothing(parser, "smoothing", "Smoothing factor, number of frames to collect and average - default 100", { 's', "smoothing" }); + args::ValueFlag _camtype(parser, "camtype", "Seek Thermal Camera Model - seek or seekpro", { 't', "camtype" }); + args::ValueFlag _warmup(parser, "warmup", "Warmup, number of frames to discard before sampling - default 10", { 'w', "warmup" }); - return description; -} -static po::variables_map parse_options(int argc, char** argv) -{ - po::positional_options_description positional_description; - positional_description.add("outfile", -1); - po::variables_map variables_map; - - auto description = get_description(); - po::store(po::command_line_parser(argc, argv) - .options(description) - .positional(positional_description) - .run(), variables_map); - - if (variables_map.count("help") > 0) { - return variables_map; + // Parse arguments + try { + parser.ParseCLI(argc, argv); } - - po::notify(variables_map); - return variables_map; -} - -static void print_help(const char* program_name) -{ - std::cout << "Usage: " << program_name << " [options] outputfilename" << std::endl; - std::cout << get_description() << std::endl; -} - -static bool process_command_line_options(int argc, char **argv, po::variables_map& options) -{ - try - { - options = parse_options(argc, argv); + catch (args::Help) { + std::cout << parser; + return 0; } - catch (po::error &e) - { - std::cerr << "ERROR: " << e.what() << std::endl; - print_help(argv[0]); - return false; + catch (args::ParseError e) { + std::cerr << e.what() << std::endl; + std::cerr << parser; + return 1; } - - if (options.find("help") != options.end()) - { - print_help(argv[0]); - return false; + catch (args::ValidationError e) { + std::cerr << e.what() << std::endl; + std::cerr << parser; + return 1; } - return true; -} + // Defaults + int smoothing = 100; + if (_smoothing) + smoothing = args::get(_smoothing); -int main(int argc, char** argv) -{ - int i; - cv::Mat frame_u16, frame, avg_frame; - LibSeek::SeekThermalPro seekpro; - LibSeek::SeekThermal seek; - LibSeek::SeekCam* cam; - po::variables_map options; + int warmup = 10; + if (_warmup) + warmup = args::get(_warmup); - if (!process_command_line_options(argc, argv, options)) - return -1; + std::string outfile = "flat_field.png"; + if (_output) + outfile = args::get(_output); + + std::string camtype = "seekpro"; + if (_camtype) + camtype = args::get(_camtype); - int smoothing = options["smoothing"].as(); - std::string outfile = options["outfile"].as(); + if (camtype == "seekpro"){ - if (options["cam"].as() == "seekpro") cam = &seekpro; - else + } + else { cam = &seek; + } if (!cam->open()) { - std::cout << "failed to open seek cam" << std::endl; + std::cout << "failed to open " << camtype << " cam" << std::endl; return -1; } - for (i=0; igrab()) { + std::cout << "no more LWIR img" << std::endl; + return -1; + } + cam->retrieve(frame_u16); + cv::waitKey(10); + } + std::cout << "warmup complete" << std::endl; + + // Aquire frames + for (i = 0; i < smoothing; i++) { if (!cam->grab()) { std::cout << "no more LWIR img" << std::endl; return -1; @@ -111,10 +98,10 @@ int main(int argc, char** argv) } else { avg_frame += frame; } - cv::waitKey(10); } + // Average the collected frames avg_frame /= smoothing; avg_frame.convertTo(frame_u16, CV_16UC1); diff --git a/examples/seek_snapshot.cpp b/examples/seek_snapshot.cpp new file mode 100644 index 0000000..a63c516 --- /dev/null +++ b/examples/seek_snapshot.cpp @@ -0,0 +1,153 @@ +/* + * Test program seek lib + * Author: Maarten Vandersteegen + */ +#include +#include +#include "seek.h" +#include +#include "args.h" +using namespace cv; +using namespace LibSeek; + + +int main(int argc, char** argv) +{ + int i; + cv::Mat frame_u16, frame, avg_frame; + LibSeek::SeekThermalPro seekpro; + LibSeek::SeekThermal seek; + LibSeek::SeekCam* cam; + + args::ArgumentParser parser("Capture a single frame"); + args::HelpFlag help(parser, "help", "Display this help menu", { 'h', "help" }); + args::ValueFlag _output(parser, "outfile", "Name of the file to write to - default output.png", { 'o', "outfile" }); + args::ValueFlag _smoothing(parser, "smoothing", "Smoothing factor, number of frames to collect and average - default 1", { 's', "smoothing" }); + args::ValueFlag _camtype(parser, "camtype", "Seek Thermal Camera Model - seek or seekpro", { 't', "camtype" }); + args::ValueFlag _warmup(parser, "warmup", "Warmup, number of frames to discard before sampling - default 10", { 'w', "warmup" }); + args::ValueFlag _colormap(parser, "colormap", "Color Map - number between 0 and 21 (see: cv::ColormapTypes for maps available in your version of OpenCV)", { 'c', "colormap" }); + args::ValueFlag _rotate(parser, "rotate", "Rotation - 0, 90, 180 or 270 (default) degrees", { 'r', "rotate" }); + + // Parse arguments + try { + parser.ParseCLI(argc, argv); + } + catch (args::Help) { + std::cout << parser; + return 0; + } + catch (args::ParseError e) { + std::cerr << e.what() << std::endl; + std::cerr << parser; + return 1; + } + catch (args::ValidationError e) { + std::cerr << e.what() << std::endl; + std::cerr << parser; + return 1; + } + + // Defaults + int smoothing = 1; + if (_smoothing) + smoothing = args::get(_smoothing); + + int warmup = 10; + if (_warmup) + warmup = args::get(_warmup); + + std::string outfile = "output.png"; + if (_output) + outfile = args::get(_output); + + std::string camtype = "seek"; + if (_camtype) + camtype = args::get(_camtype); + + // Colormap int corresponding to enum: http://docs.opencv.org/3.2.0/d3/d50/group__imgproc__colormap.html + int colormap = -1; + if (_colormap) + colormap = args::get(_colormap); + + // Rotate default is landscape view to match camera logo/markings + int rotate = 270; + if (_rotate) + rotate = args::get(_rotate); + + // Init correct cam type + if (camtype == "seekpro") { + cam = &seekpro; + } + else { + cam = &seek; + } + + if (!cam->open()) { + std::cout << "failed to open " << camtype << " cam" << std::endl; + return -1; + } + + for (i = 0; i < warmup; i++) { + if (!cam->grab()) { + std::cout << "no more LWIR img" << std::endl; + return -1; + } + cam->retrieve(frame_u16); + cv::waitKey(10); + } + + std::cout << "warmup complete" << std::endl; + + // Aquire frames + for (i = 0; i < smoothing; i++) { + if (!cam->grab()) { + std::cout << "no more LWIR img" << std::endl; + return -1; + } + + cam->retrieve(frame_u16); + frame_u16.convertTo(frame, CV_32FC1); + + if (avg_frame.rows == 0) { + frame.copyTo(avg_frame); + } else { + avg_frame += frame; + } + cv::waitKey(10); + } + + // Average the collected frames + avg_frame /= smoothing; + avg_frame.convertTo(frame_u16, CV_16UC1); + + Mat frame_g8, outframe; // Transient Mat containers for processing + + normalize(frame_u16, frame_u16, 0, 65535, NORM_MINMAX); + + // Convert seek CV_16UC1 to CV_8UC1 + frame_u16.convertTo(frame_g8, CV_8UC1, 1.0 / 256.0); + + // Apply colormap: https://docs.opencv.org/master/d3/d50/group__imgproc__colormap.html#ga9a805d8262bcbe273f16be9ea2055a65 + if (colormap != -1) { + applyColorMap(frame_g8, outframe, colormap); + } + else { + cv::cvtColor(frame_g8, outframe, cv::COLOR_GRAY2BGR); + } + + // Rotate image + if (rotate == 90) { + transpose(outframe, outframe); + flip(outframe, outframe, 1); + } + else if (rotate == 180) { + flip(outframe, outframe, -1); + } + else if (rotate == 270) { + transpose(outframe, outframe); + flip(outframe, outframe, 0); + } + + cv::imwrite(outfile, outframe); + return 0; +} diff --git a/examples/seek_viewer.cpp b/examples/seek_viewer.cpp index 20c20ff..293c6fc 100644 --- a/examples/seek_viewer.cpp +++ b/examples/seek_viewer.cpp @@ -10,12 +10,26 @@ #include #include #include "args.h" +#include +#include + +#ifdef __linux__ +#include +#include +#include +#endif using namespace cv; using namespace LibSeek; +const std::string WINDOW_NAME = "SeekThermal"; + // Setup sig handling static volatile sig_atomic_t sigflag = 0; + + +bool auto_exposure_lock = false; + void handle_sig(int sig) { (void)sig; sigflag = 1; @@ -25,7 +39,6 @@ void handle_sig(int sig) { // cv::Mat m_additional_ffc; // if (fname != std::string()) { // m_additional_ffc = cv::imread(fname, -1); - // if (m_additional_ffc.type() != m_raw_frame.type()) { // std::cerr<<"Error: "< -1) - last[0]=staticMin; - - if (staticMax > -1) - last[1]=staticMax; - - //std::cout< -1) { + min=staticMin; + } else { + last[0]=(2*last[0]+low)/3; + min=last[0]; + } + + if (staticMax > -1) { + max=staticMax; + } else { + last[1]=(2*last[1]+high)/3; + max=last[1]; + } + frame_g16=(inframe-min); //normalize(inframe, frame_g16, 0, 65535, NORM_MINMAX,-1,mask); // Convert seek CV_16UC1 to CV_8UC1 - frame_g16.convertTo(frame_g8, CV_8UC1, 1.0/256.0 ); - //inframe.convertTo(frame_g8,CV_8UC1,1.0/256.0); + + frame_g16.convertTo(frame_g8, CV_8UC1, (256.0/(max-min))); + // Rotate image if (rotate == 90) { transpose(frame_g8, frame_g8); @@ -88,8 +107,9 @@ void process_frame(Mat &inframe, Mat &outframe, float scale, int colormap, int r // Resize image: http://docs.opencv.org/3.2.0/da/d54/group__imgproc__transform.html#ga5bb5a1fea74ea38e1a5445ca803ff121 // Note this is expensive computationally, only do if option set != 1 - if (scale != 1.0) + if (scale != 1.0) { resize(frame_g8, frame_g8, Size(), scale, scale, INTER_LINEAR); + } // Apply colormap: http://docs.opencv.org/3.2.0/d3/d50/group__imgproc__colormap.html#ga9a805d8262bcbe273f16be9ea2055a65 if (colormap != -1) { @@ -99,70 +119,161 @@ void process_frame(Mat &inframe, Mat &outframe, float scale, int colormap, int r } } -int main(int argc, char** argv) -{ +void key_handler(char scancode) { + switch (scancode) { + case 'f': { + int windowFlag = getWindowProperty(WINDOW_NAME, WindowPropertyFlags::WND_PROP_FULLSCREEN) == cv::WINDOW_FULLSCREEN ? cv::WINDOW_NORMAL : cv::WINDOW_FULLSCREEN; + setWindowProperty(WINDOW_NAME, WindowPropertyFlags::WND_PROP_FULLSCREEN, windowFlag); + break; + } + case 's': { + waitKey(0); + break; + } + case 'a': { + auto_exposure_lock = !auto_exposure_lock; + std::cout << "AEL:" << auto_exposure_lock << std::endl; + break; + } + case 'q': { + sigflag = SIGINT; + } + } +} + +#ifdef __linux__ +// Function to setup output to a v4l2 device +int setup_v4l2(std::string output, int width,int height) { + int v4l2 = open(output.c_str(), O_RDWR); + if(v4l2 < 0) { + std::cout << "Error opening v4l2 device: " << strerror(errno) << std::endl; + exit(1); + } + + struct v4l2_format v; + int t; + v.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + t = ioctl(v4l2, VIDIOC_G_FMT, &v); + if( t < 0 ) { + std::cout << "VIDIOC_G_FMT error: " << strerror(errno) << std::endl; + exit(t); + } + + v.fmt.pix.width = width; + v.fmt.pix.height = height; + // BGR is not widely supported in v4l2 clients + v.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24; + v.fmt.pix.sizeimage = width * height * 3; + t = ioctl(v4l2, VIDIOC_S_FMT, &v); + if( t < 0 ) { + std::cout << "VIDIOC_S_FMT error: " << strerror(errno) << std::endl; + exit(t); + } + + std::cout << "Opened v4l2 device" << std::endl; + return v4l2; +} + +void v4l2_out(int v4l2, Mat& outframe) { + // Second colorspace conversion done here as applyColorMap only produces BGR. Do it in place. + cvtColor(outframe, outframe, COLOR_BGR2RGB); + int framesize = outframe.total() * outframe.elemSize(); + int written = write(v4l2, outframe.data, framesize); + if (written < 0) { + std::cout << "Error writing v4l2 device" << std::endl; + close(v4l2); + exit(1); + } +} +#else +int setup_v4l2(std::string output, int width, int height) { + std::cout << "v4l2 is not supported on this platform" << std::endl; + exit(1); + return -1; +} +void v4l2_out(int v4l2, Mat& outframe) {} +#endif // __linux__ + + +int main(int argc, char** argv) { // Setup arguments for parser args::ArgumentParser parser("Seek Thermal Viewer"); args::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"}); - args::ValueFlag _output(parser, "output", "Output Stream - name of the video file to write", {'o', "output"}); + args::ValueFlag _mode(parser, "mode", "The mode to use - v4l2, window, file", {'m', "mode"}); + args::ValueFlag _output(parser, "output", "Name of the file or video device to write to", {'o', "output"}); args::ValueFlag _ffc(parser, "FFC", "Additional Flat Field calibration - provide ffc file", {'F', "FFC"}); args::ValueFlag _fps(parser, "fps", "Video Output FPS - Kludge factor", {'f', "fps"}); args::ValueFlag _scale(parser, "scaling", "Output Scaling - multiple of original image", {'s', "scale"}); - args::ValueFlag _colormap(parser, "colormap", "Color Map - number between 0 and 12", {'c', "colormap"}); + args::ValueFlag _colormap(parser, "colormap", "Color Map - number between 0 and 21 (see: cv::ColormapTypes for maps available in your version of OpenCV)", { 'c', "colormap" }); args::ValueFlag _rotate(parser, "rotate", "Rotation - 0, 90, 180 or 270 (default) degrees", {'r', "rotate"}); args::ValueFlag _camtype(parser, "camtype", "Seek Thermal Camera Model - seek or seekpro", {'t', "camtype"}); args::ValueFlag _scalemin(parser, "scalemin", "Minimum value of color scale", {'m', "scalemin"}); args::ValueFlag _scalemax(parser, "scalemax", "Maximum value of color scale", {'M', "scalemax"}); args::Flag _doLogScale(parser, "logscale", "Whether to use a log scale", {'l',"logscale"}); // Parse arguments - try - { + try { parser.ParseCLI(argc, argv); } - catch (args::Help) - { + catch (args::Help) { std::cout << parser; return 0; } - catch (args::ParseError e) - { + catch (args::ParseError e) { std::cerr << e.what() << std::endl; std::cerr << parser; return 1; } - catch (args::ValidationError e) - { + catch (args::ValidationError e) { std::cerr << e.what() << std::endl; std::cerr << parser; return 1; } + float scale = 1.0; int staticMin = -1; int staticMax = -1; if (_scale) scale = args::get(_scale); - std::string output = "window"; - if (_output) - output = args::get(_output); + + std::string mode = "window"; + if (_mode) + mode = args::get(_mode); + std::string camtype = "seek"; if (_camtype) camtype = args::get(_camtype); + // 7fps seems to be about what you get from a seek thermal compact // Note: fps doesn't influence how often frames are processed, just the VideoWriter interpolation int fps = 7; if (_fps) fps = args::get(_fps); - // Colormap int corresponding to enum: http://docs.opencv.org/3.2.0/d3/d50/group__imgproc__colormap.html + + // Colormap int corresponding to enum: https://docs.opencv.org/master/d3/d50/group__imgproc__colormap.html#ga9a805d8262bcbe273f16be9ea2055a65 int colormap = -1; if (_colormap) colormap = args::get(_colormap); + // Rotate default is landscape view to match camera logo/markings int rotate = 270; if (_rotate) rotate = args::get(_rotate); + std::string output = ""; + if (_output) + output = args::get(_output); + + if (output.empty()) { + if (mode == "v4l2") { + std::cout << "Please specify a video device to output to eg: /dev/video0" << std::endl; + return 1; + } else if (mode == "file") { + std::cout << "Please specify a file to save the output to eg: seek.mp4" << std::endl; + return 1; + } + } if (_scalemin) staticMin = args::get(_scalemin); @@ -181,10 +292,11 @@ int main(int argc, char** argv) LibSeek::SeekCam* seek; LibSeek::SeekThermalPro seekpro(args::get(_ffc)); LibSeek::SeekThermal seekclassic(args::get(_ffc)); - if (camtype == "seekpro") + if (camtype == "seekpro") { seek = &seekpro; - else + } else { seek = &seekclassic; + } if (!seek->open()) { std::cout << "Error accessing camera" << std::endl; @@ -198,49 +310,91 @@ int main(int argc, char** argv) // so we can size the VideoWriter stream correctly if (!seek->read(seekframe)) { std::cout << "Failed to read initial frame from camera, exiting" << std::endl; - return -1; + return 1; } int last [2]; last[0]=0; last[1]=65535; + process_frame(seekframe, outframe, scale, colormap, rotate,last,staticMin,staticMax); - //std::vector dp = {cv::Point(212,34),cv::Point(212,35)}; - //seek->add_dead_pixel(dp); - // Create an output object, if output specified then setup the pipeline unless output is set to 'window' + // Setup video for linux if that output is chosen + int v4l2 = -1; + if (mode == "v4l2") { + v4l2 = setup_v4l2(output, outframe.size().width,outframe.size().height); + } + + // Create an output object, if mode specified then setup the pipeline unless mode is set to 'window' VideoWriter writer; - if (output != "window") { + if (mode == "file") { +#if CV_MAJOR_VERSION > 2 writer.open(output, VideoWriter::fourcc('F', 'M', 'P', '4'), fps, Size(outframe.cols, outframe.rows)); +#else + writer.open(output, CV_FOURCC('F', 'M', 'P', '4'), fps, Size(outframe.cols, outframe.rows)); +#endif + if (!writer.isOpened()) { std::cerr << "Error can't create video writer" << std::endl; return 1; } std::cout << "Video stream created, dimension: " << outframe.cols << "x" << outframe.rows << ", fps:" << fps << std::endl; + } else if (mode == "window") { + startWindowThread(); + namedWindow(WINDOW_NAME, cv::WINDOW_NORMAL); + setWindowProperty(WINDOW_NAME, WindowPropertyFlags::WND_PROP_ASPECT_RATIO, cv::WINDOW_KEEPRATIO); + resizeWindow(WINDOW_NAME, outframe.cols, outframe.rows); } - // Main loop to retrieve frames from camera and output + + // Main loop to retrieve frames from camera and write them out + if (!seek->read2_starter()) { + std::cout << "Failed to request frame from camera, exiting" << std::endl; + return 1; + } while (!sigflag) { // If signal for interrupt/termination was received, break out of main loop and exit - if (!seek->read(seekframe)) { + if (!seek->read2(seekframe)) { std::cout << "Failed to read frame from camera, exiting" << std::endl; - return -1; + return 1; } - + if (!seek->read2_starter()) { + std::cout << "Failed to request frame from camera, exiting" << std::endl; + return 1; + } + //std::cout << seek->frame_counter() << std::endl; // Retrieve frame from seek and process if(logScale){ seekframe.convertTo(seekframe,CV_64F); cv::log(seekframe,seekframe); } - process_frame(seekframe, outframe, scale, colormap, rotate,last,staticMin,staticMax); + if (auto_exposure_lock) { + process_frame(seekframe, outframe, scale, colormap, rotate,last,last[0],last[1]); + } else { + process_frame(seekframe, outframe, scale, colormap, rotate,last,staticMin,staticMax); + } //std::cout << outframe.type() <read2(seekframe); std::cout << "Break signal detected, exiting" << std::endl; return 0; } diff --git a/examples/seek_viewer_fb.cpp b/examples/seek_viewer_fb.cpp index 6eef279..91a5023 100644 --- a/examples/seek_viewer_fb.cpp +++ b/examples/seek_viewer_fb.cpp @@ -1,6 +1,10 @@ // Seek Thermal Viewer/Streamer // http://github.com/fnoop/maverick +#ifdef __APPLE__ +int main(int argc, char** argv) {} +#else + //#include #include #include "seek.h" @@ -303,12 +307,20 @@ int main(int argc, char** argv) int textcounter; textcounter=0; // Main loop to retrieve frames from camera and output + if (!seek->read2_starter()) { + std::cout << "Failed to request frame from camera, exiting" << std::endl; + return 1; + } while (!sigflag) { // If signal for interrupt/termination was received, break out of main loop and exit - if (!seek->read(seekframe)) { + if (!seek->read2(seekframe)) { std::cout << "Failed to read frame from camera, exiting" << std::endl; return -1; } + if (!seek->read2_starter()) { + std::cout << "Failed to request frame from camera, exiting" << std::endl; + return 1; + } // Retrieve frame from seek and process process_frame(seekframe, outframe, scale, colormap, rotate,last,staticMin,staticMax); @@ -326,8 +338,15 @@ int main(int argc, char** argv) //Check if user wants a flat frame if (bs.b27>20){ + // grab queued image from camera + seek->read2(seekframe); //do flat field new_flat(seek); + //request another image + if (!seek->read2_starter()) { + std::cout << "Failed to request frame from camera, exiting" << std::endl; + return 1; + } } //Check if user wants to toggle colorbar mode @@ -402,6 +421,8 @@ int main(int argc, char** argv) //outframe.convertTo(outfile, CV_16UC3); cv::imwrite("lastimg_raw.png", seekframe); std::cout<< outframe.depth()<read2(seekframe); std::cout << "Break signal detected, exiting" << std::endl; return 0; } +#endif //APPLE \ No newline at end of file diff --git a/kiyo-ir-mount-v1.stl b/kiyo-ir-mount-v1.stl new file mode 100644 index 0000000..22309da Binary files /dev/null and b/kiyo-ir-mount-v1.stl differ diff --git a/obs_scenes b/obs_scenes new file mode 100644 index 0000000..73a8654 --- /dev/null +++ b/obs_scenes @@ -0,0 +1 @@ +{"AuxAudioDevice1":{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"flags":0,"hotkeys":{"libobs.mute":[],"libobs.push-to-mute":[],"libobs.push-to-talk":[],"libobs.unmute":[]},"id":"pulse_input_capture","mixers":255,"monitoring_type":0,"muted":false,"name":"Mic/Aux","prev_ver":469827586,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"device_id":"alsa_input.usb-Blue_Microphones_Yeti_Stereo_Microphone_LT_1911191106076290034A_111000-00.analog-stereo"},"sync":0,"versioned_id":"pulse_input_capture","volume":1.0},"DesktopAudioDevice1":{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"flags":0,"hotkeys":{"libobs.mute":[],"libobs.push-to-mute":[],"libobs.push-to-talk":[],"libobs.unmute":[]},"id":"pulse_output_capture","mixers":255,"monitoring_type":0,"muted":false,"name":"Desktop Audio","prev_ver":469827586,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"device_id":"default"},"sync":0,"versioned_id":"pulse_output_capture","volume":1.0},"current_program_scene":"intro","current_scene":"intro","current_transition":"Fade","groups":[],"modules":{"auto-scene-switcher":{"active":false,"interval":300,"non_matching_scene":"","switch_if_not_matching":false,"switches":[]},"output-timer":{"autoStartRecordTimer":false,"autoStartStreamTimer":false,"pauseRecordTimer":true,"recordTimerHours":0,"recordTimerMinutes":0,"recordTimerSeconds":30,"streamTimerHours":0,"streamTimerMinutes":0,"streamTimerSeconds":30},"scripts-tool":[]},"name":"Untitled","preview_locked":false,"quick_transitions":[{"duration":300,"fade_to_black":false,"hotkeys":[],"id":1,"name":"Cut"},{"duration":300,"fade_to_black":false,"hotkeys":[],"id":2,"name":"Fade"},{"duration":300,"fade_to_black":true,"hotkeys":[],"id":3,"name":"Fade"}],"saved_projectors":[],"scaling_enabled":false,"scaling_level":0,"scaling_off_x":0.0,"scaling_off_y":0.0,"scene_order":[{"name":"Scene"},{"name":"intro"}],"sources":[{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"flags":0,"hotkeys":{},"id":"xcomposite_input","mixers":0,"monitoring_type":0,"muted":false,"name":"Window Capture (Xcomposite)","prev_ver":469827586,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"capture_window":"92274694\r\nSeekThermal\r\nunknown","cut_bot":15,"cut_top":26},"sync":0,"versioned_id":"xcomposite_input","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"flags":0,"hotkeys":{"OBSBasic.SelectScene":[],"libobs.hide_scene_item.Video Capture Device (V4L2)":[],"libobs.hide_scene_item.Window Capture (Xcomposite)":[],"libobs.show_scene_item.Video Capture Device (V4L2)":[],"libobs.show_scene_item.Window Capture (Xcomposite)":[]},"id":"scene","mixers":0,"monitoring_type":0,"muted":false,"name":"Scene","prev_ver":469827586,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"custom_size":false,"id_counter":6,"items":[{"align":5,"blend_method":"default","blend_type":"normal","bounds":{"x":0.0,"y":0.0},"bounds_align":0,"bounds_type":0,"crop_bottom":0,"crop_left":0,"crop_right":0,"crop_top":0,"group_item_backup":false,"hide_transition":{"duration":0},"id":6,"locked":false,"name":"Video Capture Device (V4L2)","pos":{"x":696.0,"y":0.0},"private_settings":{},"rot":0.0,"scale":{"x":2.4000000953674316,"y":2.4000000953674316},"scale_filter":"disable","show_transition":{"duration":0},"visible":true},{"align":5,"blend_method":"default","blend_type":"normal","bounds":{"x":0.0,"y":0.0},"bounds_align":0,"bounds_type":0,"crop_bottom":0,"crop_left":0,"crop_right":0,"crop_top":0,"group_item_backup":false,"hide_transition":{"duration":0},"id":2,"locked":false,"name":"Window Capture (Xcomposite)","pos":{"x":0.0,"y":127.0},"private_settings":{},"rot":0.0,"scale":{"x":1.6124999523162842,"y":1.611742377281189},"scale_filter":"disable","show_transition":{"duration":0},"visible":true}]},"sync":0,"versioned_id":"scene","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"filters":[{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":false,"flags":0,"hotkeys":{},"id":"color_filter","mixers":0,"monitoring_type":0,"muted":false,"name":"Color Correction","prev_ver":469827586,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"brightness":-0.080000000000000002,"contrast":0.029999999999999999,"gamma":-0.17999999999999999,"opacity":100,"saturation":0.35999999999999999},"sync":0,"versioned_id":"color_filter","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":false,"flags":0,"hotkeys":{},"id":"chroma_key_filter","mixers":0,"monitoring_type":0,"muted":false,"name":"Chroma Key","prev_ver":469827586,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"key_color":4284252205,"key_color_type":"custom","similarity":60,"smoothness":51,"spill":1},"sync":0,"versioned_id":"chroma_key_filter","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":false,"flags":0,"hotkeys":{},"id":"chroma_key_filter","mixers":0,"monitoring_type":0,"muted":false,"name":"Chroma Key 2","prev_ver":469827586,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"key_color":4283718200,"key_color_type":"custom","similarity":8,"smoothness":48,"spill":1},"sync":0,"versioned_id":"chroma_key_filter","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":false,"flags":0,"hotkeys":{},"id":"chroma_key_filter","mixers":0,"monitoring_type":0,"muted":false,"name":"Chroma Key 4","prev_ver":469827586,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"key_color":4284778263,"key_color_type":"custom","similarity":1,"smoothness":41,"spill":84},"sync":0,"versioned_id":"chroma_key_filter","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"flags":0,"hotkeys":{},"id":"crop_filter","mixers":0,"monitoring_type":0,"muted":false,"name":"Crop/Pad","prev_ver":469827586,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"bottom":15,"left":30,"right":0,"top":15},"sync":0,"versioned_id":"crop_filter","volume":1.0}],"flags":0,"hotkeys":{},"id":"v4l2_input","mixers":0,"monitoring_type":0,"muted":false,"name":"Video Capture Device (V4L2)","prev_ver":469827586,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"Brightness":128,"Exposure (Absolute)":161,"Exposure, Auto":1,"Exposure, Auto Priority":false,"Focus (absolute)":177,"Gain":0,"Power Line Frequency":0,"Saturation":130,"White Balance Temperature":3996,"White Balance Temperature, Auto":false,"buffering":false,"color_range":0,"device_id":"/dev/video2","input":0,"pixelformat":1196444237},"sync":0,"versioned_id":"v4l2_input","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"flags":0,"hotkeys":{"OBSBasic.SelectScene":[],"libobs.hide_scene_item.Image 2":[],"libobs.hide_scene_item.Image 3":[],"libobs.hide_scene_item.Video Capture Device (V4L2)":[],"libobs.show_scene_item.Image 2":[],"libobs.show_scene_item.Image 3":[],"libobs.show_scene_item.Video Capture Device (V4L2)":[]},"id":"scene","mixers":0,"monitoring_type":0,"muted":false,"name":"intro","prev_ver":469827586,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"custom_size":false,"id_counter":3,"items":[{"align":5,"blend_method":"default","blend_type":"normal","bounds":{"x":0.0,"y":0.0},"bounds_align":0,"bounds_type":0,"crop_bottom":0,"crop_left":0,"crop_right":0,"crop_top":0,"group_item_backup":false,"hide_transition":{"duration":0},"id":2,"locked":false,"name":"Image 2","pos":{"x":220.0,"y":0.0},"private_settings":{},"rot":0.0,"scale":{"x":1.7999999523162842,"y":1.7999999523162842},"scale_filter":"disable","show_transition":{"duration":0},"visible":true},{"align":5,"blend_method":"default","blend_type":"normal","bounds":{"x":0.0,"y":0.0},"bounds_align":0,"bounds_type":0,"crop_bottom":0,"crop_left":0,"crop_right":0,"crop_top":0,"group_item_backup":false,"hide_transition":{"duration":0},"id":3,"locked":false,"name":"Image 3","pos":{"x":0.0,"y":0.0},"private_settings":{},"rot":0.0,"scale":{"x":1.0,"y":1.0},"scale_filter":"disable","show_transition":{"duration":0},"visible":false},{"align":5,"blend_method":"default","blend_type":"normal","bounds":{"x":0.0,"y":0.0},"bounds_align":0,"bounds_type":0,"crop_bottom":0,"crop_left":0,"crop_right":0,"crop_top":0,"group_item_backup":false,"hide_transition":{"duration":0},"id":1,"locked":false,"name":"Video Capture Device (V4L2)","pos":{"x":210.0,"y":0.0},"private_settings":{},"rot":0.0,"scale":{"x":2.4000000953674316,"y":2.4000000953674316},"scale_filter":"disable","show_transition":{"duration":0},"visible":true}]},"sync":0,"versioned_id":"scene","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"flags":0,"hotkeys":{},"id":"image_source","mixers":0,"monitoring_type":0,"muted":false,"name":"Image 2","prev_ver":469827586,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"file":"/home/christopher/Pictures/em_spectrum.jpg"},"sync":0,"versioned_id":"image_source","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"flags":0,"hotkeys":{},"id":"image_source","mixers":0,"monitoring_type":0,"muted":false,"name":"Image 3","prev_ver":469827586,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"file":"/home/christopher/Pictures/FarCollage.png"},"sync":0,"versioned_id":"image_source","volume":1.0}],"transition_duration":300,"transitions":[],"virtual-camera":{"internal":0,"scene":"","source":"","type":0}} \ No newline at end of file diff --git a/obs_scenes.json b/obs_scenes.json new file mode 100644 index 0000000..9e054c5 --- /dev/null +++ b/obs_scenes.json @@ -0,0 +1 @@ +{"AuxAudioDevice1":{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"flags":0,"hotkeys":{"libobs.mute":[],"libobs.push-to-mute":[],"libobs.push-to-talk":[],"libobs.unmute":[]},"id":"pulse_input_capture","mixers":255,"monitoring_type":0,"muted":false,"name":"Mic/Aux","prev_ver":486539266,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"device_id":"alsa_input.usb-Blue_Microphones_Yeti_Stereo_Microphone_LT_1911191106076290034A_111000-00.analog-stereo"},"sync":0,"versioned_id":"pulse_input_capture","volume":1.0},"DesktopAudioDevice1":{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"flags":0,"hotkeys":{"libobs.mute":[],"libobs.push-to-mute":[],"libobs.push-to-talk":[],"libobs.unmute":[]},"id":"pulse_output_capture","mixers":255,"monitoring_type":0,"muted":false,"name":"Desktop Audio","prev_ver":486539266,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"device_id":"default"},"sync":0,"versioned_id":"pulse_output_capture","volume":1.0},"current_program_scene":"Scene","current_scene":"Scene","current_transition":"Fade","groups":[],"modules":{"auto-scene-switcher":{"active":false,"interval":300,"non_matching_scene":"","switch_if_not_matching":false,"switches":[]},"output-timer":{"autoStartRecordTimer":false,"autoStartStreamTimer":false,"pauseRecordTimer":true,"recordTimerHours":0,"recordTimerMinutes":0,"recordTimerSeconds":30,"streamTimerHours":0,"streamTimerMinutes":0,"streamTimerSeconds":30},"scripts-tool":[]},"name":"Untitled","preview_locked":false,"quick_transitions":[{"duration":300,"fade_to_black":false,"hotkeys":[],"id":1,"name":"Cut"},{"duration":300,"fade_to_black":false,"hotkeys":[],"id":2,"name":"Fade"},{"duration":300,"fade_to_black":true,"hotkeys":[],"id":3,"name":"Fade"}],"saved_projectors":[],"scaling_enabled":false,"scaling_level":0,"scaling_off_x":0.0,"scaling_off_y":0.0,"scene_order":[{"name":"Scene"},{"name":"intro"}],"sources":[{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"flags":0,"hotkeys":{},"id":"image_source","mixers":0,"monitoring_type":0,"muted":false,"name":"Image 3","prev_ver":486539266,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"file":"/home/christopher/Pictures/FarCollage.png"},"sync":0,"versioned_id":"image_source","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"flags":0,"hotkeys":{},"id":"image_source","mixers":0,"monitoring_type":0,"muted":false,"name":"Image 2","prev_ver":486539266,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"file":"/home/christopher/Pictures/em_spectrum.jpg"},"sync":0,"versioned_id":"image_source","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"flags":0,"hotkeys":{"OBSBasic.SelectScene":[],"libobs.hide_scene_item.Image 2":[],"libobs.hide_scene_item.Image 3":[],"libobs.show_scene_item.Image 2":[],"libobs.show_scene_item.Image 3":[]},"id":"scene","mixers":0,"monitoring_type":0,"muted":false,"name":"intro","prev_ver":486539266,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"custom_size":false,"id_counter":3,"items":[{"align":5,"blend_method":"default","blend_type":"normal","bounds":{"x":0.0,"y":0.0},"bounds_align":0,"bounds_type":0,"crop_bottom":0,"crop_left":0,"crop_right":0,"crop_top":0,"group_item_backup":false,"hide_transition":{"duration":0},"id":2,"locked":false,"name":"Image 2","pos":{"x":220.0,"y":0.0},"private_settings":{},"rot":0.0,"scale":{"x":1.7999999523162842,"y":1.7999999523162842},"scale_filter":"disable","show_transition":{"duration":0},"visible":true},{"align":5,"blend_method":"default","blend_type":"normal","bounds":{"x":0.0,"y":0.0},"bounds_align":0,"bounds_type":0,"crop_bottom":0,"crop_left":0,"crop_right":0,"crop_top":0,"group_item_backup":false,"hide_transition":{"duration":0},"id":3,"locked":false,"name":"Image 3","pos":{"x":0.0,"y":0.0},"private_settings":{},"rot":0.0,"scale":{"x":1.0,"y":1.0},"scale_filter":"disable","show_transition":{"duration":0},"visible":false}]},"sync":0,"versioned_id":"scene","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"filters":[{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":false,"flags":0,"hotkeys":{},"id":"color_filter","mixers":0,"monitoring_type":0,"muted":false,"name":"Color Correction","prev_ver":486539266,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"brightness":-0.080000000000000002,"contrast":0.029999999999999999,"gamma":-0.17999999999999999,"opacity":100,"saturation":0.35999999999999999},"sync":0,"versioned_id":"color_filter","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":false,"flags":0,"hotkeys":{},"id":"chroma_key_filter","mixers":0,"monitoring_type":0,"muted":false,"name":"Chroma Key","prev_ver":486539266,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"key_color":4284252205,"key_color_type":"custom","similarity":60,"smoothness":51,"spill":1},"sync":0,"versioned_id":"chroma_key_filter","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":false,"flags":0,"hotkeys":{},"id":"chroma_key_filter","mixers":0,"monitoring_type":0,"muted":false,"name":"Chroma Key 2","prev_ver":486539266,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"key_color":4283718200,"key_color_type":"custom","similarity":8,"smoothness":48,"spill":1},"sync":0,"versioned_id":"chroma_key_filter","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":false,"flags":0,"hotkeys":{},"id":"chroma_key_filter","mixers":0,"monitoring_type":0,"muted":false,"name":"Chroma Key 4","prev_ver":486539266,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"key_color":4284778263,"key_color_type":"custom","similarity":1,"smoothness":41,"spill":84},"sync":0,"versioned_id":"chroma_key_filter","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"flags":0,"hotkeys":{},"id":"crop_filter","mixers":0,"monitoring_type":0,"muted":false,"name":"Crop/Pad","prev_ver":486539266,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"bottom":15,"left":30,"right":0,"top":15},"sync":0,"versioned_id":"crop_filter","volume":1.0}],"flags":0,"hotkeys":{},"id":"v4l2_input","mixers":0,"monitoring_type":0,"muted":false,"name":"Video Capture Device (V4L2)","prev_ver":486539266,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"Exposure (Absolute)":593,"Exposure, Auto Priority":false,"Gain":0,"buffering":false,"color_range":0,"device_id":"/dev/video2","input":0,"pixelformat":1196444237},"sync":0,"versioned_id":"v4l2_input","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"flags":0,"hotkeys":{"OBSBasic.SelectScene":[],"libobs.hide_scene_item.Video Capture Device (V4L2)":[],"libobs.hide_scene_item.Window Capture (Xcomposite)":[],"libobs.hide_scene_item.title":[],"libobs.hide_scene_item.title 2":[],"libobs.show_scene_item.Video Capture Device (V4L2)":[],"libobs.show_scene_item.Window Capture (Xcomposite)":[],"libobs.show_scene_item.title":[],"libobs.show_scene_item.title 2":[]},"id":"scene","mixers":0,"monitoring_type":0,"muted":false,"name":"Scene","prev_ver":486539266,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"custom_size":false,"id_counter":8,"items":[{"align":5,"blend_method":"default","blend_type":"normal","bounds":{"x":0.0,"y":0.0},"bounds_align":0,"bounds_type":0,"crop_bottom":0,"crop_left":0,"crop_right":0,"crop_top":0,"group_item_backup":false,"hide_transition":{"duration":0},"id":6,"locked":false,"name":"Video Capture Device (V4L2)","pos":{"x":696.0,"y":0.0},"private_settings":{},"rot":0.0,"scale":{"x":2.4000000953674316,"y":2.4000000953674316},"scale_filter":"disable","show_transition":{"duration":0},"visible":true},{"align":5,"blend_method":"default","blend_type":"normal","bounds":{"x":0.0,"y":0.0},"bounds_align":0,"bounds_type":0,"crop_bottom":0,"crop_left":0,"crop_right":0,"crop_top":0,"group_item_backup":false,"hide_transition":{"duration":0},"id":2,"locked":false,"name":"Window Capture (Xcomposite)","pos":{"x":0.0,"y":102.0},"private_settings":{},"rot":0.0,"scale":{"x":2.2081711292266846,"y":2.2072539329528809},"scale_filter":"disable","show_transition":{"duration":0},"visible":true},{"align":5,"blend_method":"default","blend_type":"normal","bounds":{"x":0.0,"y":0.0},"bounds_align":0,"bounds_type":0,"crop_bottom":0,"crop_left":0,"crop_right":0,"crop_top":0,"group_item_backup":false,"hide_transition":{"duration":0},"id":7,"locked":false,"name":"title","pos":{"x":21.0,"y":0.0},"private_settings":{},"rot":0.0,"scale":{"x":1.0,"y":1.0},"scale_filter":"disable","show_transition":{"duration":0},"visible":true},{"align":5,"blend_method":"default","blend_type":"normal","bounds":{"x":0.0,"y":0.0},"bounds_align":0,"bounds_type":0,"crop_bottom":0,"crop_left":0,"crop_right":0,"crop_top":0,"group_item_backup":false,"hide_transition":{"duration":0},"id":8,"locked":false,"name":"title 2","pos":{"x":18.0,"y":937.0},"private_settings":{},"rot":0.0,"scale":{"x":1.0,"y":1.0},"scale_filter":"disable","show_transition":{"duration":0},"visible":true}]},"sync":0,"versioned_id":"scene","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"flags":0,"hotkeys":{},"id":"xcomposite_input","mixers":0,"monitoring_type":0,"muted":false,"name":"Window Capture (Xcomposite)","prev_ver":486539266,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"capture_window":"67108872\r\nSeekThermal\r\nunknown","cut_bot":20,"cut_left":3,"cut_right":3,"cut_top":34,"swap_redblue":false},"sync":0,"versioned_id":"xcomposite_input","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"flags":0,"hotkeys":{},"id":"text_ft2_source","mixers":0,"monitoring_type":0,"muted":false,"name":"title","prev_ver":486539266,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"font":{"face":"Sans Serif","flags":0,"size":80,"style":""},"text":"Infrared Camera"},"sync":0,"versioned_id":"text_ft2_source_v2","volume":1.0},{"balance":0.5,"deinterlace_field_order":0,"deinterlace_mode":0,"enabled":true,"flags":0,"hotkeys":{},"id":"text_ft2_source","mixers":0,"monitoring_type":0,"muted":false,"name":"title 2","prev_ver":486539266,"private_settings":{},"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"settings":{"font":{"face":"Sans Serif","flags":0,"size":80,"style":""},"text":"9-18 μm (thermal)"},"sync":0,"versioned_id":"text_ft2_source_v2","volume":1.0}],"transition_duration":300,"transitions":[],"virtual-camera":{"internal":0,"scene":"","source":"","type":0}} \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4f933c2..16245cc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,7 +19,7 @@ set (SOURCES set (SRC ${SOURCES} ${HEADERS}) include_directories ( - /usr/include/libusb-1.0 + ${LIBUSB_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS} ) @@ -27,8 +27,8 @@ add_library (seek_static STATIC ${SRC}) add_library (seek SHARED ${SRC}) target_link_libraries (seek + ${LIBUSB_LIBRARIES} ${OpenCV_LIBS} - -lusb-1.0 ) # install library target and header files diff --git a/src/SeekCam.cpp b/src/SeekCam.cpp index d43e375..34714cc 100644 --- a/src/SeekCam.cpp +++ b/src/SeekCam.cpp @@ -14,21 +14,30 @@ #include "SeekCam.h" #include "SeekLogging.h" #include +#include +#include using namespace LibSeek; -SeekCam::SeekCam(int vendor_id, int product_id, uint16_t* buffer, size_t raw_height, size_t raw_width, cv::Rect roi, std::string ffc_filename) : +SeekCam::SeekCam(int vendor_id, int product_id, uint16_t* buffer, size_t raw_height, size_t raw_width, size_t request_size, cv::Rect roi, std::string ffc_filename) : m_offset(0x4000), m_ffc_filename(ffc_filename), m_is_opened(false), m_dev(vendor_id, product_id), m_raw_data(buffer), m_raw_data_size(raw_height * raw_width), + m_request_size(request_size), m_raw_frame(raw_height, raw_width, CV_16UC1, buffer, cv::Mat::AUTO_STEP), + m_full_raw_frame(raw_height, + raw_width, + CV_16UC1, + buffer, + cv::Mat::AUTO_STEP), + m_flat_field_calibration_frame(), m_additional_ffc(), m_dead_pixel_mask() @@ -94,6 +103,7 @@ void SeekCam::close() m_dev.request_set(DeviceCommand::SET_OPERATION_MODE, data); m_dev.request_set(DeviceCommand::SET_OPERATION_MODE, data); m_dev.request_set(DeviceCommand::SET_OPERATION_MODE, data); + m_dev.close(); } m_is_opened = false; } @@ -121,11 +131,40 @@ bool SeekCam::grab() m_raw_frame.copyTo(m_flat_field_calibration_frame); //cv::imwrite("lastdark_raw.png", m_raw_frame); } + + } + return false; +} + +bool SeekCam::grab2() +{ + int i; + + for (i=0; i<40; i++) { + if(!cam_fetch_frame()) { + error("Error: frame acquisition failed\n"); + return false; + } + if (frame_id() == 3) { + return true; + //does not initiate another frame request, is that bad? + // no, we expect the caller to run read2_starter right after this (I guess) + } else if (frame_id() == 1) { + //std::cout<<"Got ffc"<12000){ + std::cout << "tHrEw AwAy A fRaMe!" << std::endl; + read2_starter(); + if (!grab2()) + return false; + retrieve(dst); + } + + return true; +} + +bool SeekCam::read2_starter() +{ + return request_frame(); +} + + void SeekCam::convertToGreyScale(cv::Mat& src, cv::Mat& dst) { double tmin, tmax, rsize; @@ -238,7 +304,7 @@ bool SeekCam::open_cam() return false; } -bool SeekCam::get_frame() +bool SeekCam::request_frame() { /* request new frame */ uint8_t* s = reinterpret_cast(&m_raw_data_size); @@ -246,14 +312,34 @@ bool SeekCam::get_frame() std::vector data = { s[0], s[1], s[2], s[3] }; if (!m_dev.request_set(DeviceCommand::START_GET_IMAGE_TRANSFER, data)) return false; + return true; +} - /* store frame data */ - if (!m_dev.fetch_frame(m_raw_data, m_raw_data_size)) +bool SeekCam::cam_fetch_frame() +{ + if (!m_dev.fetch_frame(m_raw_data, m_raw_data_size, m_request_size)) return false; - return true; } +bool SeekCam::get_frame() +{ + if (!request_frame()) + return false; + /* store frame data */ + + return cam_fetch_frame(); + + + // std::ostringstream filename; + // filename << "frame_" << frame_counter() << ".png"; + // cv::imwrite(filename.str(), m_full_raw_frame); + + // std::cout << m_raw_data[5] << std::endl; + + +} + void SeekCam::print_usb_data(std::vector& data) { std::stringstream ss; diff --git a/src/SeekCam.h b/src/SeekCam.h index 69e85e2..50eed3c 100644 --- a/src/SeekCam.h +++ b/src/SeekCam.h @@ -35,6 +35,7 @@ class SeekCam * Returns true on success */ bool grab(); + bool grab2(); /* * Retrieve the last grabbed 14-bit frame @@ -55,6 +56,15 @@ class SeekCam */ bool read(cv::Mat& dst); + /* + * Read grabs and retrieves a frame + * Returns true on success + * designed for use in for loop. Does not request a new frame + * beforehand. Use read2_starter beforehand to request the frame. + */ + bool read2(cv::Mat& dst); + bool read2_starter(); + void add_dead_pixel(std::vector new_dead_pixels); bool set_additional_ffc(cv::Mat& newframe); /* @@ -64,13 +74,15 @@ class SeekCam protected: - SeekCam(int vendor_id, int product_id, uint16_t* buffer, size_t raw_height, size_t raw_width, cv::Rect roi, std::string ffc_filename); + SeekCam(int vendor_id, int product_id, uint16_t* buffer, size_t raw_height, size_t raw_width, size_t request_size, cv::Rect roi, std::string ffc_filename); ~SeekCam(); virtual bool init_cam() = 0; virtual int frame_id() = 0; bool open_cam(); bool get_frame(); + bool request_frame(); + bool cam_fetch_frame(); void print_usb_data(std::vector& data); void create_dead_pixel_list(cv::Mat frame, cv::Mat& dead_pixel_mask, std::vector& dead_pixel_list); @@ -87,8 +99,10 @@ class SeekCam SeekDevice m_dev; uint16_t* m_raw_data; size_t m_raw_data_size; + size_t m_request_size; cv::Mat m_raw_frame; //cv::Mat m_initial_frame; + cv::Mat m_full_raw_frame; cv::Mat m_flat_field_calibration_frame; cv::Mat m_additional_ffc; cv::Mat m_dead_pixel_mask; diff --git a/src/SeekDevice.cpp b/src/SeekDevice.cpp index f7f4701..e8106c3 100644 --- a/src/SeekDevice.cpp +++ b/src/SeekDevice.cpp @@ -6,8 +6,15 @@ #include "SeekDevice.h" #include "SeekLogging.h" #include -#include -#include +#ifdef __APPLE__ + #include "macendian.h" +#else + #include +#endif +#include +#include + +#include using namespace LibSeek; @@ -78,6 +85,7 @@ bool SeekDevice::open() void SeekDevice::close() { + if (m_handle != NULL) { libusb_release_interface(m_handle, 0); /* release claim */ libusb_close(m_handle); /* revert open */ @@ -86,7 +94,7 @@ void SeekDevice::close() if (m_ctx != NULL) { libusb_exit(m_ctx); /* revert exit */ - m_ctx = NULL; + m_ctx = NULL; } m_is_opened = false; @@ -107,26 +115,33 @@ bool SeekDevice::request_get(DeviceCommand::Enum command, std::vector& return control_transfer(1, static_cast(command), 0, 0, data); } -bool SeekDevice::fetch_frame(uint16_t* buffer, std::size_t size) +bool SeekDevice::fetch_frame(uint16_t* buffer, std::size_t size, std::size_t request_size) { + auto t1 = std::chrono::high_resolution_clock::now(); + auto t2 = std::chrono::high_resolution_clock::now(); int res; int actual_length; int todo = size * sizeof(uint16_t); uint8_t* buf = reinterpret_cast(buffer); int done = 0; - + debug("Starting new transfer!"); while (todo != 0) { - debug("Asking for %d B of data at %d\n", todo, done); - res = libusb_bulk_transfer(m_handle, 0x81, &buf[done], todo, &actual_length, m_timeout); - if ((res != 0) && (res != LIBUSB_ERROR_TIMEOUT)) { + + debug("Asking for %d B of data at %d\n", request_size, done); + res = libusb_bulk_transfer(m_handle, 0x81, &buf[done], request_size, &actual_length, m_timeout); + t2 = std::chrono::high_resolution_clock::now(); + std::chrono::duration fp_ms = t2 - t1; + double test = static_cast(fp_ms.count()); + debug("took %f ms", test); + t1 = t2; + if (res == LIBUSB_ERROR_TIMEOUT) + { + error("Error: LIBUSB_ERROR_TIMEOUT\n"); + } else if (res != 0) { error("Error: bulk transfer failed: %s\n", libusb_error_name(res)); return false; } - // Sometimes we have less data transferred than expected - so just return this "broken" frame and keep going - if (res == LIBUSB_ERROR_TIMEOUT){ - error("We have libusb timeout here: %s\n", libusb_error_name(res)); - return true; - } + debug("Actual length %d\n", actual_length); todo -= actual_length; diff --git a/src/SeekDevice.h b/src/SeekDevice.h index 951721c..1a5d42e 100644 --- a/src/SeekDevice.h +++ b/src/SeekDevice.h @@ -104,11 +104,12 @@ class SeekDevice /* * get a raw camera frame previously requested - * buffer: buffer to store the received frame - * size: number of uint16_t words the buffer can hold + * buffer: buffer to store the received frame + * size: number of uint16_t words the buffer can hold + * request_size: number of bytes to request in each read * Returns true on success */ - bool fetch_frame(uint16_t* buffer, std::size_t size); + bool fetch_frame(uint16_t* buffer, std::size_t size, std::size_t request_size); private: int m_vendor_id; diff --git a/src/SeekLogging.h b/src/SeekLogging.h index 38fcc98..93fc55e 100644 --- a/src/SeekLogging.h +++ b/src/SeekLogging.h @@ -8,7 +8,7 @@ #include - +// #define SEEK_DEBUG #ifdef SEEK_DEBUG #define debug(fmt, ...) printf("%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ## __VA_ARGS__); #define error(fmt, ...) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ## __VA_ARGS__); diff --git a/src/SeekThermal.cpp b/src/SeekThermal.cpp index 236e854..315579e 100644 --- a/src/SeekThermal.cpp +++ b/src/SeekThermal.cpp @@ -5,7 +5,12 @@ #include "SeekThermal.h" #include "SeekLogging.h" -#include +#ifdef __APPLE__ + #include "macendian.h" +#else + #include +#endif + using namespace LibSeek; @@ -15,7 +20,7 @@ SeekThermal::SeekThermal() : SeekThermal::SeekThermal(std::string ffc_filename) : SeekCam(0x289d, 0x0010, m_buffer, - THERMAL_RAW_HEIGHT, THERMAL_RAW_WIDTH, + THERMAL_RAW_HEIGHT, THERMAL_RAW_WIDTH, THERMAL_REQUEST_SIZE, cv::Rect(0, 1, THERMAL_WIDTH, THERMAL_HEIGHT), ffc_filename) { } diff --git a/src/SeekThermal.h b/src/SeekThermal.h index 8c28991..877a692 100644 --- a/src/SeekThermal.h +++ b/src/SeekThermal.h @@ -13,6 +13,7 @@ #define THERMAL_HEIGHT 154 #define THERMAL_RAW_WIDTH 208 #define THERMAL_RAW_HEIGHT 156 +#define THERMAL_REQUEST_SIZE 16224 #define THERMAL_RAW_SIZE (THERMAL_RAW_WIDTH * THERMAL_RAW_HEIGHT) namespace LibSeek { diff --git a/src/SeekThermalPro.cpp b/src/SeekThermalPro.cpp index 558c572..23d41f0 100644 --- a/src/SeekThermalPro.cpp +++ b/src/SeekThermalPro.cpp @@ -5,7 +5,12 @@ #include "SeekThermalPro.h" #include "SeekLogging.h" -#include +#ifdef __APPLE__ + #include "macendian.h" +#else + #include +#endif + using namespace LibSeek; @@ -15,43 +20,55 @@ SeekThermalPro::SeekThermalPro() : SeekThermalPro::SeekThermalPro(std::string ffc_filename) : SeekCam(0x289d, 0x0011, m_buffer, - THERMAL_PRO_RAW_HEIGHT, THERMAL_PRO_RAW_WIDTH, + THERMAL_PRO_RAW_HEIGHT, THERMAL_PRO_RAW_WIDTH, THERMAL_PRO_REQUEST_SIZE, cv::Rect(1, 4, THERMAL_PRO_WIDTH, THERMAL_PRO_HEIGHT), ffc_filename) { } bool SeekThermalPro::init_cam() { + debug("init_cam"); { std::vector data = { 0x01 }; if (!m_dev.request_set(DeviceCommand::TARGET_PLATFORM, data)) { /* deinit and retry if cam was not properly closed */ + debug("closing"); close(); + debug("closing dev"); + m_dev.close(); + debug("opening dev"); + m_dev.open(); + debug("retrying"); if (!m_dev.request_set(DeviceCommand::TARGET_PLATFORM, data)) return false; } } + debug("set op mode"); { std::vector data = { 0x00, 0x00 }; if (!m_dev.request_set(DeviceCommand::SET_OPERATION_MODE, data)) return false; } + debug("get fw info"); { std::vector data(4); if (!m_dev.request_get(DeviceCommand::GET_FIRMWARE_INFO, data)) return false; print_usb_data(data); } + debug("read chip id"); { std::vector data(12); if (!m_dev.request_get(DeviceCommand::READ_CHIP_ID, data)) return false; print_usb_data(data); } + debug("set factsory settings features"); { std::vector data = { 0x06, 0x00, 0x08, 0x00, 0x00, 0x00 }; if (!m_dev.request_set(DeviceCommand::SET_FACTORY_SETTINGS_FEATURES, data)) return false; } + debug("get factory settings"); { std::vector data(12); if (!m_dev.request_get(DeviceCommand::GET_FACTORY_SETTINGS, data)) diff --git a/src/SeekThermalPro.h b/src/SeekThermalPro.h index 7d6d28a..881bc93 100644 --- a/src/SeekThermalPro.h +++ b/src/SeekThermalPro.h @@ -13,6 +13,7 @@ #define THERMAL_PRO_HEIGHT 240 #define THERMAL_PRO_RAW_WIDTH 342 #define THERMAL_PRO_RAW_HEIGHT 260 +#define THERMAL_PRO_REQUEST_SIZE 13680 #define THERMAL_PRO_RAW_SIZE (THERMAL_PRO_RAW_WIDTH * THERMAL_PRO_RAW_HEIGHT) namespace LibSeek { diff --git a/src/framebuffer.cpp b/src/framebuffer.cpp index f682d28..1b3314a 100644 --- a/src/framebuffer.cpp +++ b/src/framebuffer.cpp @@ -4,6 +4,7 @@ * Heavily copied from someone's framebuffer test code. Maybe QT? I don't remember. */ +#ifndef __APPLE__ #include "framebuffer.h" using namespace LibSeek; @@ -14,7 +15,6 @@ Framebuffer::Framebuffer(const char* filename): m_finfo(), m_screensize(0), m_fbp(), - m_imagedata(), m_uncopiedrows(0) { @@ -44,7 +44,6 @@ Framebuffer::Framebuffer(const char* filename): // (m_vinfo.yres+m_vinfo.yoffset) * m_finfo.line_length; m_fbp = (char *)mmap(0, m_screensize, PROT_READ | PROT_WRITE, MAP_SHARED, m_fbfd, 0); - m_imagedata = (char *)malloc(m_screensize); if ((int64_t)m_fbp == -1) { perror("Error: failed to map framebuffer device to memory"); exit(4); @@ -61,7 +60,6 @@ Framebuffer::~Framebuffer() } void Framebuffer::closefb(){ munmap(m_fbp,m_screensize); - free(m_imagedata); close(m_fbfd); } void Framebuffer::setUncopiedRows(uint numrows){ @@ -82,48 +80,16 @@ float Framebuffer::getScale(cv::Mat& frame){ } void Framebuffer::writeFrame(cv::Mat& frame){ + cv::Mat img_fb; + if (m_vinfo.bits_per_pixel==32) + img_fb = frame; + else if(m_vinfo.bits_per_pixel==16) + cv::cvtColor(frame, img_fb, cv::COLOR_BGR2BGR565); + else + printf("bits per pixel == %d NYI",m_vinfo.bits_per_pixel); - for (uint row = 0; row < m_vinfo.yres-m_uncopiedrows; row++) { - uint8_t* rowptr = frame.ptr(row); - //flag = true; - // apparently I fell for one of the classic blunders. - // The first is to never get involved in a land war in Asia, - // but the second, only slightly less well-known, is - // never to write directly to an mmaped framebuffer pixel by pixel. - // https://stackoverflow.com/questions/64789056/writing-into-framebuffer-with-a-c-program-is-very-slow-raspberry-pi - for (uint col=0; col>3; - uint8_t green=rowptr[3*col+1]>>2; - uint8_t red=rowptr[3*col+2]>>3; - unsigned short int px = red<<11 | green <<5 | blue; - //I don't know whose method is more readable. I'm leaving both here. - //commented one is mine. - //uint8_t byte1=((green%8)<<5)+blue; - //uint8_t byte2=(red<<3)+(green>>3); - //*(fbp+location)=byte1; - //*(fbp+location+1)=byte2; - *((unsigned short int*)(m_imagedata+location))=px; - }else{ - printf("bits per pixel == %d NYI",m_vinfo.bits_per_pixel); - } - } - } - memcpy(m_fbp, m_imagedata, m_screensize); -} \ No newline at end of file + memcpy(m_fbp, img_fb.data, m_screensize-m_uncopiedrows*m_finfo.line_length); +} +#endif //APPLE \ No newline at end of file diff --git a/src/framebuffer.h b/src/framebuffer.h index a3c74be..85a57d6 100644 --- a/src/framebuffer.h +++ b/src/framebuffer.h @@ -4,6 +4,8 @@ * */ +#ifndef __APPLE__ + #ifndef FRAMEBUFFER_H #define FRAMEBUFFER_H @@ -35,11 +37,11 @@ class Framebuffer struct fb_fix_screeninfo m_finfo; long int m_screensize; char* m_fbp; - char* m_imagedata; uint m_uncopiedrows; }; }/*libseek namespace*/ -#endif //FRAMEBUFFER_H \ No newline at end of file +#endif //FRAMEBUFFER_H +#endif //APPLE \ No newline at end of file diff --git a/src/macendian.h b/src/macendian.h new file mode 100644 index 0000000..f20d070 --- /dev/null +++ b/src/macendian.h @@ -0,0 +1,33 @@ +#ifndef __FINK_ENDIANDEV_PKG_ENDIAN_H__ +#define __FINK_ENDIANDEV_PKG_ENDIAN_H__ 1 + +/** compatibility header for endian.h + * This is a simple compatibility shim to convert + * BSD/Linux endian macros to the Mac OS X equivalents. + * It is public domain. + * */ + +#ifndef __APPLE__ + #warning "This header file (endian.h) is MacOS X specific.\n" +#endif /* __APPLE__ */ + + +#include + +#define htobe16(x) OSSwapHostToBigInt16(x) +#define htole16(x) OSSwapHostToLittleInt16(x) +#define be16toh(x) OSSwapBigToHostInt16(x) +#define le16toh(x) OSSwapLittleToHostInt16(x) + +#define htobe32(x) OSSwapHostToBigInt32(x) +#define htole32(x) OSSwapHostToLittleInt32(x) +#define be32toh(x) OSSwapBigToHostInt32(x) +#define le32toh(x) OSSwapLittleToHostInt32(x) + +#define htobe64(x) OSSwapHostToBigInt64(x) +#define htole64(x) OSSwapHostToLittleInt64(x) +#define be64toh(x) OSSwapBigToHostInt64(x) +#define le64toh(x) OSSwapLittleToHostInt64(x) + + +#endif /* __FINK_ENDIANDEV_PKG_ENDIAN_H__ */ diff --git a/win/endian.h b/win/endian.h new file mode 100644 index 0000000..d1e76f5 --- /dev/null +++ b/win/endian.h @@ -0,0 +1,121 @@ +// "License": Public Domain +// I, Mathias Panzenb�ck, place this file hereby into the public domain. Use it at your own risk for whatever you like. +// In case there are jurisdictions that don't support putting things in the public domain you can also consider it to +// be "dual licensed" under the BSD, MIT and Apache licenses, if you want to. This code is trivial anyway. Consider it +// an example on how to get the endian conversion functions on different platforms. + +#ifndef PORTABLE_ENDIAN_H__ +#define PORTABLE_ENDIAN_H__ + +#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && !defined(__WINDOWS__) + +# define __WINDOWS__ + +#endif + +#if defined(__linux__) || defined(__CYGWIN__) + +# include + +#elif defined(__APPLE__) + +# include + +# define htobe16(x) OSSwapHostToBigInt16(x) +# define htole16(x) OSSwapHostToLittleInt16(x) +# define be16toh(x) OSSwapBigToHostInt16(x) +# define le16toh(x) OSSwapLittleToHostInt16(x) + +# define htobe32(x) OSSwapHostToBigInt32(x) +# define htole32(x) OSSwapHostToLittleInt32(x) +# define be32toh(x) OSSwapBigToHostInt32(x) +# define le32toh(x) OSSwapLittleToHostInt32(x) + +# define htobe64(x) OSSwapHostToBigInt64(x) +# define htole64(x) OSSwapHostToLittleInt64(x) +# define be64toh(x) OSSwapBigToHostInt64(x) +# define le64toh(x) OSSwapLittleToHostInt64(x) + +# define __BYTE_ORDER BYTE_ORDER +# define __BIG_ENDIAN BIG_ENDIAN +# define __LITTLE_ENDIAN LITTLE_ENDIAN +# define __PDP_ENDIAN PDP_ENDIAN + +#elif defined(__OpenBSD__) + +# include + +#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) + +# include + +# define be16toh(x) betoh16(x) +# define le16toh(x) letoh16(x) + +# define be32toh(x) betoh32(x) +# define le32toh(x) letoh32(x) + +# define be64toh(x) betoh64(x) +# define le64toh(x) letoh64(x) + +#elif defined(__WINDOWS__) + +# include + +# if BYTE_ORDER == LITTLE_ENDIAN + +# if defined(_MSC_VER) +# include +# define htobe16(x) _byteswap_ushort(x) +# define htole16(x) (x) +# define be16toh(x) _byteswap_ushort(x) +# define le16toh(x) (x) + +# define htobe32(x) _byteswap_ulong(x) +# define htole32(x) (x) +# define be32toh(x) _byteswap_ulong(x) +# define le32toh(x) (x) + +# define htobe64(x) _byteswap_uint64(x) +# define htole64(x) (x) +# define be64toh(x) _byteswap_uint64(x) +# define le64toh(x) (x) + +# elif defined(__GNUC__) || defined(__clang__) + +# define htobe16(x) __builtin_bswap16(x) +# define htole16(x) (x) +# define be16toh(x) __builtin_bswap16(x) +# define le16toh(x) (x) + +# define htobe32(x) __builtin_bswap32(x) +# define htole32(x) (x) +# define be32toh(x) __builtin_bswap32(x) +# define le32toh(x) (x) + +# define htobe64(x) __builtin_bswap64(x) +# define htole64(x) (x) +# define be64toh(x) __builtin_bswap64(x) +# define le64toh(x) (x) +# else +# error platform not supported +# endif + +# else + +# error byte order not supported + +# endif + +# define __BYTE_ORDER BYTE_ORDER +# define __BIG_ENDIAN BIG_ENDIAN +# define __LITTLE_ENDIAN LITTLE_ENDIAN +# define __PDP_ENDIAN PDP_ENDIAN + +#else + +# error platform not supported + +#endif + +#endif